From 79f1612de1466f5c1bfd3549be0636d457639c36 Mon Sep 17 00:00:00 2001 From: Steve Hetzel Date: Tue, 11 Feb 2025 14:58:46 -0700 Subject: [PATCH 1/3] feat: support deleting with agent pseudo type --- src/commands/project/delete/source.ts | 6 +++++- src/commands/project/retrieve/start.ts | 4 +--- src/utils/flags.ts | 6 ++++++ 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/commands/project/delete/source.ts b/src/commands/project/delete/source.ts index ac411ae47..8ddd1b3ef 100644 --- a/src/commands/project/delete/source.ts +++ b/src/commands/project/delete/source.ts @@ -42,7 +42,7 @@ import { resolveApi, validateTests } from '../../../utils/deploy.js'; import { DeployResultFormatter } from '../../../formatters/deployResultFormatter.js'; import { DeleteResultFormatter } from '../../../formatters/deleteResultFormatter.js'; import { DeployCache } from '../../../utils/deployCache.js'; -import { testLevelFlag, testsFlag } from '../../../utils/flags.js'; +import { isPseudoType, testLevelFlag, testsFlag } from '../../../utils/flags.js'; const testFlags = 'Test'; Messages.importMessagesDirectoryFromMetaUrl(import.meta.url); @@ -165,6 +165,9 @@ export class Source extends SfCommand { protected async delete(): Promise { const sourcepaths = this.flags['source-dir']; + const retrieveFromOrg = this.flags.metadata?.some(isPseudoType) + ? this.flags['target-org'].getUsername() + : undefined; this.componentSet = await ComponentSetBuilder.build({ apiversion: this.flags['api-version'], @@ -177,6 +180,7 @@ export class Source extends SfCommand { } : undefined, projectDir: this.project?.getPath(), + ...(retrieveFromOrg ? { org: { username: retrieveFromOrg, exclude: [] } } : {}), }); if (this.flags['track-source'] && !this.flags['force-overwrite']) { await this.filterConflictsByComponentSet(); diff --git a/src/commands/project/retrieve/start.ts b/src/commands/project/retrieve/start.ts index 566e5f71b..4f928aeaf 100644 --- a/src/commands/project/retrieve/start.ts +++ b/src/commands/project/retrieve/start.ts @@ -28,7 +28,7 @@ import { SourceTracking, SourceConflictError } from '@salesforce/source-tracking import { Duration } from '@salesforce/kit'; import { Interfaces } from '@oclif/core'; -import { DEFAULT_ZIP_FILE_NAME, ensuredDirFlag, zipFileFlag } from '../../../utils/flags.js'; +import { DEFAULT_ZIP_FILE_NAME, ensuredDirFlag, isPseudoType, zipFileFlag } from '../../../utils/flags.js'; import { RetrieveResultFormatter } from '../../../formatters/retrieveResultFormatter.js'; import { MetadataRetrieveResultFormatter } from '../../../formatters/metadataRetrieveResultFormatter.js'; import { getOptionalProject, getPackageDirs } from '../../../utils/project.js'; @@ -507,5 +507,3 @@ const isRegexMatch = (mdEntry: string): boolean => { const mdName = mdEntry.split(':')[1]; return mdName?.includes('*') && mdName?.length > 1 && !mdName?.includes('.*'); }; - -const isPseudoType = (mdEntry: string): boolean => mdEntry.split(':')[0] === 'Agent'; diff --git a/src/utils/flags.ts b/src/utils/flags.ts index 668e04c1c..0bdd4df3f 100644 --- a/src/utils/flags.ts +++ b/src/utils/flags.ts @@ -121,3 +121,9 @@ const commaWarningForMultipleFlags = async (input: string, warningText: string): } return input; }; + +/** + * Returns `true` if the metadata entry (e.g., --metadata) contains a pseudo type + * such as "Agent" or "Agent:My_Agent". + */ +export const isPseudoType = (mdEntry: string): boolean => mdEntry.split(':')[0] === 'Agent'; From f5af252a7731bdb0e22ca640f673a95d94a689f0 Mon Sep 17 00:00:00 2001 From: Steve Hetzel Date: Wed, 12 Feb 2025 14:11:39 -0700 Subject: [PATCH 2/3] fix: resolve local metadata files with pseudo types --- src/commands/project/delete/source.ts | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/commands/project/delete/source.ts b/src/commands/project/delete/source.ts index 8ddd1b3ef..de70dad79 100644 --- a/src/commands/project/delete/source.ts +++ b/src/commands/project/delete/source.ts @@ -163,11 +163,10 @@ export class Source extends SfCommand { } } + // eslint-disable-next-line complexity protected async delete(): Promise { const sourcepaths = this.flags['source-dir']; - const retrieveFromOrg = this.flags.metadata?.some(isPseudoType) - ? this.flags['target-org'].getUsername() - : undefined; + const resolveFromOrg = this.flags.metadata?.some(isPseudoType) ? this.flags['target-org'].getUsername() : undefined; this.componentSet = await ComponentSetBuilder.build({ apiversion: this.flags['api-version'], @@ -180,8 +179,18 @@ export class Source extends SfCommand { } : undefined, projectDir: this.project?.getPath(), - ...(retrieveFromOrg ? { org: { username: retrieveFromOrg, exclude: [] } } : {}), + ...(resolveFromOrg ? { org: { username: resolveFromOrg, exclude: [] } } : {}), }); + + // If we built a component set from an org connection, we have to resolve + // components from the project. + if (resolveFromOrg) { + this.componentSet = ComponentSet.fromSource({ + fsPaths: await getPackageDirs(), + include: this.componentSet, + }); + } + if (this.flags['track-source'] && !this.flags['force-overwrite']) { await this.filterConflictsByComponentSet(); } From 19ee2f00caa9304779ac3345517a097e1c981075 Mon Sep 17 00:00:00 2001 From: Steve Hetzel Date: Wed, 12 Feb 2025 17:40:08 -0700 Subject: [PATCH 3/3] fix: add unit test --- test/commands/delete/source.test.ts | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/test/commands/delete/source.test.ts b/test/commands/delete/source.test.ts index 60a941643..8eb9667a2 100644 --- a/test/commands/delete/source.test.ts +++ b/test/commands/delete/source.test.ts @@ -117,6 +117,7 @@ describe('project delete source', () => { let lifecycleEmitStub: sinon.SinonStub; let resolveProjectConfigStub: sinon.SinonStub; let rmStub: sinon.SinonStub; + let compSetFromSourceStub: sinon.SinonStub; class TestDelete extends Source { public async runIt() { @@ -167,6 +168,10 @@ describe('project delete source', () => { }); const lifecycle = Lifecycle.getInstance(); lifecycleEmitStub = $$.SANDBOX.stub(lifecycle, 'emit'); + + compSetFromSourceStub = stubMethod($$.SANDBOX, ComponentSet, 'fromSource').returns({ + toArray: () => [new SourceComponent(exampleSourceComponent)], + }); }); afterEach(() => { @@ -228,6 +233,23 @@ describe('project delete source', () => { ensureHookArgs(); }); + it('should pass along metadata and org for pseudo-type matching', async () => { + const metadata = ['Agent:My_Agent']; + await runDeleteCmd(['--metadata', metadata[0], '--json']); + ensureCreateComponentSetArgs({ + metadata: { + metadataEntries: metadata, + directoryPaths: [defaultPackagePath], + }, + org: { + username: testOrg.username, + exclude: [], + }, + }); + ensureHookArgs(); + expect(compSetFromSourceStub.calledOnce).to.be.true; + }); + it('should pass along apiversion', async () => { const metadata = ['ApexClass:MyClass'];