diff --git a/src/commands/project/retrieve/start.ts b/src/commands/project/retrieve/start.ts index fef75887..e40d8041 100644 --- a/src/commands/project/retrieve/start.ts +++ b/src/commands/project/retrieve/start.ts @@ -373,6 +373,19 @@ const buildRetrieveAndDeleteTargets = async ( } return result; } else { + // check if we're retrieving metadata based on a pattern ... + let retrieveFromOrg: string | undefined; + if (flags.metadata) { + flags.metadata.some((mdEntry) => { + const mdName = mdEntry.split(':')[1]; + if (mdName?.includes('*') && mdName?.length > 1 && !mdName?.includes('.*')) { + retrieveFromOrg = flags['target-org'].getUsername(); + return true; + } + return false; + }); + } + return { componentSetFromNonDeletes: await ComponentSetBuilder.build({ sourceapiversion: ( @@ -399,6 +412,7 @@ const buildRetrieveAndDeleteTargets = async ( }, } : {}), + org: retrieveFromOrg ? { username: retrieveFromOrg, exclude: [] } : undefined, }), }; } diff --git a/test/commands/retrieve/start.test.ts b/test/commands/retrieve/start.test.ts index f71211bf..dbfdfceb 100644 --- a/test/commands/retrieve/start.test.ts +++ b/test/commands/retrieve/start.test.ts @@ -169,6 +169,23 @@ describe('project retrieve start', () => { ensureRetrieveArgs({ format: 'source' }); }); + it('should pass along metadata and org for wildcard matching', async () => { + const metadata = ['ApexClass:MyC*']; + const result = await RetrieveMetadata.run(['--metadata', metadata[0], '--json']); + expect(result).to.deep.equal(expectedResults); + ensureCreateComponentSetArgs({ + metadata: { + metadataEntries: metadata, + directoryPaths: [expectedDirectoryPath], + }, + org: { + username: testOrg.username, + exclude: [], + }, + }); + ensureRetrieveArgs({ format: 'source' }); + }); + it('should pass along manifest', async () => { const manifest = 'package.xml'; const result = await RetrieveMetadata.run(['--manifest', manifest, '--json']); diff --git a/test/nuts/deploy/metadata.nut.ts b/test/nuts/deploy/metadata.nut.ts new file mode 100644 index 00000000..dfa818f8 --- /dev/null +++ b/test/nuts/deploy/metadata.nut.ts @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2023, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +import { fileURLToPath } from 'node:url'; +import { expect } from 'chai'; +import { SourceTestkit } from '@salesforce/source-testkit'; +import { DeployResultJson } from '../../../src/utils/types.js'; + +describe('deploy metadata NUTs', () => { + let testkit: SourceTestkit; + + before(async () => { + testkit = await SourceTestkit.create({ + repository: 'https://github.com/trailheadapps/dreamhouse-lwc.git', + nut: fileURLToPath(import.meta.url), + }); + await testkit.deploy({ args: '--source-dir force-app', exitCode: 0 }); + }); + + after(async () => { + await testkit?.clean(); + }); + + it('should deploy ApexClasses from wildcard match (single character)', async () => { + const response = await testkit.deploy({ args: '--metadata "ApexClass:P*"' }); + expect(response?.status).to.equal(0); + const result = response?.result as unknown as DeployResultJson; + expect(result.success).to.be.true; + expect(result.files.length).to.equal(4); + result.files.forEach((f) => { + expect(f.type).to.equal('ApexClass'); + expect(['PagedResult', 'PropertyController']).to.include(f.fullName); + }); + }); + + it('should deploy ApexClasses from wildcard match (2 characters)', async () => { + const response = await testkit.deploy({ args: '--metadata "ApexClass:Pa*"' }); + expect(response?.status).to.equal(0); + const result = response?.result as unknown as DeployResultJson; + expect(result.success).to.be.true; + expect(result.files.length).to.equal(2); + result.files.forEach((f) => { + expect(f.type).to.equal('ApexClass'); + expect(f.fullName).to.equal('PagedResult'); + }); + }); +}); diff --git a/test/nuts/retrieve/metadata.nut.ts b/test/nuts/retrieve/metadata.nut.ts index 8b2bad21..bad269f7 100644 --- a/test/nuts/retrieve/metadata.nut.ts +++ b/test/nuts/retrieve/metadata.nut.ts @@ -11,6 +11,7 @@ import { fileURLToPath } from 'node:url'; import { execCmd } from '@salesforce/cli-plugins-testkit'; import { SourceTestkit } from '@salesforce/source-testkit'; import { expect } from 'chai'; +import { RetrieveResultJson } from '../../../src/utils/types.js'; const ELECTRON = { id: '04t6A000002zgKSQAY', name: 'ElectronBranding' }; @@ -57,6 +58,48 @@ describe('retrieve metadata NUTs', () => { await testkit.retrieve({ args: '--metadata ApexClass AuraDefinitionBundle --output-dir myOutput' }); await testkit.expect.filesToBeRetrieved(['myOutput/classes/*', 'myOutput/aura/**/*']); }); + + it('should retrieve ApexClasses from wildcard match', async () => { + const response = await testkit.retrieve({ args: '--metadata "ApexClass:Test*"' }); + expect(response?.status).to.equal(0); + const result = response?.result as unknown as RetrieveResultJson; + expect(result.success).to.be.true; + expect(result.files.length).to.equal(4); + result.files.forEach((f) => { + expect(f.type).to.equal('ApexClass'); + expect(['TestSampleDataController', 'TestPropertyController']).to.include(f.fullName); + }); + await testkit.expect.filesToBeRetrieved(['force-app/main/default/classes/Test*']); + }); + + it('should retrieve ApexClasses from wildcard match without already existing in the project', async () => { + const forceAppDir = path.join(testkit.projectDir, 'force-app'); + const forceAppDirTmp = path.join(testkit.projectDir, 'force-app-tmp'); + + try { + fs.cpSync(forceAppDir, forceAppDirTmp, { recursive: true }); + fs.rmSync(forceAppDir, { recursive: true, force: true }); + expect(fs.existsSync(forceAppDir)).to.be.false; + const defaultDir = path.join(forceAppDir, 'main', 'default'); + fs.mkdirSync(defaultDir, { recursive: true }); + + const response = await testkit.retrieve({ args: '--metadata "ApexClass:Test*"' }); + expect(response?.status).to.equal(0); + const result = response?.result as unknown as RetrieveResultJson; + expect(result.success).to.be.true; + expect(result.files.length).to.equal(4); + result.files.forEach((f) => { + expect(f.type).to.equal('ApexClass'); + expect(['TestSampleDataController', 'TestPropertyController']).to.include(f.fullName); + }); + await testkit.expect.filesToBeRetrieved(['force-app/main/default/classes/Test*']); + } finally { + if (fs.existsSync(forceAppDirTmp)) { + fs.cpSync(forceAppDirTmp, forceAppDir, { recursive: true }); + fs.rmSync(forceAppDirTmp, { recursive: true, force: true }); + } + } + }); }); describe('--manifest flag', () => {