diff --git a/package.json b/package.json index b8e7fe30a..ca85ac257 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "@oclif/config": "^1", "@salesforce/command": "^3.1.0", "@salesforce/core": "^2.20.8", - "@salesforce/source-deploy-retrieve": "1.1.21", + "@salesforce/source-deploy-retrieve": "^2", "chalk": "^4.1.0", "cli-ux": "^5.5.1", "tslib": "^2" @@ -120,7 +120,7 @@ "test": "sf-test", "test:command-reference": "./bin/run commandreference:generate --erroronwarnings", "test:deprecation-policy": "./bin/run snapshot:compare", - "test:nuts": "ts-node ./test/nuts/generateNuts.ts && nyc mocha \"**/*.nut.ts\" --slow 3000 --timeout 600000 --parallel --retries 1", + "test:nuts": "ts-node ./test/nuts/generateNuts.ts && nyc mocha \"**/*.nut.ts\" --slow 3000 --timeout 600000 --parallel --retries 0", "version": "oclif-dev readme" }, "husky": { diff --git a/src/commands/force/source/retrieve.ts b/src/commands/force/source/retrieve.ts index dd9551b37..dcf543db7 100644 --- a/src/commands/force/source/retrieve.ts +++ b/src/commands/force/source/retrieve.ts @@ -11,8 +11,8 @@ import { Lifecycle, Messages, SfdxError } from '@salesforce/core'; import { Duration } from '@salesforce/kit'; import { asArray, asString } from '@salesforce/ts-types'; import { blue } from 'chalk'; -import { MetadataApiRetrieveStatus, RetrieveResult } from '@salesforce/source-deploy-retrieve'; -import { FileProperties } from '@salesforce/source-deploy-retrieve/lib/src/client/types'; +import { MetadataApiRetrieveStatus } from '@salesforce/source-deploy-retrieve'; +import { FileProperties, FileResponse } from '@salesforce/source-deploy-retrieve/lib/src/client/types'; import { SourceCommand } from '../../../sourceCommand'; Messages.importMessagesDirectory(__dirname); @@ -52,8 +52,7 @@ export class Retrieve extends SourceCommand { }; protected readonly lifecycleEventNames = ['preretrieve', 'postretrieve']; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - public async run(): Promise { + public async run(): Promise { const hookEmitter = Lifecycle.getInstance(); const defaultPackagePath = this.project.getDefaultPackage().fullPath; @@ -85,13 +84,11 @@ export class Retrieve extends SourceCommand { throw new SfdxError(messages.getMessage('retrieveTimeout', [(this.flags.wait as Duration).minutes])); } - if (this.flags.json) { - this.ux.logJson(mdapiResult.getFileResponses()); - } else { + if (!this.flags.json) { this.printTable(results, true); } - return mdapiResult; + return mdapiResult.getFileResponses(); } /** diff --git a/src/sourceCommand.ts b/src/sourceCommand.ts index 9a97fba4b..51e69cab5 100644 --- a/src/sourceCommand.ts +++ b/src/sourceCommand.ts @@ -8,7 +8,7 @@ import * as path from 'path'; import { SfdxCommand } from '@salesforce/command'; import { ComponentSet } from '@salesforce/source-deploy-retrieve'; import { fs, SfdxError, Logger, ConfigFile } from '@salesforce/core'; -import { ComponentLike } from '@salesforce/source-deploy-retrieve/lib/src/common'; +import { ComponentLike } from '@salesforce/source-deploy-retrieve/lib/src/resolve'; export type FlagOptions = { packagenames?: string[]; @@ -59,11 +59,12 @@ export abstract class SourceCommand extends SfdxCommand { if (options.manifest) { logger.debug(`Building ComponentSet from manifest: ${options.manifest}`); const packageDirs = this.project.getUniquePackageDirectories().map((pDir) => pDir.fullPath); - for (const packageDir of packageDirs) { - logger.debug(`Searching in packageDir: ${packageDir} for matching metadata`); - const compSet = await ComponentSet.fromManifestFile(options.manifest, { resolve: packageDir }); - setAggregator.push(...compSet); - } + logger.debug(`Searching in packageDir: ${packageDirs.join(', ')} for matching metadata`); + const compSet = await ComponentSet.fromManifest({ + manifestPath: options.manifest, + resolveSourcePaths: packageDirs, + }); + setAggregator.push(...compSet); } // Resolve metadata entries with source in package directories. @@ -82,8 +83,12 @@ export abstract class SourceCommand extends SfdxCommand { // Search the packages directories for matching metadata const packageDirs = this.project.getUniquePackageDirectories().map((pDir) => pDir.fullPath); - logger.debug(`Searching for matching metadata in packageDirs: ${packageDirs.toString()}`); - setAggregator.push(...ComponentSet.fromSource({ inclusiveFilter: filter, fsPaths: packageDirs })); + logger.debug(`Searching for matching metadata in packageDirs: ${packageDirs.join(', ')}`); + + const fromSource = ComponentSet.fromSource({ fsPaths: packageDirs, include: filter }); + // If no matching metadata is found, default to the original component set + const finalized = fromSource.size > 0 ? fromSource : filter; + setAggregator.push(...finalized); } const componentSet = new ComponentSet(setAggregator); diff --git a/test/commands/source/retrieve.test.ts b/test/commands/source/retrieve.test.ts index 59a387ce7..9e9d6efc5 100644 --- a/test/commands/source/retrieve.test.ts +++ b/test/commands/source/retrieve.test.ts @@ -113,7 +113,7 @@ describe('force:source:retrieve', () => { it('should pass along sourcepath', async () => { const sourcepath = ['somepath']; const result = await run({ sourcepath, json: true }); - expect(result).to.deep.equal(stubbedResults); + expect(result).to.deep.equal(stubbedResults.getFileResponses()); ensureCreateComponentSetArgs({ sourcepath }); ensureRetrieveArgs(); ensureHookArgs(); @@ -122,7 +122,7 @@ describe('force:source:retrieve', () => { it('should pass along metadata', async () => { const metadata = ['ApexClass:MyClass']; const result = await run({ metadata, json: true }); - expect(result).to.deep.equal(stubbedResults); + expect(result).to.deep.equal(stubbedResults.getFileResponses()); ensureCreateComponentSetArgs({ metadata }); ensureRetrieveArgs(); ensureHookArgs(); @@ -131,7 +131,7 @@ describe('force:source:retrieve', () => { it('should pass along manifest', async () => { const manifest = 'package.xml'; const result = await run({ manifest, json: true }); - expect(result).to.deep.equal(stubbedResults); + expect(result).to.deep.equal(stubbedResults.getFileResponses()); ensureCreateComponentSetArgs({ manifest }); ensureRetrieveArgs(); ensureHookArgs(); @@ -141,7 +141,7 @@ describe('force:source:retrieve', () => { const manifest = 'package.xml'; const apiversion = '50.0'; const result = await run({ manifest, apiversion, json: true }); - expect(result).to.deep.equal(stubbedResults); + expect(result).to.deep.equal(stubbedResults.getFileResponses()); ensureCreateComponentSetArgs({ apiversion, manifest }); ensureRetrieveArgs(); ensureHookArgs(); @@ -151,7 +151,7 @@ describe('force:source:retrieve', () => { const manifest = 'package.xml'; const packagenames = ['package1']; const result = await run({ manifest, packagenames, json: true }); - expect(result).to.deep.equal(stubbedResults); + expect(result).to.deep.equal(stubbedResults.getFileResponses()); ensureCreateComponentSetArgs({ manifest, packagenames }); ensureRetrieveArgs({ packageNames: packagenames }); ensureHookArgs(); diff --git a/test/commands/source/sourceCommand.test.ts b/test/commands/source/sourceCommand.test.ts index b80804f6c..651485aed 100644 --- a/test/commands/source/sourceCommand.test.ts +++ b/test/commands/source/sourceCommand.test.ts @@ -55,7 +55,7 @@ describe('SourceCommand', () => { beforeEach(() => { fileExistsSyncStub = stubMethod(sandbox, fsCore, 'fileExistsSync'); fromSourceStub = stubMethod(sandbox, ComponentSet, 'fromSource'); - fromManifestStub = stubMethod(sandbox, ComponentSet, 'fromManifestFile'); + fromManifestStub = stubMethod(sandbox, ComponentSet, 'fromManifest'); getUniquePackageDirectoriesStub = stubMethod(sandbox, SfdxProject.prototype, 'getUniquePackageDirectories'); componentSet = new ComponentSet(); }); @@ -173,7 +173,7 @@ describe('SourceCommand', () => { expect(fromSourceArgs).to.have.deep.property('fsPaths', [packageDir1]); const filter = new ComponentSet(); filter.add({ type: 'ApexClass', fullName: '*' }); - expect(fromSourceArgs).to.have.deep.property('inclusiveFilter', filter); + expect(fromSourceArgs).to.have.deep.property('include', filter); expect(compSet.size).to.equal(1); expect(compSet.has(apexClassComponent)).to.equal(true); }); @@ -195,7 +195,7 @@ describe('SourceCommand', () => { expect(fromSourceArgs).to.have.deep.property('fsPaths', [packageDir1]); const filter = new ComponentSet(); filter.add({ type: 'ApexClass', fullName: 'MyClass' }); - expect(fromSourceArgs).to.have.deep.property('inclusiveFilter', filter); + expect(fromSourceArgs).to.have.deep.property('include', filter); expect(compSet.size).to.equal(1); expect(compSet.has(apexClassComponent)).to.equal(true); }); @@ -219,7 +219,7 @@ describe('SourceCommand', () => { const filter = new ComponentSet(); filter.add({ type: 'ApexClass', fullName: 'MyClass' }); filter.add({ type: 'CustomObject', fullName: '*' }); - expect(fromSourceArgs).to.have.deep.property('inclusiveFilter', filter); + expect(fromSourceArgs).to.have.deep.property('include', filter); expect(compSet.size).to.equal(2); expect(compSet.has(apexClassComponent)).to.equal(true); expect(compSet.has(customObjectComponent)).to.equal(true); @@ -245,7 +245,7 @@ describe('SourceCommand', () => { expect(fromSourceArgs).to.have.deep.property('fsPaths', [packageDir1, packageDir2]); const filter = new ComponentSet(); filter.add({ type: 'ApexClass', fullName: '*' }); - expect(fromSourceArgs).to.have.deep.property('inclusiveFilter', filter); + expect(fromSourceArgs).to.have.deep.property('include', filter); expect(compSet.size).to.equal(2); expect(compSet.has(apexClassComponent)).to.equal(true); expect(compSet.has(apexClassComponent2)).to.equal(true); @@ -264,19 +264,19 @@ describe('SourceCommand', () => { const compSet = await command.callCreateComponentSet(options); expect(fromManifestStub.calledOnce).to.equal(true); - expect(fromManifestStub.firstCall.args[0]).to.equal(options.manifest); - expect(fromManifestStub.firstCall.args[1]).to.deep.equal({ resolve: packageDir1 }); + expect(fromManifestStub.firstCall.args[0]).to.deep.equal({ + manifestPath: options.manifest, + resolveSourcePaths: [packageDir1], + }); expect(compSet.size).to.equal(1); expect(compSet.has(apexClassComponent)).to.equal(true); }); it('should create ComponentSet from manifest and multiple package', async () => { componentSet.add(apexClassComponent); - const componentSet2 = new ComponentSet(); const apexClassComponent2 = { type: 'ApexClass', fullName: 'MyClass2' }; - componentSet2.add(apexClassComponent2); + componentSet.add(apexClassComponent2); fromManifestStub.onFirstCall().resolves(componentSet); - fromManifestStub.onSecondCall().resolves(componentSet2); const packageDir1 = path.resolve('force-app'); const packageDir2 = path.resolve('my-app'); getUniquePackageDirectoriesStub.returns([{ fullPath: packageDir1 }, { fullPath: packageDir2 }]); @@ -287,11 +287,11 @@ describe('SourceCommand', () => { }; const compSet = await command.callCreateComponentSet(options); - expect(fromManifestStub.callCount).to.equal(2); - expect(fromManifestStub.firstCall.args[0]).to.equal(options.manifest); - expect(fromManifestStub.firstCall.args[1]).to.deep.equal({ resolve: packageDir1 }); - expect(fromManifestStub.secondCall.args[0]).to.equal(options.manifest); - expect(fromManifestStub.secondCall.args[1]).to.deep.equal({ resolve: packageDir2 }); + expect(fromManifestStub.callCount).to.equal(1); + expect(fromManifestStub.firstCall.args[0]).to.deep.equal({ + manifestPath: options.manifest, + resolveSourcePaths: [packageDir1, packageDir2], + }); expect(compSet.size).to.equal(2); expect(compSet.has(apexClassComponent)).to.equal(true); expect(compSet.has(apexClassComponent2)).to.equal(true); diff --git a/test/nuts/assertions.ts b/test/nuts/assertions.ts index a0692ef55..fe767cf2e 100644 --- a/test/nuts/assertions.ts +++ b/test/nuts/assertions.ts @@ -48,6 +48,14 @@ export class Assertions { expect(fileHistory.changedFromPrevious, 'File to be changed').to.be.true; } + /** + * Expect given file to NOT be changed according to the file history provided by FileTracker + */ + public fileToNotBeChanged(file: string): void { + const fileHistory = this.fileTracker.getLatest(file); + expect(fileHistory.changedFromPrevious, 'File to NOT be changed').to.be.false; + } + /** * Expect all files found by globs to be changed according to the file history provided by FileTracker */ @@ -61,6 +69,19 @@ export class Assertions { expect(allChanged, 'all files to be changed').to.be.true; } + /** + * Expect all files found by globs to NOT be changed according to the file history provided by FileTracker + */ + public async filesToNotBeChanged(globs: string[]): Promise { + const files = await this.doGlob(globs); + const fileHistories = files + .filter((f) => !f.endsWith('.resource-meta.xml')) + .map((f) => this.fileTracker.getLatest(f)) + .filter((f) => !!f); + const allChanged = fileHistories.every((f) => f.changedFromPrevious); + expect(allChanged, 'all files to NOT be changed').to.be.false; + } + /** * Finds all files in project based on the provided globs and expects them to be updated on the server */ @@ -122,7 +143,7 @@ export class Assertions { } /** - * Expects given globs to return files + * Expects files found by glob to not contain any of the provided strings */ public async filesToNotContainString(glob: string, ...strings: string[]): Promise { const files = await this.doGlob([glob]); @@ -134,6 +155,19 @@ export class Assertions { } } + /** + * Expects files found by glob to contain the provided strings + */ + public async filesToContainString(glob: string, ...strings: string[]): Promise { + const files = await this.doGlob([glob]); + for (const file of files) { + const contents = await fs.readFile(file, 'UTF-8'); + for (const str of strings) { + expect(contents, `expect ${file} to not include ${str}`).to.include(str); + } + } + } + /** * Expect the retrieved package to exist and contain some files */ diff --git a/test/nuts/executionLog.ts b/test/nuts/executionLog.ts index f63062711..616964bea 100644 --- a/test/nuts/executionLog.ts +++ b/test/nuts/executionLog.ts @@ -47,7 +47,8 @@ export class ExecutionLog { * Return the most recent entry for a command */ public getLatest(cmd: string): ExecutionLog.Details { - return this.log.get(cmd).reverse()[0]; + const sorted = this.log.get(cmd).sort((a, b) => (a.timestamp < b.timestamp ? 1 : -1)); + return sorted[0]; } private async querySourceMembers(): Promise { diff --git a/test/nuts/nutshell.ts b/test/nuts/nutshell.ts index dd1412bde..d69aefda9 100644 --- a/test/nuts/nutshell.ts +++ b/test/nuts/nutshell.ts @@ -14,7 +14,7 @@ import * as fg from 'fast-glob'; import { exec } from 'shelljs'; import { TestSession, execCmd } from '@salesforce/cli-plugins-testkit'; import { Env } from '@salesforce/kit'; -import { AnyJson, ensureString, JsonMap, Nullable } from '@salesforce/ts-types'; +import { AnyJson, Dictionary, ensureString, JsonMap, Nullable } from '@salesforce/ts-types'; import { AuthInfo, Connection, fs, NamedPackageDir, SfdxProject } from '@salesforce/core'; import { AsyncCreatable } from '@salesforce/kit'; import { debug, Debugger } from 'debug'; @@ -176,6 +176,18 @@ export class Nutshell extends AsyncCreatable { } } + /** + * Read files found by globs + */ + public async readGlobs(globs: string[]): Promise> { + const files = await this.doGlob(globs); + const returnValue = {}; + for (const file of files) { + returnValue[file] = await fs.readFile(file, 'UTF-8'); + } + return returnValue; + } + /** * Read the org's sourcePathInfos.json */ @@ -206,6 +218,28 @@ export class Nutshell extends AsyncCreatable { return fs.writeJson(maxRevisionPath, contents); } + /** + * Write file + */ + public async writeFile(filename: string, contents: string): Promise { + return fs.writeFile(filename, contents); + } + + /** + * Create a package.xml + */ + public async createPackageXml(xml: string): Promise { + const packageXml = ` + + ${xml} + 51.0 + + `; + const packageXmlPath = path.join(this.session.project.dir, 'package.xml'); + await fs.writeFile(packageXmlPath, packageXml); + return packageXmlPath; + } + /** * Delete the org's sourcePathInfos.json */ @@ -228,6 +262,16 @@ export class Nutshell extends AsyncCreatable { return fs.unlink(maxRevisionPath); } + /** + * Delete the files found by the given globs + */ + public async deleteGlobs(globs: string[]): Promise { + const files = await this.doGlob(globs); + for (const file of files) { + await fs.unlink(file); + } + } + /** * Delete all source files in the project directory */ @@ -405,6 +449,7 @@ export class Nutshell extends AsyncCreatable { : [ // TODO: remove this config:set call 'sfdx config:set apiVersion=50.0 --global', + 'sfdx config:set restDeploy=false --global', 'sfdx force:org:create -d 1 -s -f config/project-scratch-def.json', ]; return await TestSession.create({ diff --git a/test/nuts/seeds/mpd.deploy.seed.ts b/test/nuts/seeds/mpd.deploy.seed.ts new file mode 100644 index 000000000..67886f0dd --- /dev/null +++ b/test/nuts/seeds/mpd.deploy.seed.ts @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2020, 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 * as path from 'path'; +import { Nutshell } from '../nutshell'; + +// DO NOT TOUCH. generateNuts.ts will insert these values +const EXECUTABLE = '%EXECUTABLE%'; +context('MPD Deploy NUTs [exec: %EXECUTABLE%]', () => { + let nutshell: Nutshell; + + before(async () => { + nutshell = await Nutshell.create({ + repository: 'https://github.com/salesforcecli/sample-project-multiple-packages.git', + executable: EXECUTABLE, + nut: __filename, + }); + }); + + after(async () => { + await nutshell?.clean(); + }); + + describe('CustomLabels', () => { + // NOTE these are glob patterns so there's no need to use path.join here + const forceAppLabels = 'force-app/main/default/labels/CustomLabels.labels-meta.xml'; + const myAppLabels = 'my-app/labels/CustomLabels.labels-meta.xml'; + + describe('--sourcepath', () => { + it('should deploy all CustomLabels from a single package', async () => { + await nutshell.deploy({ args: `--sourcepath ${path.join('force-app', 'main', 'default', 'labels')}` }); + await nutshell.expect.filesToBeDeployed([forceAppLabels]); + }); + + it('should deploy all CustomLabels from multiple packages', async () => { + await nutshell.deploy({ + args: `--sourcepath ${path.join('force-app', 'main', 'default', 'labels')},${path.join('my-app', 'labels')}`, + }); + await nutshell.expect.filesToBeDeployed([forceAppLabels, myAppLabels]); + }); + }); + + describe('--metadata', () => { + it('should deploy all CustomLabels', async () => { + await nutshell.deploy({ args: '--metadata CustomLabels' }); + await nutshell.expect.filesToBeDeployed([forceAppLabels]); + }); + + it('should deploy individual CustomLabel', async () => { + await nutshell.deploy({ args: '--metadata CustomLabel:force_app_Label_1' }); + await nutshell.expect.filesToBeDeployed([forceAppLabels]); + }); + + it('should deploy multiple individual CustomLabel', async () => { + await nutshell.deploy({ args: '--metadata CustomLabel:force_app_Label_1,CustomLabel:my_app_Label_1' }); + await nutshell.expect.filesToBeDeployed([forceAppLabels, myAppLabels]); + }); + }); + }); +}); diff --git a/test/nuts/seeds/mpd.retrieve.seed.ts b/test/nuts/seeds/mpd.retrieve.seed.ts index c04aabf40..de5bb3923 100644 --- a/test/nuts/seeds/mpd.retrieve.seed.ts +++ b/test/nuts/seeds/mpd.retrieve.seed.ts @@ -5,6 +5,7 @@ * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ +import { Dictionary } from '@salesforce/ts-types'; import { Nutshell } from '../nutshell'; // DO NOT TOUCH. generateNuts.ts will insert these values @@ -40,21 +41,203 @@ context('MPD Retrieve NUTs [exec: %EXECUTABLE%]', () => { }); }); - // Skipping because sfdx force:source:retrieve does not support multiple CustomLabels files - describe.skip('CustomLabels', () => { - it('should put labels into appropriate CustomLabels file', async () => { - const forceAppLabels = 'force-app/main/default/labels/CustomLabels.labels-meta.xml'; - const myAppLabels = 'my-app/labels/CustomLabels.labels-meta.xml'; - await nutshell.modifyLocalGlobs([forceAppLabels, myAppLabels]); - await nutshell.retrieve({ args: '--metadata CustomLabels' }); - - await nutshell.expect.filesToBeChanged([forceAppLabels, myAppLabels]); - await nutshell.expect.filesToNotContainString(forceAppLabels, 'my_app_Label_1'); - await nutshell.expect.filesToNotContainString( - myAppLabels, - 'force_app_Label_1', - 'force_app_Label_2' - ); + // Skipping if using sfdx executable because it does not support multiple CustomLabels files on retrieve + (EXECUTABLE.includes('plugin-source') ? describe : describe.skip)('CustomLabels', () => { + // NOTE these are glob patterns so there's no need to use path.join here + const forceAppLabels = 'force-app/main/default/labels/CustomLabels.labels-meta.xml'; + const myAppLabels = 'my-app/labels/CustomLabels.labels-meta.xml'; + + let originalState: Dictionary; + + before(async () => { + originalState = await nutshell.readGlobs([forceAppLabels, myAppLabels]); + }); + + beforeEach(async () => { + for (const [filename, contents] of Object.entries(originalState)) { + await nutshell.writeFile(filename, contents); + } + }); + + describe('--metadata CustomLabels', () => { + it('should put labels into appropriate CustomLabels file', async () => { + await nutshell.modifyLocalGlobs([forceAppLabels, myAppLabels]); + await nutshell.retrieve({ args: '--metadata CustomLabels' }); + + await nutshell.expect.filesToBeChanged([forceAppLabels, myAppLabels]); + await nutshell.expect.filesToNotContainString(forceAppLabels, 'my_app_Label_1'); + await nutshell.expect.filesToNotContainString( + myAppLabels, + 'force_app_Label_1', + 'force_app_Label_2' + ); + }); + + it('should put new labels into CustomLabels file in default package', async () => { + // Delete local labels to simulate having new labels created in the org + await nutshell.deleteGlobs([myAppLabels]); + await nutshell.retrieve({ args: '--metadata CustomLabels' }); + + await nutshell.expect.filesToBeChanged([forceAppLabels]); + await nutshell.expect.filesToContainString( + forceAppLabels, + 'my_app_Label_1', + 'force_app_Label_1', + 'force_app_Label_2' + ); + }); + + it('should put new labels into existing CustomLabels file', async () => { + // Delete local labels to simulate having new labels created in the org + await nutshell.deleteGlobs([forceAppLabels]); + await nutshell.retrieve({ args: '--metadata CustomLabels' }); + + await nutshell.expect.filesToBeChanged([myAppLabels]); + await nutshell.expect.filesToContainString( + myAppLabels, + 'my_app_Label_1', + 'force_app_Label_1', + 'force_app_Label_2' + ); + }); + + it('should put all labels into default CustomLabels file when no labels exist locally', async () => { + // Delete local labels to simulate having new labels created in the org + await nutshell.deleteGlobs([forceAppLabels, myAppLabels]); + await nutshell.retrieve({ args: '--metadata CustomLabels' }); + + await nutshell.expect.filesToBeChanged([forceAppLabels]); + await nutshell.expect.filesToContainString( + forceAppLabels, + 'my_app_Label_1', + 'force_app_Label_1', + 'force_app_Label_2' + ); + }); + }); + + describe('--metadata CustomLabel:force_app_Label_1', () => { + it('should put individual label into appropriate CustomLabels file', async () => { + await nutshell.retrieve({ args: '--metadata CustomLabel:force_app_Label_1' }); + + await nutshell.expect.filesToBeChanged([forceAppLabels]); + await nutshell.expect.filesToNotBeChanged([myAppLabels]); + await nutshell.expect.filesToNotContainString( + forceAppLabels, + 'my_app_Label_1', + 'force_app_Label_2' + ); + await nutshell.expect.filesToNotContainString( + myAppLabels, + 'force_app_Label_1', + 'force_app_Label_2' + ); + }); + + it('should put individual label into default CustomLabels file when no labels exist locally', async () => { + // Delete local labels to simulate having new labels created in the org + await nutshell.deleteGlobs([forceAppLabels, myAppLabels]); + await nutshell.retrieve({ args: '--metadata CustomLabel:force_app_Label_1' }); + + await nutshell.expect.filesToBeChanged([forceAppLabels]); + await nutshell.expect.filesToContainString(forceAppLabels, 'force_app_Label_1'); + await nutshell.expect.filesToNotContainString( + forceAppLabels, + 'my_app_Label_1', + 'force_app_Label_2' + ); + }); + }); + + describe('--metadata CustomLabel:force_app_Label_1,CustomLabel:my_app_Label_1', () => { + it('should put labels into appropriate CustomLabels file', async () => { + await nutshell.modifyLocalGlobs([forceAppLabels, myAppLabels]); + await nutshell.retrieve({ args: '--metadata CustomLabel:force_app_Label_1,CustomLabel:my_app_Label_1' }); + + await nutshell.expect.filesToBeChanged([forceAppLabels, myAppLabels]); + await nutshell.expect.filesToNotContainString(forceAppLabels, 'my_app_Label_1'); + await nutshell.expect.filesToNotContainString( + myAppLabels, + 'force_app_Label_1', + 'force_app_Label_2' + ); + }); + }); + + describe('--sourcepath force-app', () => { + it('should put labels into appropriate CustomLabels file', async () => { + await nutshell.modifyLocalGlobs([forceAppLabels]); + await nutshell.retrieve({ args: '--sourcepath force-app' }); + + await nutshell.expect.filesToBeChanged([forceAppLabels]); + await nutshell.expect.filesToNotBeChanged([myAppLabels]); + + await nutshell.expect.filesToNotContainString(forceAppLabels, 'my_app_Label_1'); + await nutshell.expect.filesToNotContainString( + myAppLabels, + 'force_app_Label_1', + 'force_app_Label_2' + ); + }); + }); + + describe('--sourcepath my-app', () => { + it('should put labels into appropriate CustomLabels file', async () => { + await nutshell.modifyLocalGlobs([myAppLabels]); + await nutshell.retrieve({ args: '--sourcepath my-app' }); + + await nutshell.expect.filesToBeChanged([myAppLabels]); + await nutshell.expect.filesToNotBeChanged([forceAppLabels]); + + await nutshell.expect.filesToNotContainString(forceAppLabels, 'my_app_Label_1'); + await nutshell.expect.filesToNotContainString( + myAppLabels, + 'force_app_Label_1', + 'force_app_Label_2' + ); + }); + }); + + describe('--sourcepath force-app,my-app', () => { + it('should put labels into appropriate CustomLabels file', async () => { + await nutshell.modifyLocalGlobs([forceAppLabels, myAppLabels]); + await nutshell.retrieve({ args: '--sourcepath force-app,my-app' }); + + await nutshell.expect.filesToBeChanged([forceAppLabels, myAppLabels]); + await nutshell.expect.filesToNotContainString(forceAppLabels, 'my_app_Label_1'); + await nutshell.expect.filesToNotContainString( + myAppLabels, + 'force_app_Label_1', + 'force_app_Label_2' + ); + }); + }); + + describe('--manifest (all labels)', () => { + it('should put labels into appropriate CustomLabels file', async () => { + await nutshell.modifyLocalGlobs([forceAppLabels, myAppLabels]); + const xml = 'CustomLabelsCustomLabels'; + const packageXml = await nutshell.createPackageXml(xml); + await nutshell.retrieve({ args: `--manifest ${packageXml}` }); + + await nutshell.expect.filesToBeChanged([forceAppLabels, myAppLabels]); + await nutshell.expect.filesToNotContainString(forceAppLabels, 'my_app_Label_1'); + await nutshell.expect.filesToNotContainString( + myAppLabels, + 'force_app_Label_1', + 'force_app_Label_2' + ); + }); + }); + + describe('--manifest (individual labels)', () => { + it('should put labels into appropriate CustomLabels file', async () => { + const xml = 'force_app_Label_1CustomLabel'; + const packageXml = await nutshell.createPackageXml(xml); + await nutshell.retrieve({ args: `--manifest ${packageXml}` }); + await nutshell.expect.filesToContainString(forceAppLabels, 'force_app_Label_1'); + await nutshell.expect.filesToNotContainString(forceAppLabels, 'my_app_Label_1'); + }); }); }); }); diff --git a/yarn.lock b/yarn.lock index 04e69d74e..cb65fe62d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -738,10 +738,10 @@ resolved "https://registry.npmjs.org/@salesforce/schemas/-/schemas-1.0.4.tgz#e75ea97102f028bb1773523ca4e783e89a381ff7" integrity sha512-JatCrSuWbr4aWJlkJ9CVCUucZvY1pfKfO/m1AHxaWix5kUQykA+oI1zQRKjelOy2IbDmz09cBCEGjMrMp3u+dA== -"@salesforce/source-deploy-retrieve@1.1.21": - version "1.1.21" - resolved "https://registry.yarnpkg.com/@salesforce/source-deploy-retrieve/-/source-deploy-retrieve-1.1.21.tgz#afb3b7dfe5b79ce1b3b9a016433fba9e3d83e0c7" - integrity sha512-+2nJh/GsSGE6bUolrcV6zA+RAutGIKjzS6IS7uFwHrpy0r7NCElgjRL/EkfahMgKxuVJDg8nQiVzWzi108AgPw== +"@salesforce/source-deploy-retrieve@^2": + version "2.0.0" + resolved "https://registry.npmjs.org/@salesforce/source-deploy-retrieve/-/source-deploy-retrieve-2.0.0.tgz#457ed93af2e5a544145b233664abb10e8062b378" + integrity sha512-1KK2aEcGx4c6cSJ6jeXUWDotBuMEAgnEPt32Ozy/I8rhP7tMvGp08lg5ICnsn3EOx4dcvXEmc3et9JbChezPgw== dependencies: "@salesforce/core" "2.13.0" archiver "4.0.1"