Skip to content

Commit 452cac5

Browse files
fix: move force:source:ignored:list command directly over, with aliases
1 parent 464e762 commit 452cac5

File tree

7 files changed

+289
-17
lines changed

7 files changed

+289
-17
lines changed

messages/list.ignored.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# description
2+
3+
check your local project package directories for forceignored files
4+
5+
# examples
6+
7+
- $ <%= config.bin %> <%= command.id %>
8+
9+
- $ <%= config.bin %> <%= command.id %> --source-dir force-app
10+
11+
# flags.source-dir
12+
13+
file or directory of files that the command checks for foreceignored files
14+
15+
# invalidSourceDir
16+
17+
File or directory '%s' doesn't exist in your project. Specify one that exists and rerun the command.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
},
2020
"devDependencies": {
2121
"@oclif/plugin-command-snapshot": "^3.3.9",
22-
"@salesforce/cli-plugins-testkit": "^3.2.25",
22+
"@salesforce/cli-plugins-testkit": "^3.2.26",
2323
"@salesforce/dev-config": "^3.1.0",
2424
"@salesforce/dev-scripts": "^4.1.3",
2525
"@salesforce/plugin-command-reference": "^2.4.1",

src/commands/project/deploy/preview.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,6 @@ export default class DeployMetadataPreview extends SfCommand<PreviewResult> {
3232
description: messages.getMessage('flags.ignore-conflicts.description'),
3333
default: false,
3434
}),
35-
'only-ignored': Flags.boolean({
36-
char: 'i',
37-
summary: messages.getMessage('flags.only-ignored.summary'),
38-
}),
3935
manifest: Flags.file({
4036
char: 'x',
4137
description: messages.getMessage('flags.manifest.description'),
@@ -80,7 +76,7 @@ export default class DeployMetadataPreview extends SfCommand<PreviewResult> {
8076

8177
const [componentSet, filesWithConflicts] = await Promise.all([
8278
buildComponentSet({ ...flags, 'target-org': flags['target-org'].getUsername() }, stl),
83-
getConflictFiles(stl, flags['ignore-conflicts'] || flags['only-ignored']),
79+
getConflictFiles(stl, flags['ignore-conflicts']),
8480
]);
8581

8682
const output = compileResults({
@@ -92,7 +88,7 @@ export default class DeployMetadataPreview extends SfCommand<PreviewResult> {
9288
});
9389

9490
if (!this.jsonEnabled()) {
95-
printTables(output, 'deploy', flags['only-ignored']);
91+
printTables(output, 'deploy');
9692
}
9793
return output;
9894
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/*
2+
* Copyright (c) 2022, salesforce.com, inc.
3+
* All rights reserved.
4+
* Licensed under the BSD 3-Clause license.
5+
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
6+
*/
7+
import * as path from 'path';
8+
import * as fs from 'fs';
9+
import { Messages, SfError } from '@salesforce/core';
10+
import { ForceIgnore } from '@salesforce/source-deploy-retrieve';
11+
import { SfCommand, Flags } from '@salesforce/sf-plugins-core';
12+
13+
Messages.importMessagesDirectory(__dirname);
14+
const messages = Messages.loadMessages('@salesforce/plugin-deploy-retrieve', 'list.ignored');
15+
16+
export type SourceIgnoredResults = {
17+
ignoredFiles: string[];
18+
};
19+
20+
export class Ignored extends SfCommand<SourceIgnoredResults> {
21+
public static readonly summary = messages.getMessage('description');
22+
public static readonly description = messages.getMessage('description');
23+
public static readonly examples = messages.getMessages('examples');
24+
public static readonly requiresProject = true;
25+
public static readonly aliases = ['force:source:ignored:list'];
26+
public static readonly deprecateAliases = true;
27+
public static readonly flags = {
28+
'source-dir': Flags.file({
29+
char: 'p',
30+
aliases: ['sourcepath'],
31+
deprecateAliases: true,
32+
summary: messages.getMessage('flags.source-dir'),
33+
}),
34+
};
35+
36+
private forceIgnore!: ForceIgnore;
37+
/**
38+
* Outputs all forceignored files from package directories of a project,
39+
* or based on a sourcepath param that points to a specific file or directory.
40+
*/
41+
// eslint-disable-next-line @typescript-eslint/require-await
42+
public async run(): Promise<SourceIgnoredResults> {
43+
const flags = (await this.parse(Ignored)).flags;
44+
try {
45+
this.forceIgnore = ForceIgnore.findAndCreate(this.project.getPath());
46+
const sourcepaths = flags['source-dir']
47+
? [flags['source-dir']]
48+
: this.project.getUniquePackageDirectories().map((pDir) => pDir.path);
49+
50+
const ignoredFiles = (await Promise.all(sourcepaths.map((sp) => this.statIgnored(sp.trim())))).flat();
51+
52+
// Command output
53+
if (ignoredFiles.length) {
54+
this.log('Found the following ignored files:');
55+
ignoredFiles.forEach((filepath) => this.log(filepath));
56+
} else {
57+
this.log('No ignored files found in paths:');
58+
sourcepaths.forEach((sp) => this.log(sp));
59+
}
60+
61+
return { ignoredFiles };
62+
} catch (err) {
63+
const error = err as Error;
64+
if ('code' in error && error.code === 'ENOENT') {
65+
throw messages.createError('invalidSourceDir', [flags['source-dir']]);
66+
}
67+
throw SfError.wrap(error);
68+
}
69+
}
70+
71+
// Stat the filepath. Test if a file, recurse if a directory.
72+
private async statIgnored(filepath: string): Promise<string[]> {
73+
const stats = await fs.promises.stat(filepath);
74+
if (stats.isDirectory()) {
75+
return (await Promise.all(await this.findIgnored(filepath))).flat();
76+
} else {
77+
return this.isIgnored(filepath) ? [filepath] : [];
78+
}
79+
}
80+
81+
// Recursively search a directory for source files to test.
82+
private async findIgnored(dir: string): Promise<Array<Promise<string[]>>> {
83+
return (await fs.promises.readdir(dir)).map((filename) => this.statIgnored(path.join(dir, filename)));
84+
}
85+
86+
// Test if a source file is denied, adding any ignored files to
87+
// the ignoredFiles array for output.
88+
private isIgnored(filepath: string): boolean {
89+
return this.forceIgnore.denies(filepath);
90+
}
91+
}

src/utils/previewOutput.ts

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -242,16 +242,15 @@ export const printIgnoredTable = (files: PreviewFile[], baseOperation: BaseOpera
242242
}
243243
};
244244

245-
export const printTables = (result: PreviewResult, baseOperation: BaseOperation, onlyIgnored = false): void => {
246-
if (!onlyIgnored) {
247-
printConflictsTable(result.conflicts);
248-
printDeleteTable(result.toDelete);
249-
if (baseOperation === 'deploy') {
250-
printDeployTable(result.toDeploy);
251-
} else if (baseOperation === 'retrieve') {
252-
printRetrieveTable(result.toRetrieve);
253-
}
245+
export const printTables = (result: PreviewResult, baseOperation: BaseOperation): void => {
246+
printConflictsTable(result.conflicts);
247+
printDeleteTable(result.toDelete);
248+
if (baseOperation === 'deploy') {
249+
printDeployTable(result.toDeploy);
250+
} else if (baseOperation === 'retrieve') {
251+
printRetrieveTable(result.toRetrieve);
254252
}
253+
255254
printIgnoredTable(result.ignored, baseOperation);
256255
};
257256

test/nuts/list/ignored.nut.ts

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
/*
2+
* Copyright (c) 2020, salesforce.com, inc.
3+
* All rights reserved.
4+
* Licensed under the BSD 3-Clause license.
5+
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
6+
*/
7+
import * as fs from 'fs';
8+
import * as os from 'os';
9+
import * as path from 'path';
10+
import { execCmd, TestSession } from '@salesforce/cli-plugins-testkit';
11+
import { expect } from 'chai';
12+
import { SourceIgnoredResults } from '@salesforce/plugin-source/lib/commands/force/source/ignored/list';
13+
14+
describe('project:list:ignored', () => {
15+
let session: TestSession;
16+
let forceIgnorePath: string;
17+
let originalForceIgnore: string;
18+
19+
const pathToIgnoredFile1 = path.join('foo-bar', 'app', 'classes', 'FooBar.cls');
20+
const pathToIgnoredFile2 = path.join('foo-bar', 'app', 'classes', 'FooBar.cls-meta.xml');
21+
22+
before(async () => {
23+
session = await TestSession.create({
24+
project: {
25+
gitClone: 'https://github.com/salesforcecli/sample-project-multiple-packages',
26+
},
27+
});
28+
forceIgnorePath = path.join(session.project.dir, '.forceignore');
29+
originalForceIgnore = await fs.promises.readFile(forceIgnorePath, 'utf8');
30+
});
31+
32+
after(async () => {
33+
await session?.clean();
34+
});
35+
36+
describe('no forceignore', () => {
37+
before(async () => {
38+
await fs.promises.rm(forceIgnorePath);
39+
});
40+
after(async () => {
41+
await fs.promises.writeFile(forceIgnorePath, originalForceIgnore);
42+
});
43+
it('default PkgDir', () => {
44+
const result = execCmd<SourceIgnoredResults>('project:list:ignored --json', { ensureExitCode: 0 })?.jsonOutput
45+
?.result;
46+
expect(result?.ignoredFiles).to.deep.equal([]);
47+
});
48+
it('specified sourcePath', () => {
49+
const result2 = execCmd<SourceIgnoredResults>('project:list:ignored --json -p foo-bar', {
50+
ensureExitCode: 0,
51+
})?.jsonOutput?.result;
52+
expect(result2?.ignoredFiles).to.deep.equal([]);
53+
});
54+
});
55+
56+
describe('no files are ignored (empty forceignore)', () => {
57+
before(async () => {
58+
await fs.promises.writeFile(forceIgnorePath, '');
59+
});
60+
after(async () => {
61+
await fs.promises.writeFile(forceIgnorePath, originalForceIgnore);
62+
});
63+
it('default PkgDir', () => {
64+
const result = execCmd<SourceIgnoredResults>('project:list:ignored --json', { ensureExitCode: 0 })?.jsonOutput
65+
?.result;
66+
expect(result?.ignoredFiles).to.deep.equal([]);
67+
});
68+
it('specified sourcePath', () => {
69+
const result2 = execCmd<SourceIgnoredResults>('project:list:ignored --json -p foo-bar', {
70+
ensureExitCode: 0,
71+
})?.jsonOutput?.result;
72+
expect(result2?.ignoredFiles).to.deep.equal([]);
73+
});
74+
});
75+
76+
describe('returns an ignored class using specified path in forceignore', () => {
77+
before(async () => {
78+
// forceignore uses a library that wants ignore rules in posix format.
79+
await fs.promises.appendFile(
80+
forceIgnorePath,
81+
`${path.normalize(pathToIgnoredFile1).split(path.sep).join(path.posix.sep)}${os.EOL}`
82+
);
83+
await fs.promises.appendFile(
84+
forceIgnorePath,
85+
`${path.normalize(pathToIgnoredFile2).split(path.sep).join(path.posix.sep)}${os.EOL}`
86+
);
87+
});
88+
after(async () => {
89+
await fs.promises.writeFile(forceIgnorePath, originalForceIgnore);
90+
});
91+
it('default PkgDir', () => {
92+
const result = execCmd<SourceIgnoredResults>('project:list:ignored --json', { ensureExitCode: 0 })?.jsonOutput
93+
?.result;
94+
expect(result?.ignoredFiles).to.include(pathToIgnoredFile1);
95+
expect(result?.ignoredFiles).to.include(pathToIgnoredFile2);
96+
});
97+
it('specified sourcePath', () => {
98+
const result2 = execCmd<SourceIgnoredResults>('project:list:ignored --json -p foo-bar', {
99+
ensureExitCode: 0,
100+
})?.jsonOutput?.result;
101+
expect(result2?.ignoredFiles).to.include(pathToIgnoredFile1);
102+
expect(result2?.ignoredFiles).to.include(pathToIgnoredFile2);
103+
});
104+
});
105+
106+
describe('returns an ignored class using wildcards', () => {
107+
before(async () => {
108+
await fs.promises.appendFile(forceIgnorePath, '**/FooBar.*');
109+
});
110+
after(async () => {
111+
await fs.promises.writeFile(forceIgnorePath, originalForceIgnore);
112+
});
113+
114+
it('default PkgDir', () => {
115+
const result = execCmd<SourceIgnoredResults>('project:list:ignored --json', {
116+
ensureExitCode: 0,
117+
})?.jsonOutput?.result?.ignoredFiles;
118+
expect(result).to.include(pathToIgnoredFile1);
119+
expect(result).to.include(pathToIgnoredFile2);
120+
});
121+
it('specified sourcePath', () => {
122+
const result2 = execCmd<SourceIgnoredResults>('project:list:ignored --json -p foo-bar', {
123+
ensureExitCode: 0,
124+
})?.jsonOutput?.result;
125+
expect(result2?.ignoredFiles).to.include(pathToIgnoredFile1);
126+
expect(result2?.ignoredFiles).to.include(pathToIgnoredFile2);
127+
});
128+
});
129+
130+
describe('returns an ignored non-metadata component', () => {
131+
const lwcDir = path.join('foo-bar', 'app', 'lwc');
132+
const lwcConfigPath = path.join(lwcDir, 'jsconfig.json');
133+
134+
before(async () => {
135+
await fs.promises.mkdir(path.join(session.project.dir, lwcDir), { recursive: true });
136+
await fs.promises.writeFile(path.join(session.project.dir, lwcConfigPath), '{}');
137+
});
138+
after(async () => {
139+
await fs.promises.writeFile(forceIgnorePath, originalForceIgnore);
140+
});
141+
142+
it('default PkgDir', () => {
143+
const result = execCmd<SourceIgnoredResults>('project:list:ignored --json', { ensureExitCode: 0 })?.jsonOutput
144+
?.result;
145+
expect(result?.ignoredFiles).to.include(lwcConfigPath);
146+
});
147+
it('specified sourcePath', () => {
148+
const result2 = execCmd<SourceIgnoredResults>('project:list:ignored --json -p foo-bar', {
149+
ensureExitCode: 0,
150+
})?.jsonOutput?.result;
151+
expect(result2?.ignoredFiles).to.include(lwcConfigPath);
152+
});
153+
});
154+
});

yarn.lock

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1090,7 +1090,7 @@
10901090
mv "~2"
10911091
safe-json-stringify "~1"
10921092

1093-
"@salesforce/cli-plugins-testkit@^3.2.24", "@salesforce/cli-plugins-testkit@^3.2.25":
1093+
"@salesforce/cli-plugins-testkit@^3.2.24":
10941094
version "3.2.25"
10951095
resolved "https://registry.yarnpkg.com/@salesforce/cli-plugins-testkit/-/cli-plugins-testkit-3.2.25.tgz#c0799b36bd3563950a057b0925a9879f4f80751f"
10961096
integrity sha512-bnzQnY/T7A2Wq894wmEvv9btxDhBSX8Iyq8XDUSgcMdjeWFuZS1JQHgazlSaDkfA/3J5tSbEyPY/vEAi0HRd4g==
@@ -1105,6 +1105,21 @@
11051105
strip-ansi "6.0.1"
11061106
ts-retry-promise "^0.7.0"
11071107

1108+
"@salesforce/cli-plugins-testkit@^3.2.26":
1109+
version "3.2.26"
1110+
resolved "https://registry.yarnpkg.com/@salesforce/cli-plugins-testkit/-/cli-plugins-testkit-3.2.26.tgz#c87324fb7ac0068fa1a4520bdc979ba000be8752"
1111+
integrity sha512-9NceA4xUFBpLuDlHdOsK37+6sb9fGNUGO2oXHu2p5iy30LBrl4qvfInVi4UhTWRCcPmIxJNSR4qDCBf+k/BjlQ==
1112+
dependencies:
1113+
"@salesforce/core" "^3.34.1"
1114+
"@salesforce/kit" "^1.9.2"
1115+
"@salesforce/ts-types" "^1.7.3"
1116+
"@types/shelljs" "^0.8.11"
1117+
archiver "^5.2.0"
1118+
debug "^4.3.1"
1119+
shelljs "^0.8.4"
1120+
strip-ansi "6.0.1"
1121+
ts-retry-promise "^0.7.0"
1122+
11081123
"@salesforce/command@^5.3.0":
11091124
version "5.3.3"
11101125
resolved "https://registry.yarnpkg.com/@salesforce/command/-/command-5.3.3.tgz#863380090d8febeb803ceb32690302c490ffe1fc"

0 commit comments

Comments
 (0)