@octoberswimmer/aer-sf-plugin 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,138 @@
1
+ # @octoberswimmer/aer-sf-plugin
2
+
3
+ A [Salesforce CLI](https://developer.salesforce.com/tools/salesforcecli) plugin
4
+ that runs Apex tests locally using [aer](https://github.com/octoberswimmer/aer-dist).
5
+
6
+ ```
7
+ sf aer apex run test
8
+ ```
9
+
10
+ works the same way as
11
+
12
+ ```
13
+ sf apex run test
14
+ ```
15
+
16
+ but instead of submitting tests to a Salesforce org, it stages your project's
17
+ Apex source into a temp directory and invokes `aer test` against the staged
18
+ copy.
19
+
20
+ ## Requirements
21
+
22
+ - [Salesforce CLI](https://developer.salesforce.com/tools/salesforcecli) (`sf`)
23
+ - [aer](https://github.com/octoberswimmer/aer-dist) on `PATH` (or set
24
+ `AER_BIN=/abs/path/to/aer`)
25
+ - Node.js >= 18
26
+
27
+ ## Install
28
+
29
+ From npm:
30
+
31
+ ```
32
+ sf plugins install @octoberswimmer/aer-sf-plugin
33
+ ```
34
+
35
+ ## Usage
36
+
37
+ Run every test in the project:
38
+
39
+ ```
40
+ sf aer apex run test
41
+ ```
42
+
43
+ Run one or more classes (passed to aer as `--filter <ClassName>.*`):
44
+
45
+ ```
46
+ sf aer apex run test --class-names MyClassTest --class-names AccountServiceTest
47
+ ```
48
+
49
+ Run a single method:
50
+
51
+ ```
52
+ sf aer apex run test --tests MyClassTest.testCoolFeature
53
+ ```
54
+
55
+ JUnit output:
56
+
57
+ ```
58
+ sf aer apex run test --result-format junit --output-dir test-results
59
+ ```
60
+
61
+ With code coverage:
62
+
63
+ ```
64
+ sf aer apex run test --code-coverage --output-dir test-results
65
+ ```
66
+
67
+ ## How source is staged
68
+
69
+ The plugin copies the project's Apex source into a temp directory before
70
+ handing it to aer. Two things happen during staging.
71
+
72
+ ### sfdx-project.json `replacements` are applied
73
+
74
+ Salesforce CLI supports a `replacements` configuration that substitutes tokens
75
+ in source files at deploy time:
76
+
77
+ ```json
78
+ {
79
+ "replacements": [
80
+ {
81
+ "glob": "*.*",
82
+ "stringToReplace": "{NAMESPACE}",
83
+ "replaceWithFile": "config/namespace.txt"
84
+ }
85
+ ]
86
+ }
87
+ ```
88
+
89
+ aer does not process `replacements`, so without staging, tokens like
90
+ `{NAMESPACE}` would remain literal in the loaded source and code such as
91
+ `Label.get('{NAMESPACE}', labelName, language)` would fail at runtime. The
92
+ plugin reads `replacements` from `sfdx-project.json` and applies them during
93
+ staging — `stringToReplace` or `regexToReplace` paired with either
94
+ `replaceWithFile` or `replaceWithEnv`. A single trailing newline is trimmed
95
+ from `replaceWithFile` contents, matching
96
+ `@salesforce/source-deploy-retrieve`.
97
+
98
+ ### Duplicate Apex class names across `packageDirectories` are resolved
99
+
100
+ If the same Apex class name appears in more than one `packageDirectories`
101
+ entry, `sf project deploy start` deploys the copies in full-path alphabetical
102
+ order, so the copy at the alphabetically-last full path is the one that ends
103
+ up in the org. `aer test` rejects this and errors on duplicates.
104
+
105
+ To match sf's behaviour, the plugin dedupes `.cls` / `.cls-meta.xml` /
106
+ `.trigger` / `.trigger-meta.xml` files by basename (case-insensitive). The
107
+ copy whose full path sorts alphabetically last is staged; the others are
108
+ discarded. Listing order in `sfdx-project.json` does not affect the outcome.
109
+
110
+ ## Flag compatibility with `sf apex run test`
111
+
112
+ | flag | behaviour |
113
+ | --- | --- |
114
+ | `--class-names`, `--tests` | translated to aer `--filter` |
115
+ | `--result-format human\|junit\|json` | passed through (tap falls back to human) |
116
+ | `--output-dir` | result files written here |
117
+ | `--code-coverage` | passed to aer as `--coverage` (JSON file) |
118
+ | `--test-level RunLocalTests`, `RunSpecifiedTests` | runs locally |
119
+ | `--test-level RunAllTestsInOrg` | warns; falls back to running all local tests |
120
+ | `--suite-names` | warns; not yet implemented |
121
+ | `--target-org`, `--wait`, `--poll-interval`, `--synchronous`, `--api-version`, `--concise`, `--detailed-coverage` | accepted for compatibility; ignored (a warning is printed when any of these is supplied) |
122
+
123
+ ## Development
124
+
125
+ ```
126
+ yarn install
127
+ yarn compile # tsc
128
+ yarn test # mocha unit tests
129
+ node bin/dev.js aer apex run test --help
130
+ ```
131
+
132
+ ## License
133
+
134
+ This plugin is open source, licensed under BSD-3-Clause.
135
+
136
+ Note that [aer](https://github.com/octoberswimmer/aer-dist) itself is not open
137
+ source — it is distributed as a binary under its own license. You must obtain
138
+ and install aer separately for the plugin to do anything useful.
package/bin/dev.js ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env -S node --loader ts-node/esm --no-warnings=ExperimentalWarning
2
+ // eslint-disable-next-line node/shebang
3
+ async function main() {
4
+ const { execute } = await import('@oclif/core');
5
+ await execute({ development: true, dir: import.meta.url });
6
+ }
7
+
8
+ await main();
package/bin/run.js ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+
3
+ // eslint-disable-next-line node/shebang
4
+ async function main() {
5
+ const { execute } = await import('@oclif/core');
6
+ await execute({ dir: import.meta.url });
7
+ }
8
+
9
+ await main();
package/lib/aer.d.ts ADDED
@@ -0,0 +1,12 @@
1
+ export type AerResultFormat = 'human' | 'junit' | 'json' | 'tap';
2
+ export type AerInvocation = {
3
+ stagedDir: string;
4
+ filters: string[];
5
+ resultFormat: AerResultFormat;
6
+ resultFile?: string;
7
+ coverageFile?: string;
8
+ verbose: boolean;
9
+ };
10
+ export declare function buildAerArgs(inv: AerInvocation): string[];
11
+ export declare function aerBinary(): string;
12
+ export declare function runAer(args: string[], cwd: string): Promise<number>;
package/lib/aer.js ADDED
@@ -0,0 +1,48 @@
1
+ import { spawn } from 'node:child_process';
2
+ export function buildAerArgs(inv) {
3
+ const args = ['test', inv.stagedDir];
4
+ for (const f of inv.filters) {
5
+ args.push('--filter', f);
6
+ }
7
+ if (inv.resultFormat === 'junit' && inv.resultFile) {
8
+ args.push('--junit', inv.resultFile);
9
+ }
10
+ else if (inv.resultFormat === 'json' && inv.resultFile) {
11
+ args.push('--json', inv.resultFile);
12
+ }
13
+ else if (inv.resultFormat === 'json' && !inv.resultFile) {
14
+ // aer's --json without a file writes to stdout
15
+ args.push('--json');
16
+ }
17
+ if (inv.coverageFile) {
18
+ args.push('--coverage', inv.coverageFile);
19
+ }
20
+ return args;
21
+ }
22
+ export function aerBinary() {
23
+ return process.env.AER_BIN ?? 'aer';
24
+ }
25
+ export async function runAer(args, cwd) {
26
+ return new Promise((resolvePromise, rejectPromise) => {
27
+ const child = spawn(aerBinary(), args, {
28
+ cwd,
29
+ stdio: 'inherit',
30
+ });
31
+ child.on('error', (err) => {
32
+ if (err.code === 'ENOENT') {
33
+ rejectPromise(new Error(`Could not find the 'aer' binary on PATH. Install aer from https://github.com/octoberswimmer/aer-dist or set AER_BIN to its absolute path.`));
34
+ return;
35
+ }
36
+ rejectPromise(err);
37
+ });
38
+ child.on('exit', (code, signal) => {
39
+ if (signal) {
40
+ resolvePromise(128 + (signal === 'SIGINT' ? 2 : signal === 'SIGTERM' ? 15 : 1));
41
+ }
42
+ else {
43
+ resolvePromise(code ?? 0);
44
+ }
45
+ });
46
+ });
47
+ }
48
+ //# sourceMappingURL=aer.js.map
package/lib/aer.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"aer.js","sourceRoot":"","sources":["../src/aer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAa3C,MAAM,UAAU,YAAY,CAAC,GAAkB;IAC9C,MAAM,IAAI,GAAa,CAAC,MAAM,EAAE,GAAG,CAAC,SAAS,CAAC,CAAC;IAC/C,KAAK,MAAM,CAAC,IAAI,GAAG,CAAC,OAAO,EAAE,CAAC;QAC7B,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC;IAC1B,CAAC;IACD,IAAI,GAAG,CAAC,YAAY,KAAK,OAAO,IAAI,GAAG,CAAC,UAAU,EAAE,CAAC;QACpD,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,CAAC,UAAU,CAAC,CAAC;IACtC,CAAC;SAAM,IAAI,GAAG,CAAC,YAAY,KAAK,MAAM,IAAI,GAAG,CAAC,UAAU,EAAE,CAAC;QAC1D,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,UAAU,CAAC,CAAC;IACrC,CAAC;SAAM,IAAI,GAAG,CAAC,YAAY,KAAK,MAAM,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,CAAC;QAC3D,+CAA+C;QAC/C,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACrB,CAAC;IACD,IAAI,GAAG,CAAC,YAAY,EAAE,CAAC;QACtB,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,GAAG,CAAC,YAAY,CAAC,CAAC;IAC3C,CAAC;IACD,OAAO,IAAI,CAAC;AACb,CAAC;AAED,MAAM,UAAU,SAAS;IACxB,OAAO,OAAO,CAAC,GAAG,CAAC,OAAO,IAAI,KAAK,CAAC;AACrC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,IAAc,EAAE,GAAW;IACvD,OAAO,IAAI,OAAO,CAAC,CAAC,cAAc,EAAE,aAAa,EAAE,EAAE;QACpD,MAAM,KAAK,GAAG,KAAK,CAAC,SAAS,EAAE,EAAE,IAAI,EAAE;YACtC,GAAG;YACH,KAAK,EAAE,SAAS;SAChB,CAAC,CAAC;QACH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAA0B,EAAE,EAAE;YAChD,IAAI,GAAG,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;gBAC3B,aAAa,CACZ,IAAI,KAAK,CACR,2IAA2I,CAC3I,CACD,CAAC;gBACF,OAAO;YACR,CAAC;YACD,aAAa,CAAC,GAAG,CAAC,CAAC;QACpB,CAAC,CAAC,CAAC;QACH,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE;YACjC,IAAI,MAAM,EAAE,CAAC;gBACZ,cAAc,CAAC,GAAG,GAAG,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YACjF,CAAC;iBAAM,CAAC;gBACP,cAAc,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC;YAC3B,CAAC;QACF,CAAC,CAAC,CAAC;IACJ,CAAC,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,31 @@
1
+ import { SfCommand } from '@salesforce/sf-plugins-core';
2
+ type ResultFormat = 'human' | 'tap' | 'junit' | 'json';
3
+ type TestLevel = 'RunLocalTests' | 'RunAllTestsInOrg' | 'RunSpecifiedTests';
4
+ export type AerApexRunTestResult = {
5
+ stagedDir: string;
6
+ filters: string[];
7
+ exitCode: number;
8
+ };
9
+ export default class AerApexRunTest extends SfCommand<AerApexRunTestResult> {
10
+ static readonly summary: string;
11
+ static readonly description: string;
12
+ static readonly examples: string[];
13
+ static readonly flags: {
14
+ 'target-org': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
15
+ 'test-level': import("@oclif/core/interfaces").OptionFlag<TestLevel | undefined, import("@oclif/core/interfaces").CustomOptions>;
16
+ 'class-names': import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
17
+ 'suite-names': import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
18
+ tests: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
19
+ 'result-format': import("@oclif/core/interfaces").OptionFlag<ResultFormat, import("@oclif/core/interfaces").CustomOptions>;
20
+ 'output-dir': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
21
+ 'code-coverage': import("@oclif/core/interfaces").BooleanFlag<boolean>;
22
+ 'detailed-coverage': import("@oclif/core/interfaces").BooleanFlag<boolean>;
23
+ synchronous: import("@oclif/core/interfaces").BooleanFlag<boolean>;
24
+ wait: import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
25
+ 'poll-interval': import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
26
+ concise: import("@oclif/core/interfaces").BooleanFlag<boolean>;
27
+ 'api-version': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
28
+ };
29
+ run(): Promise<AerApexRunTestResult>;
30
+ }
31
+ export {};
@@ -0,0 +1,177 @@
1
+ import { mkdir } from 'node:fs/promises';
2
+ import { join, resolve } from 'node:path';
3
+ import { SfCommand, Flags } from '@salesforce/sf-plugins-core';
4
+ import { Messages, SfProject } from '@salesforce/core';
5
+ import { stageSource } from '../../../../staging.js';
6
+ import { buildAerArgs, runAer } from '../../../../aer.js';
7
+ Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
8
+ const messages = Messages.loadMessages('@octoberswimmer/aer-sf-plugin', 'aer.apex.run.test');
9
+ export default class AerApexRunTest extends SfCommand {
10
+ static summary = messages.getMessage('summary');
11
+ static description = messages.getMessage('description');
12
+ static examples = messages.getMessages('examples');
13
+ static flags = {
14
+ 'target-org': Flags.string({
15
+ char: 'o',
16
+ summary: messages.getMessage('flags.target-org.summary'),
17
+ description: messages.getMessage('flags.target-org.description'),
18
+ }),
19
+ 'test-level': Flags.custom({
20
+ options: ['RunLocalTests', 'RunAllTestsInOrg', 'RunSpecifiedTests'],
21
+ })({
22
+ char: 'l',
23
+ summary: messages.getMessage('flags.test-level.summary'),
24
+ }),
25
+ 'class-names': Flags.string({
26
+ char: 'n',
27
+ multiple: true,
28
+ summary: messages.getMessage('flags.class-names.summary'),
29
+ }),
30
+ 'suite-names': Flags.string({
31
+ char: 's',
32
+ multiple: true,
33
+ summary: messages.getMessage('flags.suite-names.summary'),
34
+ }),
35
+ tests: Flags.string({
36
+ char: 't',
37
+ multiple: true,
38
+ summary: messages.getMessage('flags.tests.summary'),
39
+ }),
40
+ 'result-format': Flags.custom({
41
+ options: ['human', 'tap', 'junit', 'json'],
42
+ })({
43
+ char: 'r',
44
+ summary: messages.getMessage('flags.result-format.summary'),
45
+ default: 'human',
46
+ }),
47
+ 'output-dir': Flags.directory({
48
+ char: 'd',
49
+ summary: messages.getMessage('flags.output-dir.summary'),
50
+ }),
51
+ 'code-coverage': Flags.boolean({
52
+ char: 'c',
53
+ summary: messages.getMessage('flags.code-coverage.summary'),
54
+ }),
55
+ 'detailed-coverage': Flags.boolean({
56
+ char: 'v',
57
+ summary: messages.getMessage('flags.detailed-coverage.summary'),
58
+ dependsOn: ['code-coverage'],
59
+ }),
60
+ synchronous: Flags.boolean({
61
+ char: 'y',
62
+ summary: messages.getMessage('flags.synchronous.summary'),
63
+ }),
64
+ wait: Flags.integer({
65
+ char: 'w',
66
+ summary: messages.getMessage('flags.wait.summary'),
67
+ }),
68
+ 'poll-interval': Flags.integer({
69
+ char: 'i',
70
+ summary: messages.getMessage('flags.poll-interval.summary'),
71
+ }),
72
+ concise: Flags.boolean({
73
+ summary: messages.getMessage('flags.concise.summary'),
74
+ }),
75
+ 'api-version': Flags.string({
76
+ summary: messages.getMessage('flags.api-version.summary'),
77
+ }),
78
+ };
79
+ async run() {
80
+ const { flags } = await this.parse(AerApexRunTest);
81
+ const project = await SfProject.resolve();
82
+ const projectRoot = project.getPath();
83
+ const contents = project.getSfProjectJson().getContents();
84
+ const packageDirectories = contents.packageDirectories ?? [];
85
+ if (packageDirectories.length === 0) {
86
+ throw messages.createError('error.noPackageDirectories');
87
+ }
88
+ const filters = buildFilters(flags['class-names'], flags.tests);
89
+ const testLevel = flags['test-level'];
90
+ if (testLevel === 'RunAllTestsInOrg') {
91
+ this.warn(messages.getMessage('warn.testLevelInOrgUnsupported'));
92
+ }
93
+ if (flags['suite-names'] && flags['suite-names'].length > 0) {
94
+ this.warn(messages.getMessage('warn.suiteNamesUnsupported'));
95
+ }
96
+ const ignoredFlags = [];
97
+ if (flags['target-org'])
98
+ ignoredFlags.push('--target-org');
99
+ if (flags.synchronous)
100
+ ignoredFlags.push('--synchronous');
101
+ if (flags.wait !== undefined)
102
+ ignoredFlags.push('--wait');
103
+ if (flags['poll-interval'] !== undefined)
104
+ ignoredFlags.push('--poll-interval');
105
+ if (flags['api-version'])
106
+ ignoredFlags.push('--api-version');
107
+ if (flags['detailed-coverage'])
108
+ ignoredFlags.push('--detailed-coverage');
109
+ if (flags.concise)
110
+ ignoredFlags.push('--concise');
111
+ if (flags['result-format'] === 'tap')
112
+ ignoredFlags.push('--result-format=tap (using human instead)');
113
+ if (ignoredFlags.length > 0) {
114
+ this.warn(messages.getMessage('warn.ignoredFlags', [ignoredFlags.join(', ')]));
115
+ }
116
+ const staged = await stageSource({
117
+ projectRoot,
118
+ packageDirectories,
119
+ replacements: contents.replacements,
120
+ });
121
+ let resultFile;
122
+ let coverageFile;
123
+ const effectiveFormat = flags['result-format'] === 'junit'
124
+ ? 'junit'
125
+ : flags['result-format'] === 'json'
126
+ ? 'json'
127
+ : 'human';
128
+ if (flags['output-dir']) {
129
+ const outDir = resolve(projectRoot, flags['output-dir']);
130
+ await mkdir(outDir, { recursive: true });
131
+ if (effectiveFormat === 'junit') {
132
+ resultFile = join(outDir, 'test-result.xml');
133
+ }
134
+ else if (effectiveFormat === 'json') {
135
+ resultFile = join(outDir, 'test-result.json');
136
+ }
137
+ if (flags['code-coverage']) {
138
+ coverageFile = join(outDir, 'test-result-codecoverage.json');
139
+ }
140
+ }
141
+ const args = buildAerArgs({
142
+ stagedDir: staged.dir,
143
+ filters,
144
+ resultFormat: effectiveFormat,
145
+ resultFile,
146
+ coverageFile,
147
+ verbose: flags['detailed-coverage'] ?? false,
148
+ });
149
+ let exitCode = 0;
150
+ try {
151
+ exitCode = await runAer(args, projectRoot);
152
+ }
153
+ finally {
154
+ await staged.cleanup();
155
+ }
156
+ if (exitCode !== 0 && !this.jsonEnabled()) {
157
+ this.warn(messages.getMessage('warn.aerExit', [String(exitCode)]));
158
+ }
159
+ return {
160
+ stagedDir: staged.dir,
161
+ filters,
162
+ exitCode,
163
+ };
164
+ }
165
+ }
166
+ function buildFilters(classNames, tests) {
167
+ const out = [];
168
+ for (const cn of classNames ?? []) {
169
+ // aer matches test names like "ClassName.methodName"; anchor on class name with a glob.
170
+ out.push(`${cn}.*`);
171
+ }
172
+ for (const t of tests ?? []) {
173
+ out.push(t);
174
+ }
175
+ return out;
176
+ }
177
+ //# sourceMappingURL=test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test.js","sourceRoot":"","sources":["../../../../../src/commands/aer/apex/run/test.ts"],"names":[],"mappings":"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"}
package/lib/index.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ declare const _default: {};
2
+ export default _default;
package/lib/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export default {};
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,eAAe,EAAE,CAAC"}
@@ -0,0 +1,20 @@
1
+ export type Replacement = {
2
+ glob: string;
3
+ stringToReplace?: string;
4
+ regexToReplace?: string;
5
+ replaceWithFile?: string;
6
+ replaceWithEnv?: string;
7
+ };
8
+ export type PackageDirectory = {
9
+ path: string;
10
+ };
11
+ export type StagingResult = {
12
+ dir: string;
13
+ cleanup: () => Promise<void>;
14
+ fileCount: number;
15
+ };
16
+ export declare function stageSource(opts: {
17
+ projectRoot: string;
18
+ packageDirectories: PackageDirectory[];
19
+ replacements?: Replacement[];
20
+ }): Promise<StagingResult>;
package/lib/staging.js ADDED
@@ -0,0 +1,163 @@
1
+ import { mkdir, mkdtemp, readFile, readdir, writeFile, rm } from 'node:fs/promises';
2
+ import { rmSync } from 'node:fs';
3
+ import { dirname, join, relative, resolve, sep } from 'node:path';
4
+ import { tmpdir } from 'node:os';
5
+ import { minimatch } from 'minimatch';
6
+ // Metadata file extensions whose basename uniquely identifies the component
7
+ // (i.e. the same basename in different packageDirectories is the same
8
+ // component and should be deduped). This excludes folder-scoped types like
9
+ // CustomField (`Foo__c.field-meta.xml` under `objects/<Obj>/fields/` — the
10
+ // field's identity is parent-object + name, not basename alone).
11
+ function getDedupableExtension(name) {
12
+ if (name.endsWith('.cls-meta.xml'))
13
+ return '.cls-meta.xml';
14
+ if (name.endsWith('.trigger-meta.xml'))
15
+ return '.trigger-meta.xml';
16
+ if (name.endsWith('.cls'))
17
+ return '.cls';
18
+ if (name.endsWith('.trigger'))
19
+ return '.trigger';
20
+ if (name.endsWith('.md-meta.xml'))
21
+ return '.md-meta.xml';
22
+ return undefined;
23
+ }
24
+ async function walk(root) {
25
+ const out = [];
26
+ const stack = [root];
27
+ while (stack.length) {
28
+ const dir = stack.pop();
29
+ let entries;
30
+ try {
31
+ entries = await readdir(dir, { withFileTypes: true });
32
+ }
33
+ catch {
34
+ continue;
35
+ }
36
+ for (const entry of entries) {
37
+ const full = join(dir, entry.name);
38
+ if (entry.isDirectory()) {
39
+ stack.push(full);
40
+ }
41
+ else if (entry.isFile()) {
42
+ out.push(full);
43
+ }
44
+ }
45
+ }
46
+ return out;
47
+ }
48
+ function matchesGlob(glob, relPath) {
49
+ const normalized = relPath.split(sep).join('/');
50
+ return minimatch(normalized, glob, { matchBase: true, dot: true });
51
+ }
52
+ export async function stageSource(opts) {
53
+ const { projectRoot, packageDirectories, replacements = [] } = opts;
54
+ const tempDir = await mkdtemp(join(tmpdir(), 'aer-stage-'));
55
+ const allFiles = [];
56
+ for (const pd of packageDirectories) {
57
+ const absPdRoot = resolve(projectRoot, pd.path);
58
+ const files = await walk(absPdRoot);
59
+ for (const f of files) {
60
+ allFiles.push({
61
+ fullPath: f,
62
+ relativeToProject: relative(projectRoot, f),
63
+ });
64
+ }
65
+ }
66
+ // Sort by full path so the alphabetically-last entry wins on collision.
67
+ allFiles.sort((a, b) => (a.fullPath < b.fullPath ? -1 : a.fullPath > b.fullPath ? 1 : 0));
68
+ // Files whose basename uniquely identifies their metadata component
69
+ // (Apex classes/triggers + CustomMetadata records) must be globally
70
+ // unique. The alphabetically-last full path wins, matching
71
+ // `sf project deploy start`.
72
+ const dedupedByKey = new Map();
73
+ const passthrough = [];
74
+ for (const f of allFiles) {
75
+ const base = f.relativeToProject.split(sep).pop();
76
+ if (getDedupableExtension(base)) {
77
+ dedupedByKey.set(base.toLowerCase(), f);
78
+ }
79
+ else {
80
+ passthrough.push(f);
81
+ }
82
+ }
83
+ const filesToStage = [...dedupedByKey.values(), ...passthrough];
84
+ const fileValueCache = new Map();
85
+ const compiledReplacements = await Promise.all(replacements.map(async (r) => {
86
+ let value;
87
+ if (r.replaceWithFile) {
88
+ const abs = resolve(projectRoot, r.replaceWithFile);
89
+ if (!fileValueCache.has(abs)) {
90
+ const content = await readFile(abs, 'utf8');
91
+ // Match @salesforce/source-deploy-retrieve: trim a single trailing newline.
92
+ const trimmed = content.endsWith('\r\n')
93
+ ? content.slice(0, -2)
94
+ : content.endsWith('\n')
95
+ ? content.slice(0, -1)
96
+ : content;
97
+ fileValueCache.set(abs, trimmed);
98
+ }
99
+ value = fileValueCache.get(abs);
100
+ }
101
+ else if (r.replaceWithEnv) {
102
+ const env = process.env[r.replaceWithEnv];
103
+ if (env === undefined) {
104
+ throw new Error(`sfdx-project.json replacement references missing environment variable: ${r.replaceWithEnv}`);
105
+ }
106
+ value = env;
107
+ }
108
+ else {
109
+ throw new Error('sfdx-project.json replacement entry has neither replaceWithFile nor replaceWithEnv');
110
+ }
111
+ return { ...r, value };
112
+ }));
113
+ let fileCount = 0;
114
+ for (const f of filesToStage) {
115
+ const target = join(tempDir, f.relativeToProject);
116
+ await mkdir(dirname(target), { recursive: true });
117
+ const matching = compiledReplacements.filter((r) => matchesGlob(r.glob, f.relativeToProject));
118
+ if (matching.length === 0) {
119
+ const buf = await readFile(f.fullPath);
120
+ await writeFile(target, buf);
121
+ }
122
+ else {
123
+ let text = await readFile(f.fullPath, 'utf8');
124
+ for (const r of matching) {
125
+ if (r.stringToReplace !== undefined) {
126
+ text = text.split(r.stringToReplace).join(r.value);
127
+ }
128
+ else if (r.regexToReplace !== undefined) {
129
+ text = text.replace(new RegExp(r.regexToReplace, 'g'), r.value);
130
+ }
131
+ }
132
+ await writeFile(target, text);
133
+ }
134
+ fileCount++;
135
+ }
136
+ const cleanup = async () => {
137
+ try {
138
+ await rm(tempDir, { recursive: true, force: true });
139
+ }
140
+ catch {
141
+ /* ignore */
142
+ }
143
+ };
144
+ const syncCleanup = () => {
145
+ try {
146
+ rmSync(tempDir, { recursive: true, force: true });
147
+ }
148
+ catch {
149
+ /* ignore */
150
+ }
151
+ };
152
+ process.once('exit', syncCleanup);
153
+ process.once('SIGINT', () => {
154
+ syncCleanup();
155
+ process.exit(130);
156
+ });
157
+ process.once('SIGTERM', () => {
158
+ syncCleanup();
159
+ process.exit(143);
160
+ });
161
+ return { dir: tempDir, cleanup, fileCount };
162
+ }
163
+ //# sourceMappingURL=staging.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"staging.js","sourceRoot":"","sources":["../src/staging.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,kBAAkB,CAAC;AACpF,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAClE,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAkBtC,4EAA4E;AAC5E,sEAAsE;AACtE,2EAA2E;AAC3E,2EAA2E;AAC3E,iEAAiE;AACjE,SAAS,qBAAqB,CAAC,IAAY;IAC1C,IAAI,IAAI,CAAC,QAAQ,CAAC,eAAe,CAAC;QAAE,OAAO,eAAe,CAAC;IAC3D,IAAI,IAAI,CAAC,QAAQ,CAAC,mBAAmB,CAAC;QAAE,OAAO,mBAAmB,CAAC;IACnE,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;QAAE,OAAO,MAAM,CAAC;IACzC,IAAI,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC;QAAE,OAAO,UAAU,CAAC;IACjD,IAAI,IAAI,CAAC,QAAQ,CAAC,cAAc,CAAC;QAAE,OAAO,cAAc,CAAC;IACzD,OAAO,SAAS,CAAC;AAClB,CAAC;AAED,KAAK,UAAU,IAAI,CAAC,IAAY;IAC/B,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,MAAM,KAAK,GAAa,CAAC,IAAI,CAAC,CAAC;IAC/B,OAAO,KAAK,CAAC,MAAM,EAAE,CAAC;QACrB,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,EAAG,CAAC;QACzB,IAAI,OAAO,CAAC;QACZ,IAAI,CAAC;YACJ,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;QACvD,CAAC;QAAC,MAAM,CAAC;YACR,SAAS;QACV,CAAC;QACD,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC7B,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;YACnC,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;gBACzB,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAClB,CAAC;iBAAM,IAAI,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC;gBAC3B,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAChB,CAAC;QACF,CAAC;IACF,CAAC;IACD,OAAO,GAAG,CAAC;AACZ,CAAC;AAED,SAAS,WAAW,CAAC,IAAY,EAAE,OAAe;IACjD,MAAM,UAAU,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAChD,OAAO,SAAS,CAAC,UAAU,EAAE,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC;AACpE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,IAIjC;IACA,MAAM,EAAE,WAAW,EAAE,kBAAkB,EAAE,YAAY,GAAG,EAAE,EAAE,GAAG,IAAI,CAAC;IACpE,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,YAAY,CAAC,CAAC,CAAC;IAG5D,MAAM,QAAQ,GAAiB,EAAE,CAAC;IAClC,KAAK,MAAM,EAAE,IAAI,kBAAkB,EAAE,CAAC;QACrC,MAAM,SAAS,GAAG,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC;QAChD,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,CAAC;QACpC,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;YACvB,QAAQ,CAAC,IAAI,CAAC;gBACb,QAAQ,EAAE,CAAC;gBACX,iBAAiB,EAAE,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAC;aAC3C,CAAC,CAAC;QACJ,CAAC;IACF,CAAC;IAED,wEAAwE;IACxE,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAE1F,oEAAoE;IACpE,oEAAoE;IACpE,2DAA2D;IAC3D,6BAA6B;IAC7B,MAAM,YAAY,GAAG,IAAI,GAAG,EAAsB,CAAC;IACnD,MAAM,WAAW,GAAiB,EAAE,CAAC;IACrC,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;QAC1B,MAAM,IAAI,GAAG,CAAC,CAAC,iBAAiB,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAG,CAAC;QACnD,IAAI,qBAAqB,CAAC,IAAI,CAAC,EAAE,CAAC;YACjC,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC,CAAC;QACzC,CAAC;aAAM,CAAC;YACP,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACrB,CAAC;IACF,CAAC;IACD,MAAM,YAAY,GAAiB,CAAC,GAAG,YAAY,CAAC,MAAM,EAAE,EAAE,GAAG,WAAW,CAAC,CAAC;IAE9E,MAAM,cAAc,GAAG,IAAI,GAAG,EAAkB,CAAC;IACjD,MAAM,oBAAoB,GAAG,MAAM,OAAO,CAAC,GAAG,CAC7C,YAAY,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE;QAC5B,IAAI,KAAa,CAAC;QAClB,IAAI,CAAC,CAAC,eAAe,EAAE,CAAC;YACvB,MAAM,GAAG,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC,eAAe,CAAC,CAAC;YACpD,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;gBAC9B,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;gBAC5C,4EAA4E;gBAC5E,MAAM,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC;oBACvC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;oBACtB,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC;wBACvB,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;wBACtB,CAAC,CAAC,OAAO,CAAC;gBACZ,cAAc,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;YAClC,CAAC;YACD,KAAK,GAAG,cAAc,CAAC,GAAG,CAAC,GAAG,CAAE,CAAC;QAClC,CAAC;aAAM,IAAI,CAAC,CAAC,cAAc,EAAE,CAAC;YAC7B,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC;YAC1C,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;gBACvB,MAAM,IAAI,KAAK,CACd,0EAA0E,CAAC,CAAC,cAAc,EAAE,CAC5F,CAAC;YACH,CAAC;YACD,KAAK,GAAG,GAAG,CAAC;QACb,CAAC;aAAM,CAAC;YACP,MAAM,IAAI,KAAK,CACd,oFAAoF,CACpF,CAAC;QACH,CAAC;QACD,OAAO,EAAE,GAAG,CAAC,EAAE,KAAK,EAAE,CAAC;IACxB,CAAC,CAAC,CACF,CAAC;IAEF,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,KAAK,MAAM,CAAC,IAAI,YAAY,EAAE,CAAC;QAC9B,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,iBAAiB,CAAC,CAAC;QAClD,MAAM,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAElD,MAAM,QAAQ,GAAG,oBAAoB,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC;QAC9F,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC3B,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;YACvC,MAAM,SAAS,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAC9B,CAAC;aAAM,CAAC;YACP,IAAI,IAAI,GAAG,MAAM,QAAQ,CAAC,CAAC,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;YAC9C,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;gBAC1B,IAAI,CAAC,CAAC,eAAe,KAAK,SAAS,EAAE,CAAC;oBACrC,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;gBACpD,CAAC;qBAAM,IAAI,CAAC,CAAC,cAAc,KAAK,SAAS,EAAE,CAAC;oBAC3C,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,cAAc,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC;gBACjE,CAAC;YACF,CAAC;YACD,MAAM,SAAS,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QAC/B,CAAC;QACD,SAAS,EAAE,CAAC;IACb,CAAC;IAED,MAAM,OAAO,GAAG,KAAK,IAAmB,EAAE;QACzC,IAAI,CAAC;YACJ,MAAM,EAAE,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QACrD,CAAC;QAAC,MAAM,CAAC;YACR,YAAY;QACb,CAAC;IACF,CAAC,CAAC;IAEF,MAAM,WAAW,GAAG,GAAS,EAAE;QAC9B,IAAI,CAAC;YACJ,MAAM,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QACnD,CAAC;QAAC,MAAM,CAAC;YACR,YAAY;QACb,CAAC;IACF,CAAC,CAAC;IACF,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IAClC,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,EAAE;QAC3B,WAAW,EAAE,CAAC;QACd,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACnB,CAAC,CAAC,CAAC;IACH,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,EAAE;QAC5B,WAAW,EAAE,CAAC;QACd,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACnB,CAAC,CAAC,CAAC;IAEH,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC;AAC7C,CAAC"}
@@ -0,0 +1,118 @@
1
+ # summary
2
+
3
+ Run Apex tests locally using aer.
4
+
5
+ # description
6
+
7
+ Run Apex tests against a local sandbox using aer instead of submitting them to a Salesforce org.
8
+
9
+ This command accepts the same flags as `sf apex run test` so it can be used as a drop-in replacement, but flags that only make sense when running against a real org (--target-org, --wait, --poll-interval, --synchronous, --api-version) are accepted for compatibility and otherwise ignored.
10
+
11
+ Source is staged into a temporary directory before invoking aer. The staging step reproduces two behaviors of `sf project deploy start` that aer does not implement natively:
12
+
13
+ 1. When the same Apex class name appears in more than one packageDirectories entry, the copy whose full path sorts alphabetically last wins.
14
+ 2. The `replacements` configuration in sfdx-project.json (stringToReplace / regexToReplace + replaceWithFile / replaceWithEnv) is applied to file contents during staging.
15
+
16
+ The `aer` binary (https://github.com/octoberswimmer/aer-dist) must be installed and on PATH. Set the `AER_BIN` environment variable to override the path.
17
+
18
+ # examples
19
+
20
+ - Run every Apex test in the project:
21
+
22
+ <%= config.bin %> <%= command.id %>
23
+
24
+ - Run a specific Apex test class:
25
+
26
+ <%= config.bin %> <%= command.id %> --class-names MyClassTest
27
+
28
+ - Run a single test method:
29
+
30
+ <%= config.bin %> <%= command.id %> --tests MyClassTest.testCoolFeature
31
+
32
+ - Write JUnit results to a directory:
33
+
34
+ <%= config.bin %> <%= command.id %> --result-format junit --output-dir test-results
35
+
36
+ - Collect code coverage:
37
+
38
+ <%= config.bin %> <%= command.id %> --code-coverage --output-dir test-results
39
+
40
+ # flags.target-org.summary
41
+
42
+ Username or alias of the target org. Accepted for compatibility with `sf apex run test`; aer runs locally and ignores this flag.
43
+
44
+ # flags.target-org.description
45
+
46
+ aer does not connect to an org. This flag is parsed for drop-in compatibility but has no effect.
47
+
48
+ # flags.test-level.summary
49
+
50
+ Level of tests to run. RunLocalTests and RunSpecifiedTests run locally via aer; RunAllTestsInOrg is unsupported.
51
+
52
+ # flags.class-names.summary
53
+
54
+ Apex test class names to run; passed to aer as `--filter <ClassName>.*`. Can be repeated.
55
+
56
+ # flags.suite-names.summary
57
+
58
+ Apex test suite names to run. Not yet supported by this plugin; a warning is printed if used.
59
+
60
+ # flags.tests.summary
61
+
62
+ Specific Apex test methods to run (e.g. MyClassTest.testFoo). Passed to aer as `--filter`. Can be repeated.
63
+
64
+ # flags.result-format.summary
65
+
66
+ Format of the test results: human, junit, json. tap is accepted but falls back to human.
67
+
68
+ # flags.output-dir.summary
69
+
70
+ Directory to write test result files (test-result.xml for junit, test-result.json for json, test-result-codecoverage.json for coverage).
71
+
72
+ # flags.code-coverage.summary
73
+
74
+ Collect Apex code coverage. Output is written under --output-dir.
75
+
76
+ # flags.detailed-coverage.summary
77
+
78
+ Accepted for compatibility; aer's coverage output is not yet broken down per test.
79
+
80
+ # flags.synchronous.summary
81
+
82
+ Accepted for compatibility; aer always runs tests synchronously.
83
+
84
+ # flags.wait.summary
85
+
86
+ Accepted for compatibility; aer runs synchronously so there is nothing to wait for.
87
+
88
+ # flags.poll-interval.summary
89
+
90
+ Accepted for compatibility; aer does not poll an org.
91
+
92
+ # flags.concise.summary
93
+
94
+ Accepted for compatibility; not yet wired into aer's output.
95
+
96
+ # flags.api-version.summary
97
+
98
+ Accepted for compatibility; aer does not talk to the org API.
99
+
100
+ # warn.testLevelInOrgUnsupported
101
+
102
+ --test-level RunAllTestsInOrg is not supported when running tests locally. All tests in the project will be run.
103
+
104
+ # warn.suiteNamesUnsupported
105
+
106
+ --suite-names is not yet supported by this plugin; the flag was ignored. Use --class-names or --tests to select tests.
107
+
108
+ # warn.ignoredFlags
109
+
110
+ The following flags were accepted for compatibility but have no effect when running tests locally with aer: %s
111
+
112
+ # warn.aerExit
113
+
114
+ aer exited with non-zero status %s.
115
+
116
+ # error.noPackageDirectories
117
+
118
+ sfdx-project.json must contain at least one packageDirectories entry.
@@ -0,0 +1,201 @@
1
+ {
2
+ "commands": {
3
+ "aer:apex:run:test": {
4
+ "aliases": [],
5
+ "args": {},
6
+ "description": "Run Apex tests against a local sandbox using aer instead of submitting them to a Salesforce org.\n\nThis command accepts the same flags as `sf apex run test` so it can be used as a drop-in replacement, but flags that only make sense when running against a real org (--target-org, --wait, --poll-interval, --synchronous, --api-version) are accepted for compatibility and otherwise ignored.\n\nSource is staged into a temporary directory before invoking aer. The staging step reproduces two behaviors of `sf project deploy start` that aer does not implement natively:\n\n1. When the same Apex class name appears in more than one packageDirectories entry, the copy whose full path sorts alphabetically last wins.\n2. The `replacements` configuration in sfdx-project.json (stringToReplace / regexToReplace + replaceWithFile / replaceWithEnv) is applied to file contents during staging.\n\nThe `aer` binary (https://github.com/octoberswimmer/aer-dist) must be installed and on PATH. Set the `AER_BIN` environment variable to override the path.",
7
+ "examples": [
8
+ "Run every Apex test in the project:\n<%= config.bin %> <%= command.id %>",
9
+ "Run a specific Apex test class:\n<%= config.bin %> <%= command.id %> --class-names MyClassTest",
10
+ "Run a single test method:\n<%= config.bin %> <%= command.id %> --tests MyClassTest.testCoolFeature",
11
+ "Write JUnit results to a directory:\n<%= config.bin %> <%= command.id %> --result-format junit --output-dir test-results",
12
+ "Collect code coverage:\n<%= config.bin %> <%= command.id %> --code-coverage --output-dir test-results"
13
+ ],
14
+ "flags": {
15
+ "json": {
16
+ "description": "Format output as json.",
17
+ "helpGroup": "GLOBAL",
18
+ "name": "json",
19
+ "allowNo": false,
20
+ "type": "boolean"
21
+ },
22
+ "flags-dir": {
23
+ "helpGroup": "GLOBAL",
24
+ "name": "flags-dir",
25
+ "summary": "Import flag values from a directory.",
26
+ "hasDynamicHelp": false,
27
+ "multiple": false,
28
+ "type": "option"
29
+ },
30
+ "target-org": {
31
+ "char": "o",
32
+ "description": "aer does not connect to an org. This flag is parsed for drop-in compatibility but has no effect.",
33
+ "name": "target-org",
34
+ "summary": "Username or alias of the target org. Accepted for compatibility with `sf apex run test`; aer runs locally and ignores this flag.",
35
+ "hasDynamicHelp": false,
36
+ "multiple": false,
37
+ "type": "option"
38
+ },
39
+ "test-level": {
40
+ "char": "l",
41
+ "name": "test-level",
42
+ "summary": "Level of tests to run. RunLocalTests and RunSpecifiedTests run locally via aer; RunAllTestsInOrg is unsupported.",
43
+ "hasDynamicHelp": false,
44
+ "multiple": false,
45
+ "options": [
46
+ "RunLocalTests",
47
+ "RunAllTestsInOrg",
48
+ "RunSpecifiedTests"
49
+ ],
50
+ "type": "option"
51
+ },
52
+ "class-names": {
53
+ "char": "n",
54
+ "name": "class-names",
55
+ "summary": "Apex test class names to run; passed to aer as `--filter <ClassName>.*`. Can be repeated.",
56
+ "hasDynamicHelp": false,
57
+ "multiple": true,
58
+ "type": "option"
59
+ },
60
+ "suite-names": {
61
+ "char": "s",
62
+ "name": "suite-names",
63
+ "summary": "Apex test suite names to run. Not yet supported by this plugin; a warning is printed if used.",
64
+ "hasDynamicHelp": false,
65
+ "multiple": true,
66
+ "type": "option"
67
+ },
68
+ "tests": {
69
+ "char": "t",
70
+ "name": "tests",
71
+ "summary": "Specific Apex test methods to run (e.g. MyClassTest.testFoo). Passed to aer as `--filter`. Can be repeated.",
72
+ "hasDynamicHelp": false,
73
+ "multiple": true,
74
+ "type": "option"
75
+ },
76
+ "result-format": {
77
+ "char": "r",
78
+ "name": "result-format",
79
+ "summary": "Format of the test results: human, junit, json. tap is accepted but falls back to human.",
80
+ "default": "human",
81
+ "hasDynamicHelp": false,
82
+ "multiple": false,
83
+ "options": [
84
+ "human",
85
+ "tap",
86
+ "junit",
87
+ "json"
88
+ ],
89
+ "type": "option"
90
+ },
91
+ "output-dir": {
92
+ "char": "d",
93
+ "name": "output-dir",
94
+ "summary": "Directory to write test result files (test-result.xml for junit, test-result.json for json, test-result-codecoverage.json for coverage).",
95
+ "hasDynamicHelp": false,
96
+ "multiple": false,
97
+ "type": "option"
98
+ },
99
+ "code-coverage": {
100
+ "char": "c",
101
+ "name": "code-coverage",
102
+ "summary": "Collect Apex code coverage. Output is written under --output-dir.",
103
+ "allowNo": false,
104
+ "type": "boolean"
105
+ },
106
+ "detailed-coverage": {
107
+ "char": "v",
108
+ "dependsOn": [
109
+ "code-coverage"
110
+ ],
111
+ "name": "detailed-coverage",
112
+ "summary": "Accepted for compatibility; aer's coverage output is not yet broken down per test.",
113
+ "allowNo": false,
114
+ "type": "boolean"
115
+ },
116
+ "synchronous": {
117
+ "char": "y",
118
+ "name": "synchronous",
119
+ "summary": "Accepted for compatibility; aer always runs tests synchronously.",
120
+ "allowNo": false,
121
+ "type": "boolean"
122
+ },
123
+ "wait": {
124
+ "char": "w",
125
+ "name": "wait",
126
+ "summary": "Accepted for compatibility; aer runs synchronously so there is nothing to wait for.",
127
+ "hasDynamicHelp": false,
128
+ "multiple": false,
129
+ "type": "option"
130
+ },
131
+ "poll-interval": {
132
+ "char": "i",
133
+ "name": "poll-interval",
134
+ "summary": "Accepted for compatibility; aer does not poll an org.",
135
+ "hasDynamicHelp": false,
136
+ "multiple": false,
137
+ "type": "option"
138
+ },
139
+ "concise": {
140
+ "name": "concise",
141
+ "summary": "Accepted for compatibility; not yet wired into aer's output.",
142
+ "allowNo": false,
143
+ "type": "boolean"
144
+ },
145
+ "api-version": {
146
+ "name": "api-version",
147
+ "summary": "Accepted for compatibility; aer does not talk to the org API.",
148
+ "hasDynamicHelp": false,
149
+ "multiple": false,
150
+ "type": "option"
151
+ }
152
+ },
153
+ "hasDynamicHelp": false,
154
+ "hiddenAliases": [],
155
+ "id": "aer:apex:run:test",
156
+ "pluginAlias": "@octoberswimmer/aer-sf-plugin",
157
+ "pluginName": "@octoberswimmer/aer-sf-plugin",
158
+ "pluginType": "core",
159
+ "strict": true,
160
+ "summary": "Run Apex tests locally using aer.",
161
+ "enableJsonFlag": true,
162
+ "isESM": true,
163
+ "relativePath": [
164
+ "lib",
165
+ "commands",
166
+ "aer",
167
+ "apex",
168
+ "run",
169
+ "test.js"
170
+ ],
171
+ "aliasPermutations": [],
172
+ "permutations": [
173
+ "aer:apex:run:test",
174
+ "apex:aer:run:test",
175
+ "apex:run:aer:test",
176
+ "apex:run:test:aer",
177
+ "aer:run:apex:test",
178
+ "run:aer:apex:test",
179
+ "run:apex:aer:test",
180
+ "run:apex:test:aer",
181
+ "aer:run:test:apex",
182
+ "run:aer:test:apex",
183
+ "run:test:aer:apex",
184
+ "run:test:apex:aer",
185
+ "aer:apex:test:run",
186
+ "apex:aer:test:run",
187
+ "apex:test:aer:run",
188
+ "apex:test:run:aer",
189
+ "aer:test:apex:run",
190
+ "test:aer:apex:run",
191
+ "test:apex:aer:run",
192
+ "test:apex:run:aer",
193
+ "aer:test:run:apex",
194
+ "test:aer:run:apex",
195
+ "test:run:aer:apex",
196
+ "test:run:apex:aer"
197
+ ]
198
+ }
199
+ },
200
+ "version": "0.0.1"
201
+ }
package/package.json ADDED
@@ -0,0 +1,85 @@
1
+ {
2
+ "name": "@octoberswimmer/aer-sf-plugin",
3
+ "description": "Salesforce CLI plugin that runs Apex tests locally using aer.",
4
+ "version": "0.0.1",
5
+ "author": "Christian G. Warden",
6
+ "bugs": "https://github.com/octoberswimmer/aer-sf-plugin/issues",
7
+ "homepage": "https://github.com/octoberswimmer/aer-sf-plugin",
8
+ "repository": "octoberswimmer/aer-sf-plugin",
9
+ "type": "module",
10
+ "exports": "./lib/index.js",
11
+ "engines": {
12
+ "node": ">=18.0.0"
13
+ },
14
+ "main": "lib/index.js",
15
+ "files": [
16
+ "/bin",
17
+ "/lib",
18
+ "/messages",
19
+ "/oclif.manifest.json"
20
+ ],
21
+ "keywords": [
22
+ "aer",
23
+ "apex",
24
+ "force",
25
+ "salesforce",
26
+ "salesforcedx",
27
+ "sf",
28
+ "sf-plugin",
29
+ "sfdx",
30
+ "sfdx-plugin"
31
+ ],
32
+ "license": "BSD-3-Clause",
33
+ "oclif": {
34
+ "commands": "./lib/commands",
35
+ "bin": "sf",
36
+ "topicSeparator": " ",
37
+ "devPlugins": [
38
+ "@oclif/plugin-help"
39
+ ],
40
+ "topics": {
41
+ "aer": {
42
+ "description": "Run Apex code and tests locally using aer.",
43
+ "subtopics": {
44
+ "apex": {
45
+ "description": "Work with Apex code locally via aer.",
46
+ "subtopics": {
47
+ "run": {
48
+ "description": "Run Apex code or tests locally via aer."
49
+ }
50
+ }
51
+ }
52
+ }
53
+ }
54
+ },
55
+ "flexibleTaxonomy": true
56
+ },
57
+ "dependencies": {
58
+ "@oclif/core": "^4",
59
+ "@salesforce/core": "^8",
60
+ "@salesforce/sf-plugins-core": "^12",
61
+ "minimatch": "^9"
62
+ },
63
+ "devDependencies": {
64
+ "@types/chai": "^4.3.16",
65
+ "@types/mocha": "^10.0.6",
66
+ "@types/node": "^20.12.12",
67
+ "chai": "^4.4.1",
68
+ "mocha": "^10.4.0",
69
+ "oclif": "^4.14.0",
70
+ "ts-node": "^10.9.2",
71
+ "typescript": "^5.4.5"
72
+ },
73
+ "scripts": {
74
+ "build": "tsc -p .",
75
+ "compile": "tsc -p . --pretty --incremental",
76
+ "clean": "rm -rf lib oclif.manifest.json .tsbuildinfo",
77
+ "prepack": "yarn build && oclif manifest && oclif readme",
78
+ "postpack": "rm -f oclif.manifest.json",
79
+ "test": "mocha \"test/**/*.test.ts\"",
80
+ "version": "oclif readme"
81
+ },
82
+ "publishConfig": {
83
+ "access": "public"
84
+ }
85
+ }