Skip to content

fix: change CSB to build from all sources combined #1498

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 48 additions & 40 deletions src/collections/componentSetBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -91,15 +91,15 @@ export class ComponentSetBuilder {
*/

public static async build(options: ComponentSetOptions): Promise<ComponentSet> {
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,
});
Expand All @@ -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(', ')}`);
Expand All @@ -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({
Expand All @@ -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(
Expand Down Expand Up @@ -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[] =>
Expand Down
25 changes: 13 additions & 12 deletions test/collections/componentSet.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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 });
Expand Down Expand Up @@ -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(() => {
Expand Down Expand Up @@ -712,7 +713,7 @@ describe('ComponentSet', () => {
members: ['b', 'c'],
},
],
version: testApiVersionAsString,
version: manifestFiles.testApiVersionAsString,
},
});
expect(getCurrentApiVersionStub.calledOnce).to.be.true;
Expand All @@ -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: [
Expand All @@ -738,7 +739,7 @@ describe('ComponentSet', () => {
members: ['b', 'c'],
},
],
version: testApiVersionAsString,
version: manifestFiles.testApiVersionAsString,
},
});
expect(resolveSpy.called).to.be.false;
Expand Down Expand Up @@ -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: [
Expand All @@ -791,7 +792,7 @@ describe('ComponentSet', () => {
members: ['b', 'c'],
},
],
version: testApiVersionAsString,
version: manifestFiles.testApiVersionAsString,
},
});
expect(getCurrentApiVersionStub.called).to.be.false;
Expand Down Expand Up @@ -843,7 +844,7 @@ describe('ComponentSet', () => {
members: ['b', 'c'],
},
],
version: testApiVersionAsString,
version: manifestFiles.testApiVersionAsString,
},
});
});
Expand All @@ -868,7 +869,7 @@ describe('ComponentSet', () => {
members: ['b', 'c'],
},
],
version: testApiVersionAsString,
version: manifestFiles.testApiVersionAsString,
},
});
});
Expand Down Expand Up @@ -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;
Expand Down
42 changes: 37 additions & 5 deletions test/collections/componentSetBuilder.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
});

Expand Down
Loading