Skip to content

Commit ab290a7

Browse files
authored
feat: support split custom labels (#62)
* feat: support split custom labels * test: fix tests
1 parent 782a099 commit ab290a7

File tree

11 files changed

+388
-60
lines changed

11 files changed

+388
-60
lines changed

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"@oclif/config": "^1",
99
"@salesforce/command": "^3.1.0",
1010
"@salesforce/core": "^2.20.8",
11-
"@salesforce/source-deploy-retrieve": "1.1.21",
11+
"@salesforce/source-deploy-retrieve": "^2",
1212
"chalk": "^4.1.0",
1313
"cli-ux": "^5.5.1",
1414
"tslib": "^2"
@@ -120,7 +120,7 @@
120120
"test": "sf-test",
121121
"test:command-reference": "./bin/run commandreference:generate --erroronwarnings",
122122
"test:deprecation-policy": "./bin/run snapshot:compare",
123-
"test:nuts": "ts-node ./test/nuts/generateNuts.ts && nyc mocha \"**/*.nut.ts\" --slow 3000 --timeout 600000 --parallel --retries 1",
123+
"test:nuts": "ts-node ./test/nuts/generateNuts.ts && nyc mocha \"**/*.nut.ts\" --slow 3000 --timeout 600000 --parallel --retries 0",
124124
"version": "oclif-dev readme"
125125
},
126126
"husky": {

src/commands/force/source/retrieve.ts

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ import { Lifecycle, Messages, SfdxError } from '@salesforce/core';
1111
import { Duration } from '@salesforce/kit';
1212
import { asArray, asString } from '@salesforce/ts-types';
1313
import { blue } from 'chalk';
14-
import { MetadataApiRetrieveStatus, RetrieveResult } from '@salesforce/source-deploy-retrieve';
15-
import { FileProperties } from '@salesforce/source-deploy-retrieve/lib/src/client/types';
14+
import { MetadataApiRetrieveStatus } from '@salesforce/source-deploy-retrieve';
15+
import { FileProperties, FileResponse } from '@salesforce/source-deploy-retrieve/lib/src/client/types';
1616
import { SourceCommand } from '../../../sourceCommand';
1717

1818
Messages.importMessagesDirectory(__dirname);
@@ -52,8 +52,7 @@ export class Retrieve extends SourceCommand {
5252
};
5353
protected readonly lifecycleEventNames = ['preretrieve', 'postretrieve'];
5454

55-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
56-
public async run(): Promise<RetrieveResult> {
55+
public async run(): Promise<FileResponse[]> {
5756
const hookEmitter = Lifecycle.getInstance();
5857

5958
const defaultPackagePath = this.project.getDefaultPackage().fullPath;
@@ -85,13 +84,11 @@ export class Retrieve extends SourceCommand {
8584
throw new SfdxError(messages.getMessage('retrieveTimeout', [(this.flags.wait as Duration).minutes]));
8685
}
8786

88-
if (this.flags.json) {
89-
this.ux.logJson(mdapiResult.getFileResponses());
90-
} else {
87+
if (!this.flags.json) {
9188
this.printTable(results, true);
9289
}
9390

94-
return mdapiResult;
91+
return mdapiResult.getFileResponses();
9592
}
9693

9794
/**

src/sourceCommand.ts

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import * as path from 'path';
88
import { SfdxCommand } from '@salesforce/command';
99
import { ComponentSet } from '@salesforce/source-deploy-retrieve';
1010
import { fs, SfdxError, Logger, ConfigFile } from '@salesforce/core';
11-
import { ComponentLike } from '@salesforce/source-deploy-retrieve/lib/src/common';
11+
import { ComponentLike } from '@salesforce/source-deploy-retrieve/lib/src/resolve';
1212

1313
export type FlagOptions = {
1414
packagenames?: string[];
@@ -59,11 +59,12 @@ export abstract class SourceCommand extends SfdxCommand {
5959
if (options.manifest) {
6060
logger.debug(`Building ComponentSet from manifest: ${options.manifest}`);
6161
const packageDirs = this.project.getUniquePackageDirectories().map((pDir) => pDir.fullPath);
62-
for (const packageDir of packageDirs) {
63-
logger.debug(`Searching in packageDir: ${packageDir} for matching metadata`);
64-
const compSet = await ComponentSet.fromManifestFile(options.manifest, { resolve: packageDir });
65-
setAggregator.push(...compSet);
66-
}
62+
logger.debug(`Searching in packageDir: ${packageDirs.join(', ')} for matching metadata`);
63+
const compSet = await ComponentSet.fromManifest({
64+
manifestPath: options.manifest,
65+
resolveSourcePaths: packageDirs,
66+
});
67+
setAggregator.push(...compSet);
6768
}
6869

6970
// Resolve metadata entries with source in package directories.
@@ -82,8 +83,12 @@ export abstract class SourceCommand extends SfdxCommand {
8283

8384
// Search the packages directories for matching metadata
8485
const packageDirs = this.project.getUniquePackageDirectories().map((pDir) => pDir.fullPath);
85-
logger.debug(`Searching for matching metadata in packageDirs: ${packageDirs.toString()}`);
86-
setAggregator.push(...ComponentSet.fromSource({ inclusiveFilter: filter, fsPaths: packageDirs }));
86+
logger.debug(`Searching for matching metadata in packageDirs: ${packageDirs.join(', ')}`);
87+
88+
const fromSource = ComponentSet.fromSource({ fsPaths: packageDirs, include: filter });
89+
// If no matching metadata is found, default to the original component set
90+
const finalized = fromSource.size > 0 ? fromSource : filter;
91+
setAggregator.push(...finalized);
8792
}
8893

8994
const componentSet = new ComponentSet(setAggregator);

test/commands/source/retrieve.test.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ describe('force:source:retrieve', () => {
113113
it('should pass along sourcepath', async () => {
114114
const sourcepath = ['somepath'];
115115
const result = await run({ sourcepath, json: true });
116-
expect(result).to.deep.equal(stubbedResults);
116+
expect(result).to.deep.equal(stubbedResults.getFileResponses());
117117
ensureCreateComponentSetArgs({ sourcepath });
118118
ensureRetrieveArgs();
119119
ensureHookArgs();
@@ -122,7 +122,7 @@ describe('force:source:retrieve', () => {
122122
it('should pass along metadata', async () => {
123123
const metadata = ['ApexClass:MyClass'];
124124
const result = await run({ metadata, json: true });
125-
expect(result).to.deep.equal(stubbedResults);
125+
expect(result).to.deep.equal(stubbedResults.getFileResponses());
126126
ensureCreateComponentSetArgs({ metadata });
127127
ensureRetrieveArgs();
128128
ensureHookArgs();
@@ -131,7 +131,7 @@ describe('force:source:retrieve', () => {
131131
it('should pass along manifest', async () => {
132132
const manifest = 'package.xml';
133133
const result = await run({ manifest, json: true });
134-
expect(result).to.deep.equal(stubbedResults);
134+
expect(result).to.deep.equal(stubbedResults.getFileResponses());
135135
ensureCreateComponentSetArgs({ manifest });
136136
ensureRetrieveArgs();
137137
ensureHookArgs();
@@ -141,7 +141,7 @@ describe('force:source:retrieve', () => {
141141
const manifest = 'package.xml';
142142
const apiversion = '50.0';
143143
const result = await run({ manifest, apiversion, json: true });
144-
expect(result).to.deep.equal(stubbedResults);
144+
expect(result).to.deep.equal(stubbedResults.getFileResponses());
145145
ensureCreateComponentSetArgs({ apiversion, manifest });
146146
ensureRetrieveArgs();
147147
ensureHookArgs();
@@ -151,7 +151,7 @@ describe('force:source:retrieve', () => {
151151
const manifest = 'package.xml';
152152
const packagenames = ['package1'];
153153
const result = await run({ manifest, packagenames, json: true });
154-
expect(result).to.deep.equal(stubbedResults);
154+
expect(result).to.deep.equal(stubbedResults.getFileResponses());
155155
ensureCreateComponentSetArgs({ manifest, packagenames });
156156
ensureRetrieveArgs({ packageNames: packagenames });
157157
ensureHookArgs();

test/commands/source/sourceCommand.test.ts

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ describe('SourceCommand', () => {
5555
beforeEach(() => {
5656
fileExistsSyncStub = stubMethod(sandbox, fsCore, 'fileExistsSync');
5757
fromSourceStub = stubMethod(sandbox, ComponentSet, 'fromSource');
58-
fromManifestStub = stubMethod(sandbox, ComponentSet, 'fromManifestFile');
58+
fromManifestStub = stubMethod(sandbox, ComponentSet, 'fromManifest');
5959
getUniquePackageDirectoriesStub = stubMethod(sandbox, SfdxProject.prototype, 'getUniquePackageDirectories');
6060
componentSet = new ComponentSet();
6161
});
@@ -173,7 +173,7 @@ describe('SourceCommand', () => {
173173
expect(fromSourceArgs).to.have.deep.property('fsPaths', [packageDir1]);
174174
const filter = new ComponentSet();
175175
filter.add({ type: 'ApexClass', fullName: '*' });
176-
expect(fromSourceArgs).to.have.deep.property('inclusiveFilter', filter);
176+
expect(fromSourceArgs).to.have.deep.property('include', filter);
177177
expect(compSet.size).to.equal(1);
178178
expect(compSet.has(apexClassComponent)).to.equal(true);
179179
});
@@ -195,7 +195,7 @@ describe('SourceCommand', () => {
195195
expect(fromSourceArgs).to.have.deep.property('fsPaths', [packageDir1]);
196196
const filter = new ComponentSet();
197197
filter.add({ type: 'ApexClass', fullName: 'MyClass' });
198-
expect(fromSourceArgs).to.have.deep.property('inclusiveFilter', filter);
198+
expect(fromSourceArgs).to.have.deep.property('include', filter);
199199
expect(compSet.size).to.equal(1);
200200
expect(compSet.has(apexClassComponent)).to.equal(true);
201201
});
@@ -219,7 +219,7 @@ describe('SourceCommand', () => {
219219
const filter = new ComponentSet();
220220
filter.add({ type: 'ApexClass', fullName: 'MyClass' });
221221
filter.add({ type: 'CustomObject', fullName: '*' });
222-
expect(fromSourceArgs).to.have.deep.property('inclusiveFilter', filter);
222+
expect(fromSourceArgs).to.have.deep.property('include', filter);
223223
expect(compSet.size).to.equal(2);
224224
expect(compSet.has(apexClassComponent)).to.equal(true);
225225
expect(compSet.has(customObjectComponent)).to.equal(true);
@@ -245,7 +245,7 @@ describe('SourceCommand', () => {
245245
expect(fromSourceArgs).to.have.deep.property('fsPaths', [packageDir1, packageDir2]);
246246
const filter = new ComponentSet();
247247
filter.add({ type: 'ApexClass', fullName: '*' });
248-
expect(fromSourceArgs).to.have.deep.property('inclusiveFilter', filter);
248+
expect(fromSourceArgs).to.have.deep.property('include', filter);
249249
expect(compSet.size).to.equal(2);
250250
expect(compSet.has(apexClassComponent)).to.equal(true);
251251
expect(compSet.has(apexClassComponent2)).to.equal(true);
@@ -264,19 +264,19 @@ describe('SourceCommand', () => {
264264

265265
const compSet = await command.callCreateComponentSet(options);
266266
expect(fromManifestStub.calledOnce).to.equal(true);
267-
expect(fromManifestStub.firstCall.args[0]).to.equal(options.manifest);
268-
expect(fromManifestStub.firstCall.args[1]).to.deep.equal({ resolve: packageDir1 });
267+
expect(fromManifestStub.firstCall.args[0]).to.deep.equal({
268+
manifestPath: options.manifest,
269+
resolveSourcePaths: [packageDir1],
270+
});
269271
expect(compSet.size).to.equal(1);
270272
expect(compSet.has(apexClassComponent)).to.equal(true);
271273
});
272274

273275
it('should create ComponentSet from manifest and multiple package', async () => {
274276
componentSet.add(apexClassComponent);
275-
const componentSet2 = new ComponentSet();
276277
const apexClassComponent2 = { type: 'ApexClass', fullName: 'MyClass2' };
277-
componentSet2.add(apexClassComponent2);
278+
componentSet.add(apexClassComponent2);
278279
fromManifestStub.onFirstCall().resolves(componentSet);
279-
fromManifestStub.onSecondCall().resolves(componentSet2);
280280
const packageDir1 = path.resolve('force-app');
281281
const packageDir2 = path.resolve('my-app');
282282
getUniquePackageDirectoriesStub.returns([{ fullPath: packageDir1 }, { fullPath: packageDir2 }]);
@@ -287,11 +287,11 @@ describe('SourceCommand', () => {
287287
};
288288

289289
const compSet = await command.callCreateComponentSet(options);
290-
expect(fromManifestStub.callCount).to.equal(2);
291-
expect(fromManifestStub.firstCall.args[0]).to.equal(options.manifest);
292-
expect(fromManifestStub.firstCall.args[1]).to.deep.equal({ resolve: packageDir1 });
293-
expect(fromManifestStub.secondCall.args[0]).to.equal(options.manifest);
294-
expect(fromManifestStub.secondCall.args[1]).to.deep.equal({ resolve: packageDir2 });
290+
expect(fromManifestStub.callCount).to.equal(1);
291+
expect(fromManifestStub.firstCall.args[0]).to.deep.equal({
292+
manifestPath: options.manifest,
293+
resolveSourcePaths: [packageDir1, packageDir2],
294+
});
295295
expect(compSet.size).to.equal(2);
296296
expect(compSet.has(apexClassComponent)).to.equal(true);
297297
expect(compSet.has(apexClassComponent2)).to.equal(true);

test/nuts/assertions.ts

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,14 @@ export class Assertions {
4848
expect(fileHistory.changedFromPrevious, 'File to be changed').to.be.true;
4949
}
5050

51+
/**
52+
* Expect given file to NOT be changed according to the file history provided by FileTracker
53+
*/
54+
public fileToNotBeChanged(file: string): void {
55+
const fileHistory = this.fileTracker.getLatest(file);
56+
expect(fileHistory.changedFromPrevious, 'File to NOT be changed').to.be.false;
57+
}
58+
5159
/**
5260
* Expect all files found by globs to be changed according to the file history provided by FileTracker
5361
*/
@@ -61,6 +69,19 @@ export class Assertions {
6169
expect(allChanged, 'all files to be changed').to.be.true;
6270
}
6371

72+
/**
73+
* Expect all files found by globs to NOT be changed according to the file history provided by FileTracker
74+
*/
75+
public async filesToNotBeChanged(globs: string[]): Promise<void> {
76+
const files = await this.doGlob(globs);
77+
const fileHistories = files
78+
.filter((f) => !f.endsWith('.resource-meta.xml'))
79+
.map((f) => this.fileTracker.getLatest(f))
80+
.filter((f) => !!f);
81+
const allChanged = fileHistories.every((f) => f.changedFromPrevious);
82+
expect(allChanged, 'all files to NOT be changed').to.be.false;
83+
}
84+
6485
/**
6586
* Finds all files in project based on the provided globs and expects them to be updated on the server
6687
*/
@@ -122,7 +143,7 @@ export class Assertions {
122143
}
123144

124145
/**
125-
* Expects given globs to return files
146+
* Expects files found by glob to not contain any of the provided strings
126147
*/
127148
public async filesToNotContainString(glob: string, ...strings: string[]): Promise<void> {
128149
const files = await this.doGlob([glob]);
@@ -134,6 +155,19 @@ export class Assertions {
134155
}
135156
}
136157

158+
/**
159+
* Expects files found by glob to contain the provided strings
160+
*/
161+
public async filesToContainString(glob: string, ...strings: string[]): Promise<void> {
162+
const files = await this.doGlob([glob]);
163+
for (const file of files) {
164+
const contents = await fs.readFile(file, 'UTF-8');
165+
for (const str of strings) {
166+
expect(contents, `expect ${file} to not include ${str}`).to.include(str);
167+
}
168+
}
169+
}
170+
137171
/**
138172
* Expect the retrieved package to exist and contain some files
139173
*/

test/nuts/executionLog.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ export class ExecutionLog {
4747
* Return the most recent entry for a command
4848
*/
4949
public getLatest(cmd: string): ExecutionLog.Details {
50-
return this.log.get(cmd).reverse()[0];
50+
const sorted = this.log.get(cmd).sort((a, b) => (a.timestamp < b.timestamp ? 1 : -1));
51+
return sorted[0];
5152
}
5253

5354
private async querySourceMembers(): Promise<SourceMember[]> {

test/nuts/nutshell.ts

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import * as fg from 'fast-glob';
1414
import { exec } from 'shelljs';
1515
import { TestSession, execCmd } from '@salesforce/cli-plugins-testkit';
1616
import { Env } from '@salesforce/kit';
17-
import { AnyJson, ensureString, JsonMap, Nullable } from '@salesforce/ts-types';
17+
import { AnyJson, Dictionary, ensureString, JsonMap, Nullable } from '@salesforce/ts-types';
1818
import { AuthInfo, Connection, fs, NamedPackageDir, SfdxProject } from '@salesforce/core';
1919
import { AsyncCreatable } from '@salesforce/kit';
2020
import { debug, Debugger } from 'debug';
@@ -176,6 +176,18 @@ export class Nutshell extends AsyncCreatable<Nutshell.Options> {
176176
}
177177
}
178178

179+
/**
180+
* Read files found by globs
181+
*/
182+
public async readGlobs(globs: string[]): Promise<Dictionary<string>> {
183+
const files = await this.doGlob(globs);
184+
const returnValue = {};
185+
for (const file of files) {
186+
returnValue[file] = await fs.readFile(file, 'UTF-8');
187+
}
188+
return returnValue;
189+
}
190+
179191
/**
180192
* Read the org's sourcePathInfos.json
181193
*/
@@ -206,6 +218,28 @@ export class Nutshell extends AsyncCreatable<Nutshell.Options> {
206218
return fs.writeJson(maxRevisionPath, contents);
207219
}
208220

221+
/**
222+
* Write file
223+
*/
224+
public async writeFile(filename: string, contents: string): Promise<void> {
225+
return fs.writeFile(filename, contents);
226+
}
227+
228+
/**
229+
* Create a package.xml
230+
*/
231+
public async createPackageXml(xml: string): Promise<string> {
232+
const packageXml = `<?xml version="1.0" encoding="UTF-8"?>
233+
<Package xmlns="http://soap.sforce.com/2006/04/metadata">
234+
${xml}
235+
<version>51.0</version>
236+
</Package>
237+
`;
238+
const packageXmlPath = path.join(this.session.project.dir, 'package.xml');
239+
await fs.writeFile(packageXmlPath, packageXml);
240+
return packageXmlPath;
241+
}
242+
209243
/**
210244
* Delete the org's sourcePathInfos.json
211245
*/
@@ -228,6 +262,16 @@ export class Nutshell extends AsyncCreatable<Nutshell.Options> {
228262
return fs.unlink(maxRevisionPath);
229263
}
230264

265+
/**
266+
* Delete the files found by the given globs
267+
*/
268+
public async deleteGlobs(globs: string[]): Promise<void> {
269+
const files = await this.doGlob(globs);
270+
for (const file of files) {
271+
await fs.unlink(file);
272+
}
273+
}
274+
231275
/**
232276
* Delete all source files in the project directory
233277
*/
@@ -405,6 +449,7 @@ export class Nutshell extends AsyncCreatable<Nutshell.Options> {
405449
: [
406450
// TODO: remove this config:set call
407451
'sfdx config:set apiVersion=50.0 --global',
452+
'sfdx config:set restDeploy=false --global',
408453
'sfdx force:org:create -d 1 -s -f config/project-scratch-def.json',
409454
];
410455
return await TestSession.create({

0 commit comments

Comments
 (0)