diff --git a/src/collections/componentSetBuilder.ts b/src/collections/componentSetBuilder.ts index 3372f40374..4a36f6765d 100644 --- a/src/collections/componentSetBuilder.ts +++ b/src/collections/componentSetBuilder.ts @@ -14,7 +14,7 @@ import { SourceComponent } from '../resolve/sourceComponent'; import { ComponentSet } from '../collections/componentSet'; import { RegistryAccess } from '../registry/registryAccess'; import type { FileProperties } from '../client/types'; -import { MetadataType } from '../registry/types'; +import type { MetadataType } from '../registry/types'; import { MetadataResolver } from '../resolve'; import { DestructiveChangesType, FromConnectionOptions } from './types'; @@ -91,15 +91,15 @@ export class ComponentSetBuilder { */ public static async build(options: ComponentSetOptions): Promise { - let componentSet: ComponentSet | undefined; - const { sourcepath, manifest, metadata, packagenames, org } = options; const registry = new RegistryAccess(undefined, options.projectDir); + let componentSetToReturn = new ComponentSet(undefined, registry); if (sourcepath) { getLogger().debug(`Building ComponentSet from sourcepath: ${sourcepath.join(', ')}`); const fsPaths = sourcepath.map(validateAndResolvePath); - componentSet = ComponentSet.fromSource({ + // first possible option, so we can just set it, next CSB options, must add to this componentSet + componentSetToReturn = ComponentSet.fromSource({ fsPaths, registry, }); @@ -108,38 +108,20 @@ export class ComponentSetBuilder { // Return empty ComponentSet and use packageNames in the connection via `.retrieve` options if (packagenames) { getLogger().debug(`Building ComponentSet for packagenames: ${packagenames.toString()}`); - componentSet ??= new ComponentSet(undefined, registry); } - // Resolve manifest with source in package directories. - if (manifest) { - getLogger().debug(`Building ComponentSet from manifest: ${manifest.manifestPath}`); - assertFileExists(manifest.manifestPath); - - getLogger().debug(`Searching in packageDir: ${manifest.directoryPaths.join(', ')} for matching metadata`); - componentSet = await ComponentSet.fromManifest({ - manifestPath: manifest.manifestPath, - resolveSourcePaths: manifest.directoryPaths, - forceAddWildcards: true, - destructivePre: manifest.destructiveChangesPre, - destructivePost: manifest.destructiveChangesPost, - registry, - }); - } - - // Resolve metadata entries with source in package directories, unless we are building a ComponentSet - // from metadata in an org. - if (metadata && !org) { + // Resolve metadata entries with source in package directories + if (metadata) { getLogger().debug(`Building ComponentSet from metadata: ${metadata.metadataEntries.toString()}`); const directoryPaths = metadata.directoryPaths; - componentSet ??= new ComponentSet(undefined, registry); const componentSetFilter = new ComponentSet(undefined, registry); // Build a Set of metadata entries metadata.metadataEntries + .filter(filterPsuedoTypes) .map(entryToTypeAndName(registry)) .flatMap(typeAndNameToMetadataComponents({ directoryPaths, registry })) - .map(addToComponentSet(componentSet)) + .map(addToComponentSet(componentSetToReturn)) .map(addToComponentSet(componentSetFilter)); getLogger().debug(`Searching for matching metadata in directories: ${directoryPaths.join(', ')}`); @@ -148,19 +130,21 @@ export class ComponentSetBuilder { // are resolved to SourceComponents if (metadata.destructiveEntriesPre) { metadata.destructiveEntriesPre + .filter(filterPsuedoTypes) .map(entryToTypeAndName(registry)) .map(assertNoWildcardInDestructiveEntries) .flatMap(typeAndNameToMetadataComponents({ directoryPaths, registry })) .map((mdComponent) => new SourceComponent({ type: mdComponent.type, name: mdComponent.fullName })) - .map(addToComponentSet(componentSet, DestructiveChangesType.PRE)); + .map(addToComponentSet(componentSetToReturn, DestructiveChangesType.PRE)); } if (metadata.destructiveEntriesPost) { metadata.destructiveEntriesPost + .filter(filterPsuedoTypes) .map(entryToTypeAndName(registry)) .map(assertNoWildcardInDestructiveEntries) .flatMap(typeAndNameToMetadataComponents({ directoryPaths, registry })) .map((mdComponent) => new SourceComponent({ type: mdComponent.type, name: mdComponent.fullName })) - .map(addToComponentSet(componentSet, DestructiveChangesType.POST)); + .map(addToComponentSet(componentSetToReturn, DestructiveChangesType.POST)); } const resolvedComponents = ComponentSet.fromSource({ @@ -169,39 +153,56 @@ export class ComponentSetBuilder { registry, }); - if (resolvedComponents.forceIgnoredPaths) { + if (resolvedComponents?.forceIgnoredPaths) { // if useFsForceIgnore = true, then we won't be able to resolve a forceignored path, // which we need to do to get the ignored source component const resolver = new MetadataResolver(registry, undefined, false); for (const ignoredPath of resolvedComponents.forceIgnoredPaths ?? []) { resolver.getComponentsFromPath(ignoredPath).map((ignored) => { - componentSet = componentSet?.filter( + componentSetToReturn = componentSetToReturn?.filter( (resolved) => !(resolved.fullName === ignored.name && resolved.type === ignored.type) ); }); } - componentSet.forceIgnoredPaths = resolvedComponents.forceIgnoredPaths; + componentSetToReturn.forceIgnoredPaths = resolvedComponents.forceIgnoredPaths; } - resolvedComponents.toArray().map(addToComponentSet(componentSet)); + resolvedComponents?.toArray().map(addToComponentSet(componentSetToReturn)); } // Resolve metadata entries with an org connection if (org) { - componentSet ??= new ComponentSet(undefined, registry); const orgComponentSet = await this.resolveOrgComponents(registry, options); - orgComponentSet.toArray().map(addToComponentSet(componentSet)); + orgComponentSet.toArray().map(addToComponentSet(componentSetToReturn)); + } + + // Resolve manifest with source in package directories. + // build from the manifest last, because if this value is passed with other ones, it will override the componentSetToReturn CS + if (manifest) { + getLogger().debug(`Building ComponentSet from manifest: ${manifest.manifestPath}`); + assertFileExists(manifest.manifestPath); + + getLogger().debug(`Searching in packageDir: ${manifest.directoryPaths.join(', ')} for matching metadata`); + // set here (using =) because generating a CS by using manifest + other options, is not supported + componentSetToReturn = await ComponentSet.fromManifest({ + manifestPath: manifest.manifestPath, + resolveSourcePaths: manifest.directoryPaths, + forceAddWildcards: true, + destructivePre: manifest.destructiveChangesPre, + destructivePost: manifest.destructiveChangesPost, + registry, + }); } // there should have been a componentSet created by this point. - componentSet = assertComponentSetIsNotUndefined(componentSet); - componentSet.apiVersion ??= options.apiversion; - componentSet.sourceApiVersion ??= options.sourceapiversion; - componentSet.projectDirectory = options.projectDir; + componentSetToReturn = assertComponentSetIsNotUndefined(componentSetToReturn); + componentSetToReturn.apiVersion ??= options.apiversion; + componentSetToReturn.sourceApiVersion ??= options.sourceapiversion; + componentSetToReturn.projectDirectory = options.projectDir; - logComponents(componentSet); - return componentSet; + logComponents(componentSetToReturn); + return componentSetToReturn; } private static async resolveOrgComponents( @@ -331,6 +332,13 @@ export const entryToTypeAndName = return { type, metadataName: name.length ? name.join(':').trim() : '*' }; }; +const filterPsuedoTypes = (value: string): boolean => { + const [typeName] = value.split(':'); + return !Object.values(PSEUDO_TYPES) + .map((p) => p.toLowerCase()) + .includes(typeName.toLowerCase()); +}; + const typeAndNameToMetadataComponents = (context: { directoryPaths: ManifestOption['directoryPaths']; registry: RegistryAccess }) => ({ type, metadataName }: MetadataTypeAndMetadataName): MetadataComponent[] => diff --git a/test/collections/componentSet.test.ts b/test/collections/componentSet.test.ts index a595d71814..13c16f52fc 100644 --- a/test/collections/componentSet.test.ts +++ b/test/collections/componentSet.test.ts @@ -34,7 +34,6 @@ import { import { decomposedtoplevel, digitalExperienceBundle, matchingContentFile, mixedContentSingleFile } from '../mock'; import { MATCHING_RULES_COMPONENT } from '../mock/type-constants/customlabelsConstant'; import * as manifestFiles from '../mock/manifestConstants'; -import { testApiVersion, testApiVersionAsString } from '../mock/manifestConstants'; import * as coverage from '../../src/registry/coverage'; const registryAccess = new RegistryAccess(); @@ -436,7 +435,7 @@ describe('ComponentSet', () => { ]; const resolveStub = $$.SANDBOX.stub(ManifestResolver.prototype, 'resolve').resolves({ components: expected, - apiVersion: testApiVersionAsString, + apiVersion: manifestFiles.testApiVersionAsString, }); $$.SANDBOX.stub(RegistryAccess.prototype, 'getTypeByName').returns(registry.types.apexclass); const manifest = manifestFiles.ONE_FOLDER_MEMBER; @@ -581,7 +580,7 @@ describe('ComponentSet', () => { ]; const resolveStub = $$.SANDBOX.stub(ConnectionResolver.prototype, 'resolve').resolves({ components: expected, - apiVersion: testApiVersionAsString, + apiVersion: manifestFiles.testApiVersionAsString, }); $$.SANDBOX.stub(RegistryAccess.prototype, 'getTypeByName').returns(registry.types.apexclass); const set = await ComponentSet.fromConnection({ usernameOrConnection: connection }); @@ -684,7 +683,9 @@ describe('ComponentSet', () => { let getCurrentApiVersionStub: SinonStub; beforeEach(() => { - getCurrentApiVersionStub = $$.SANDBOX.stub(coverage, 'getCurrentApiVersion').resolves(testApiVersion); + getCurrentApiVersionStub = $$.SANDBOX.stub(coverage, 'getCurrentApiVersion').resolves( + manifestFiles.testApiVersion + ); }); afterEach(() => { @@ -712,7 +713,7 @@ describe('ComponentSet', () => { members: ['b', 'c'], }, ], - version: testApiVersionAsString, + version: manifestFiles.testApiVersionAsString, }, }); expect(getCurrentApiVersionStub.calledOnce).to.be.true; @@ -725,7 +726,7 @@ describe('ComponentSet', () => { registry: registryAccess, tree: manifestFiles.TREE, }); - set.apiVersion = testApiVersionAsString; + set.apiVersion = manifestFiles.testApiVersionAsString; expect(await set.getObject()).to.deep.equal({ Package: { types: [ @@ -738,7 +739,7 @@ describe('ComponentSet', () => { members: ['b', 'c'], }, ], - version: testApiVersionAsString, + version: manifestFiles.testApiVersionAsString, }, }); expect(resolveSpy.called).to.be.false; @@ -778,7 +779,7 @@ describe('ComponentSet', () => { registry: registryAccess, tree: manifestFiles.TREE, }); - process.env.SF_ORG_API_VERSION = testApiVersionAsString; + process.env.SF_ORG_API_VERSION = manifestFiles.testApiVersionAsString; expect(await set.getObject()).to.deep.equal({ Package: { types: [ @@ -791,7 +792,7 @@ describe('ComponentSet', () => { members: ['b', 'c'], }, ], - version: testApiVersionAsString, + version: manifestFiles.testApiVersionAsString, }, }); expect(getCurrentApiVersionStub.called).to.be.false; @@ -843,7 +844,7 @@ describe('ComponentSet', () => { members: ['b', 'c'], }, ], - version: testApiVersionAsString, + version: manifestFiles.testApiVersionAsString, }, }); }); @@ -868,7 +869,7 @@ describe('ComponentSet', () => { members: ['b', 'c'], }, ], - version: testApiVersionAsString, + version: manifestFiles.testApiVersionAsString, }, }); }); @@ -1003,7 +1004,7 @@ describe('ComponentSet', () => { describe('getPackageXml', () => { beforeEach(() => { - $$.SANDBOX.stub(coverage, 'getCurrentApiVersion').resolves(testApiVersion); + $$.SANDBOX.stub(coverage, 'getCurrentApiVersion').resolves(manifestFiles.testApiVersion); }); it('should return manifest string when initialized from manifest file', async () => { const manifest = manifestFiles.ONE_OF_EACH; diff --git a/test/collections/componentSetBuilder.test.ts b/test/collections/componentSetBuilder.test.ts index 677fbb3bfd..01fc2ce3ad 100644 --- a/test/collections/componentSetBuilder.test.ts +++ b/test/collections/componentSetBuilder.test.ts @@ -13,9 +13,13 @@ import { assert, expect, config } from 'chai'; import { Connection, SfError } from '@salesforce/core'; import { instantiateContext, MockTestOrgData, restoreContext, stubContext } from '@salesforce/core/testSetup'; import { RegistryAccess } from '../../src/registry/registryAccess'; -import { ComponentSetBuilder, entryToTypeAndName } from '../../src/collections/componentSetBuilder'; +import { + ComponentSetBuilder, + ComponentSetOptions, + entryToTypeAndName, +} from '../../src/collections/componentSetBuilder'; import { ComponentSet } from '../../src/collections/componentSet'; -import { FromSourceOptions } from '../../src/collections/types'; +import type { FromSourceOptions } from '../../src/collections/types'; import { MetadataResolver, SourceComponent } from '../../src'; config.truncateThreshold = 0; @@ -491,6 +495,34 @@ describe('ComponentSetBuilder', () => { restoreContext($$); }); + it('should create a CS from every option possible', async () => { + fileExistsSyncStub.returns(true); + fromSourceStub.returns(new ComponentSet([customObjectComponent])); + fromManifestStub.resolves(new ComponentSet([apexClassWildcardMatch])); + fromConnectionStub.resolves(new ComponentSet([apexClassWildcardNoMatch])); + + const packageDir1 = path.resolve('force-app'); + const sourcepath = ['force-app', 'my-app']; + + const options: ComponentSetOptions = { + sourcepath, + org: { + username: testOrg.username, + exclude: [], + }, + metadata: { directoryPaths: [packageDir1], metadataEntries: ['ApexClass:MyClass'] }, + }; + + const compSet = await ComponentSetBuilder.build(options); + expect(fromSourceStub.callCount).to.equal(2); + expect(fromConnectionStub.callCount).to.equal(1); + + expect(compSet.size).to.equal(3); + expect(compSet.has(apexClassComponent)).to.equal(true); + expect(compSet.has(customObjectComponent)).to.equal(true); + expect(compSet.has(apexClassWildcardNoMatch)).to.equal(true); + }); + it('should create ComponentSet from org connection', async () => { componentSet.add(apexClassComponent); fromConnectionStub.resolves(componentSet); @@ -538,10 +570,10 @@ describe('ComponentSetBuilder', () => { }; const compSet = await ComponentSetBuilder.build(options); - expect(fromSourceStub.callCount).to.equal(0); + expect(fromSourceStub.callCount).to.equal(2); expect(fromConnectionStub.callCount).to.equal(1); - expect(compSet.size).to.equal(1); - expect(compSet.has(apexClassComponent)).to.equal(false); + expect(compSet.size).to.equal(2); + expect(compSet.has(apexClassComponent)).to.equal(true); expect(compSet.has(apexClassWildcardMatch)).to.equal(true); });