@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 +138 -0
- package/bin/dev.js +8 -0
- package/bin/run.js +9 -0
- package/lib/aer.d.ts +12 -0
- package/lib/aer.js +48 -0
- package/lib/aer.js.map +1 -0
- package/lib/commands/aer/apex/run/test.d.ts +31 -0
- package/lib/commands/aer/apex/run/test.js +177 -0
- package/lib/commands/aer/apex/run/test.js.map +1 -0
- package/lib/index.d.ts +2 -0
- package/lib/index.js +2 -0
- package/lib/index.js.map +1 -0
- package/lib/staging.d.ts +20 -0
- package/lib/staging.js +163 -0
- package/lib/staging.js.map +1 -0
- package/messages/aer.apex.run.test.md +118 -0
- package/oclif.manifest.json +201 -0
- package/package.json +85 -0
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
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
package/lib/index.js
ADDED
package/lib/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,eAAe,EAAE,CAAC"}
|
package/lib/staging.d.ts
ADDED
|
@@ -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":"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"}
|
|
@@ -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
|
+
}
|