agentic-workflow-manager 8.1.4 → 8.1.6
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/dist/src/commands/preflight/checks.js +70 -1
- package/dist/src/commands/preflight/index.js +6 -5
- package/dist/src/commands/sensors/changed.js +15 -0
- package/dist/src/commands/sensors/compatibility/contract.js +17 -3
- package/dist/src/commands/sensors/compatibility/manifest.js +16 -4
- package/dist/src/commands/sensors/compatibility/timeout.js +21 -0
- package/dist/src/commands/sensors/exec.js +3 -1
- package/dist/src/commands/sensors/index.js +5 -13
- package/dist/src/commands/sensors/init.js +1 -0
- package/dist/src/commands/sensors/prepare.js +140 -0
- package/dist/src/commands/sensors/result.js +140 -0
- package/dist/src/commands/sensors/run.js +56 -335
- package/dist/src/commands/sensors/status.js +93 -21
- package/dist/src/commands/sensors/verdict.js +27 -0
- package/dist/tests/commands/preflight/preflight.test.js +50 -0
- package/dist/tests/commands/sensors/baseline.test.js +14 -0
- package/dist/tests/commands/sensors/changed-windows.test.js +3 -0
- package/dist/tests/commands/sensors/compatibility/contract.test.js +51 -0
- package/dist/tests/commands/sensors/compatibility/manifest.test.js +27 -11
- package/dist/tests/commands/sensors/compatibility/probe.test.js +13 -3
- package/dist/tests/commands/sensors/exec-fixtures.js +1 -1
- package/dist/tests/commands/sensors/exec.test.js +36 -0
- package/dist/tests/commands/sensors/index.test.js +46 -8
- package/dist/tests/commands/sensors/init.test.js +30 -1
- package/dist/tests/commands/sensors/prepare.test.js +88 -0
- package/dist/tests/commands/sensors/router.test.js +15 -1
- package/dist/tests/commands/sensors/run-changed.test.js +4 -4
- package/dist/tests/commands/sensors/run.test.js +29 -0
- package/dist/tests/commands/sensors/status-windows.test.js +1 -1
- package/dist/tests/commands/sensors/status.test.js +52 -20
- package/dist/tests/integration/preflight-json-pipe.e2e.test.js +79 -3
- package/dist/tests/integration/sensor-compatibility.e2e.test.js +40 -1
- package/dist/tests/structural/sensor-documentation-contract.test.js +33 -5
- package/dist/tests/structural/support-matrix-is-current.test.js +25 -0
- package/package.json +1 -1
|
@@ -8,7 +8,10 @@ const fs_1 = __importDefault(require("fs"));
|
|
|
8
8
|
const path_1 = __importDefault(require("path"));
|
|
9
9
|
const paths_1 = require("../../core/paths");
|
|
10
10
|
const manifest_1 = require("./compatibility/manifest");
|
|
11
|
-
const
|
|
11
|
+
const contract_1 = require("./compatibility/contract");
|
|
12
|
+
const discovery_1 = require("./compatibility/discovery");
|
|
13
|
+
const pack_source_1 = require("./compatibility/pack-source");
|
|
14
|
+
const resolve_1 = require("./compatibility/resolve");
|
|
12
15
|
/** First non-flag token after `npx` — the tool the command actually runs. */
|
|
13
16
|
function npxTool(parts) {
|
|
14
17
|
for (let i = 1; i < parts.length; i++) {
|
|
@@ -55,6 +58,83 @@ function checkCmd(cmd, cwd) {
|
|
|
55
58
|
}
|
|
56
59
|
return configCheck(parts, cwd) ?? { ok: true, detail: bin };
|
|
57
60
|
}
|
|
61
|
+
/** Check a v2 command's declared local prerequisite without invoking it. */
|
|
62
|
+
function checkStructuredCommand(command, cwd, assets = []) {
|
|
63
|
+
const missingAsset = assets.find(asset => !fs_1.default.existsSync(path_1.default.join(cwd, asset)));
|
|
64
|
+
if (missingAsset)
|
|
65
|
+
return { ok: false, detail: `missing config: ${missingAsset}` };
|
|
66
|
+
if (command.resolution === 'node-modules-bin') {
|
|
67
|
+
const binary = path_1.default.join(cwd, 'node_modules', '.bin', command.executable);
|
|
68
|
+
const present = fs_1.default.existsSync(binary)
|
|
69
|
+
|| fs_1.default.existsSync(`${binary}.cmd`)
|
|
70
|
+
|| fs_1.default.existsSync(`${binary}.exe`);
|
|
71
|
+
return present
|
|
72
|
+
? { ok: true, detail: `${command.executable} (node_modules/.bin)` }
|
|
73
|
+
: { ok: false, detail: `${command.executable} not installed locally` };
|
|
74
|
+
}
|
|
75
|
+
if (command.resolution === 'python-environment') {
|
|
76
|
+
if (!command.pythonEnvironmentRoot)
|
|
77
|
+
return { ok: false, detail: `${command.executable} has no selected Python environment` };
|
|
78
|
+
const root = path_1.default.join(cwd, command.pythonEnvironmentRoot);
|
|
79
|
+
const candidates = process.platform === 'win32'
|
|
80
|
+
? [path_1.default.join(root, 'Scripts', `${command.executable}.exe`), path_1.default.join(root, 'Scripts', `${command.executable}.cmd`)]
|
|
81
|
+
: [path_1.default.join(root, 'bin', command.executable)];
|
|
82
|
+
return candidates.some(candidate => fs_1.default.existsSync(candidate))
|
|
83
|
+
? { ok: true, detail: `${command.executable} (${command.pythonEnvironmentRoot})` }
|
|
84
|
+
: { ok: false, detail: `${command.executable} not found in ${command.pythonEnvironmentRoot}` };
|
|
85
|
+
}
|
|
86
|
+
return (0, paths_1.resolveOnPath)(command.executable)
|
|
87
|
+
? { ok: true, detail: command.executable }
|
|
88
|
+
: { ok: false, detail: `${command.executable} not found in PATH` };
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Re-evaluate only locally discoverable v2 compatibility evidence. Unlike the
|
|
92
|
+
* execution path, status never runs a compatibility probe: probes such as
|
|
93
|
+
* `eslint --print-config` are process execution and would turn this command
|
|
94
|
+
* into a health check. A selected variant still proves that its current tool
|
|
95
|
+
* and runtime ranges are compatible; its probe state remains unverifiable.
|
|
96
|
+
*/
|
|
97
|
+
function resolveStaticV2Compatibility(cwd, manifest) {
|
|
98
|
+
const source = manifest.registryRoot === undefined
|
|
99
|
+
? (0, pack_source_1.resolvePackSource)(manifest.pack)
|
|
100
|
+
: (0, pack_source_1.resolvePackSource)(manifest.pack, { registries: [{ name: 'manifest-provenance', remote: 'local', contentRoot: manifest.registryRoot }] });
|
|
101
|
+
const parsed = (0, contract_1.parseSensorPack)(JSON.parse(source.content), source.path);
|
|
102
|
+
if (parsed.kind !== 'v2')
|
|
103
|
+
throw new Error(`sensor pack "${manifest.pack}" does not provide a v2 compatibility contract`);
|
|
104
|
+
const evidence = (0, discovery_1.discoverProjectEvidence)(cwd, parsed.pack);
|
|
105
|
+
const resolutionEvidence = {
|
|
106
|
+
...evidence,
|
|
107
|
+
...(manifest.packSelection === 'explicit' ? { packSelection: 'explicit' } : {}),
|
|
108
|
+
};
|
|
109
|
+
const initial = (0, resolve_1.resolveProjectCompatibility)(parsed.pack, resolutionEvidence).sensors;
|
|
110
|
+
return Object.fromEntries(Object.entries(parsed.pack.sensors).map(([name, sensor]) => {
|
|
111
|
+
const variant = initial[name]?.variantId === null
|
|
112
|
+
? null
|
|
113
|
+
: sensor.variants.find(candidate => candidate.id === initial[name]?.variantId) ?? null;
|
|
114
|
+
// These two probe kinds consume discovery output only. Keep their useful
|
|
115
|
+
// static validation in status while leaving every command-backed probe
|
|
116
|
+
// inconclusive rather than dispatching it.
|
|
117
|
+
const probeStatus = variant?.probe.kind === 'config-present'
|
|
118
|
+
? (evidence.configFiles.length > 0 ? 'matched' : 'not-matched')
|
|
119
|
+
: variant?.probe.kind === 'package-script-present'
|
|
120
|
+
? (evidence.scripts.length > 0 ? 'matched' : 'not-matched')
|
|
121
|
+
: undefined;
|
|
122
|
+
return [name, probeStatus === undefined
|
|
123
|
+
? initial[name]
|
|
124
|
+
: (0, resolve_1.resolveSensorCompatibility)(sensor, { ...resolutionEvidence, probe: { status: probeStatus } }, { pack: parsed.pack.name, sensor: name })];
|
|
125
|
+
}));
|
|
126
|
+
}
|
|
127
|
+
function staticCompatibilityCheck(sensor, live) {
|
|
128
|
+
if (!live)
|
|
129
|
+
return { ok: false, detail: 'live compatibility unavailable' };
|
|
130
|
+
if (live.variantId !== sensor.variantId) {
|
|
131
|
+
return { ok: false, detail: `compatibility drift: initialized ${sensor.variantId}, resolved ${live.variantId ?? live.state}` };
|
|
132
|
+
}
|
|
133
|
+
if (live.state === 'incompatible' || live.state === 'missing-tool' || live.state === 'not-applicable' || live.reason === 'probe-not-matched') {
|
|
134
|
+
return { ok: false, detail: `live compatibility ${live.state}: ${live.reason}` };
|
|
135
|
+
}
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
58
138
|
async function computeSensorStatus(cwd = process.cwd()) {
|
|
59
139
|
const manifestPath = path_1.default.join(cwd, '.awm', 'sensors.json');
|
|
60
140
|
if (!fs_1.default.existsSync(manifestPath)) {
|
|
@@ -66,32 +146,26 @@ async function computeSensorStatus(cwd = process.cwd()) {
|
|
|
66
146
|
const parsed = (0, manifest_1.parseSensorManifest)(raw, manifestPath);
|
|
67
147
|
if (parsed.kind === 'v2') {
|
|
68
148
|
const checks = {};
|
|
69
|
-
let
|
|
149
|
+
let compatibility;
|
|
70
150
|
try {
|
|
71
|
-
|
|
151
|
+
compatibility = resolveStaticV2Compatibility(cwd, parsed.pack);
|
|
72
152
|
}
|
|
73
153
|
catch (error) {
|
|
74
|
-
const detail =
|
|
154
|
+
const detail = error instanceof Error ? error.message : 'live compatibility unavailable';
|
|
75
155
|
for (const [name, sensor] of Object.entries(parsed.pack.sensors)) {
|
|
76
156
|
checks[name] = sensor.enabled === false ? { ok: true, detail: 'disabled' } : { ok: false, detail };
|
|
77
157
|
}
|
|
78
158
|
return { overall: 'DEGRADED', pack: parsed.pack.pack, checks };
|
|
79
159
|
}
|
|
80
160
|
for (const [name, sensor] of Object.entries(parsed.pack.sensors)) {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
? { ok: false, detail: `variant-drift: manifest ${sensor.variantId}, live ${state.variantId ?? 'none'}; run \`awm sensors init\`` }
|
|
88
|
-
: state.state === 'certified'
|
|
89
|
-
? { ok: true, detail: `certified (${state.variantId})` }
|
|
90
|
-
: state.state === 'not-applicable'
|
|
91
|
-
? { ok: true, detail: 'not applicable' }
|
|
92
|
-
: { ok: false, detail: `${state.state}: ${state.reason}` };
|
|
161
|
+
if (sensor.enabled === false) {
|
|
162
|
+
checks[name] = { ok: true, detail: 'disabled' };
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
checks[name] = staticCompatibilityCheck(sensor, compatibility[name])
|
|
166
|
+
?? checkStructuredCommand(sensor.command, cwd, sensor.assets);
|
|
93
167
|
}
|
|
94
|
-
return { overall: Object.keys(checks).length > 0 && Object.values(checks).every(check => check.ok) ? '
|
|
168
|
+
return { overall: Object.keys(checks).length > 0 && Object.values(checks).every(check => check.ok) ? 'READY' : 'DEGRADED', pack: parsed.pack.pack, checks };
|
|
95
169
|
}
|
|
96
170
|
manifest = parsed.pack;
|
|
97
171
|
}
|
|
@@ -122,12 +196,10 @@ async function computeSensorStatus(cwd = process.cwd()) {
|
|
|
122
196
|
}
|
|
123
197
|
// `Object.values({}).every(...)` is vacuously true — a manifest with zero sensor
|
|
124
198
|
// entries (the registry had no pack.json for this stack; see init.ts) must not read
|
|
125
|
-
// as
|
|
199
|
+
// as READY just because there was nothing to fail. Same false-green `checkManifest`
|
|
126
200
|
// guards against in preflight.
|
|
127
201
|
if (Object.keys(manifest.sensors ?? {}).length === 0) {
|
|
128
202
|
return { overall: 'DEGRADED', pack: manifest.pack, checks };
|
|
129
203
|
}
|
|
130
|
-
|
|
131
|
-
// has no versioned/structured contract and must never present as certified.
|
|
132
|
-
return { overall: 'DEGRADED', pack: manifest.pack, checks };
|
|
204
|
+
return { overall: Object.values(checks).every(check => check.ok) ? 'READY' : 'DEGRADED', pack: manifest.pack, checks };
|
|
133
205
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.exitCodeForVerdict = exitCodeForVerdict;
|
|
4
|
+
exports.reduceVerdict = reduceVerdict;
|
|
5
|
+
/** Map the semantic sensor verdict to the CLI process status. Only a global pass succeeds. */
|
|
6
|
+
function exitCodeForVerdict(overall) {
|
|
7
|
+
return overall === 'pass' ? 0 : 1;
|
|
8
|
+
}
|
|
9
|
+
/** Reduce all selected sensor outcomes through one format-agnostic verdict rule. */
|
|
10
|
+
function reduceVerdict(results) {
|
|
11
|
+
if (!Array.isArray(results))
|
|
12
|
+
throw new Error('sensor results must be an array');
|
|
13
|
+
for (const result of results) {
|
|
14
|
+
if (!result || typeof result !== 'object')
|
|
15
|
+
throw new Error('sensor result must be an object');
|
|
16
|
+
if (!['pass', 'fail', 'inconclusive', 'skipped'].includes(result.status)) {
|
|
17
|
+
throw new Error('sensor result status is invalid');
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
if (results.some(result => result.status === 'fail'))
|
|
21
|
+
return 'fail';
|
|
22
|
+
if (results.some(result => result.status === 'inconclusive'))
|
|
23
|
+
return 'not_certified';
|
|
24
|
+
if (results.some(result => result.status === 'pass'))
|
|
25
|
+
return 'pass';
|
|
26
|
+
return 'skipped';
|
|
27
|
+
}
|
|
@@ -9,6 +9,7 @@ const path_1 = __importDefault(require("path"));
|
|
|
9
9
|
const child_process_1 = require("child_process");
|
|
10
10
|
const checks_1 = require("../../../src/commands/preflight/checks");
|
|
11
11
|
const preflight_1 = require("../../../src/commands/preflight");
|
|
12
|
+
const run_1 = require("../../../src/commands/sensors/run");
|
|
12
13
|
// Only `execSync` (used by `resolveOnPath` to check for `gh`/`glab`) is mocked — `git
|
|
13
14
|
// remote get-url origin` runs for real via `execFileSync` against real tmpdir git repos,
|
|
14
15
|
// same as every other check in this file exercises the real filesystem.
|
|
@@ -16,7 +17,12 @@ jest.mock('child_process', () => ({
|
|
|
16
17
|
...jest.requireActual('child_process'),
|
|
17
18
|
execSync: jest.fn(),
|
|
18
19
|
}));
|
|
20
|
+
jest.mock('../../../src/commands/sensors/run', () => ({
|
|
21
|
+
...jest.requireActual('../../../src/commands/sensors/run'),
|
|
22
|
+
runSensors: jest.fn(),
|
|
23
|
+
}));
|
|
19
24
|
const mockExecSync = child_process_1.execSync;
|
|
25
|
+
const mockRunSensors = run_1.runSensors;
|
|
20
26
|
/** Turn a tmpdir into a real git repo with (optionally) an `origin` remote. */
|
|
21
27
|
function gitRepo(dir, remoteUrl) {
|
|
22
28
|
(0, child_process_1.execFileSync)('git', ['init'], { cwd: dir, stdio: 'pipe' });
|
|
@@ -47,6 +53,50 @@ const make = (o) => { const d = project(o); dirs.push(d); return d; };
|
|
|
47
53
|
afterAll(() => dirs.forEach(d => fs_1.default.rmSync(d, { recursive: true, force: true })));
|
|
48
54
|
const check = (r, id) => r.checks.find(c => c.id === id);
|
|
49
55
|
describe('preflight', () => {
|
|
56
|
+
afterEach(() => mockRunSensors.mockReset());
|
|
57
|
+
it('keeps default preflight static and does not dispatch sensors', async () => {
|
|
58
|
+
const dir = make({
|
|
59
|
+
manifest: { pack: 'generic', sensors: { security: { enabled: false } } },
|
|
60
|
+
});
|
|
61
|
+
await (0, checks_1.preflight)(dir);
|
|
62
|
+
expect(mockRunSensors).not.toHaveBeenCalled();
|
|
63
|
+
});
|
|
64
|
+
it('rejects an array passed as public preflight options before filesystem work', async () => {
|
|
65
|
+
await expect((0, checks_1.preflight)(process.cwd(), []))
|
|
66
|
+
.rejects.toThrow('preflight options must contain an optional boolean verifySensors');
|
|
67
|
+
});
|
|
68
|
+
it('requires an empirical sensor pass when verification is requested', async () => {
|
|
69
|
+
const dir = make({
|
|
70
|
+
manifest: { pack: 'generic', sensors: { security: { enabled: false } } },
|
|
71
|
+
});
|
|
72
|
+
mockRunSensors.mockResolvedValue({
|
|
73
|
+
overall: 'not_certified',
|
|
74
|
+
sensors: [{
|
|
75
|
+
name: 'lint', status: 'inconclusive', errors: [], skipReason: 'timeout after 30000ms',
|
|
76
|
+
execution: { timeoutMs: 30000, timeoutSource: 'project', elapsedMs: 30012, requestedScope: 'full', effectiveScope: 'full' },
|
|
77
|
+
}],
|
|
78
|
+
});
|
|
79
|
+
const report = await (0, checks_1.preflight)(dir, { verifySensors: true });
|
|
80
|
+
expect(mockRunSensors).toHaveBeenCalledWith({ cwd: dir, all: true });
|
|
81
|
+
expect(report.status).toBe('degraded');
|
|
82
|
+
expect(check(report, 'sensors-execution')).toMatchObject({
|
|
83
|
+
ok: false,
|
|
84
|
+
detail: expect.stringMatching(/lint.*30000ms.*elapsed.*timeout/i),
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
it('does not invent a sensor or timeout when empirical verification has no executed sensors', async () => {
|
|
88
|
+
const dir = make();
|
|
89
|
+
mockRunSensors.mockResolvedValue({ overall: 'not_certified', sensors: [] });
|
|
90
|
+
const report = await (0, checks_1.preflight)(dir, { verifySensors: true });
|
|
91
|
+
const execution = check(report, 'sensors-execution');
|
|
92
|
+
expect(execution).toMatchObject({
|
|
93
|
+
ok: false,
|
|
94
|
+
detail: 'sensor verdict was not_certified; no sensor established an empirical pass',
|
|
95
|
+
});
|
|
96
|
+
expect(execution.detail).not.toMatch(/timeout|elapsed|named sensor/i);
|
|
97
|
+
expect(execution.remedy).toContain('awm sensors init');
|
|
98
|
+
expect(execution.remedy).not.toContain('named sensor');
|
|
99
|
+
});
|
|
50
100
|
it('reports not_configured when no sensor manifest exists', async () => {
|
|
51
101
|
// The team-rollout case: a developer clones the repo and never runs
|
|
52
102
|
// `awm sensors init`. Today nothing notices until an unattended run is already
|
|
@@ -7,6 +7,7 @@ const fs_1 = __importDefault(require("fs"));
|
|
|
7
7
|
const path_1 = __importDefault(require("path"));
|
|
8
8
|
const os_1 = __importDefault(require("os"));
|
|
9
9
|
const baseline_1 = require("../../../src/commands/sensors/baseline");
|
|
10
|
+
const result_1 = require("../../../src/commands/sensors/result");
|
|
10
11
|
const err = (over = {}) => ({
|
|
11
12
|
file: 'lib/a.ts', rule: 'TS2345', message: 'Argument of type X', ...over,
|
|
12
13
|
});
|
|
@@ -76,6 +77,19 @@ describe('partition', () => {
|
|
|
76
77
|
expect(newErrors).toHaveLength(0);
|
|
77
78
|
});
|
|
78
79
|
});
|
|
80
|
+
describe('applyBaseline', () => {
|
|
81
|
+
it('applies the same baseline to structured findings (R2)', () => {
|
|
82
|
+
const finding = { file: 'src/a.ts', line: 1, message: 'x' };
|
|
83
|
+
const accepted = [(0, baseline_1.fingerprint)('lint', finding)];
|
|
84
|
+
expect((0, result_1.applyBaseline)({ name: 'lint', status: 'fail', errors: [finding] }, accepted))
|
|
85
|
+
.toMatchObject({ status: 'pass', baselineCount: 1, newCount: 0 });
|
|
86
|
+
});
|
|
87
|
+
test.each(['inconclusive', 'skipped'])('baseline never changes %s to pass (R2.1)', status => {
|
|
88
|
+
const finding = { file: 'src/a.ts', line: 1, message: 'fixture finding' };
|
|
89
|
+
const accepted = [(0, baseline_1.fingerprint)('lint', finding)];
|
|
90
|
+
expect((0, result_1.applyBaseline)({ name: 'lint', status, errors: [finding], skipReason: 'fixture' }, accepted).status).toBe(status);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
79
93
|
describe('readBaseline / writeBaseline', () => {
|
|
80
94
|
let cwd;
|
|
81
95
|
beforeEach(() => { cwd = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-bl-')); });
|
|
@@ -41,4 +41,7 @@ describe('applyChangedCmd — Windows quoting', () => {
|
|
|
41
41
|
expect((0, changed_1.applyChangedCmd)('eslint {files}', ['report\\']))
|
|
42
42
|
.toBe(`eslint "report\\\\"`);
|
|
43
43
|
});
|
|
44
|
+
it('refuses unsafe filenames for legacy shell interpolation', () => {
|
|
45
|
+
expect((0, changed_1.changedScopeError)({ files: ['src/a&b.ts'] })).toMatch(/cmd\.exe metacharacter/);
|
|
46
|
+
});
|
|
44
47
|
});
|
|
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
const contract_1 = require("../../../../src/commands/sensors/compatibility/contract");
|
|
7
|
+
const timeout_1 = require("../../../../src/commands/sensors/compatibility/timeout");
|
|
7
8
|
const fs_1 = __importDefault(require("fs"));
|
|
8
9
|
const os_1 = __importDefault(require("os"));
|
|
9
10
|
const path_1 = __importDefault(require("path"));
|
|
@@ -43,6 +44,22 @@ function validPack() {
|
|
|
43
44
|
};
|
|
44
45
|
}
|
|
45
46
|
describe('sensor pack v2 contract', () => {
|
|
47
|
+
it('exports bounded timeout validation and resolution (R3.1, R3.4)', () => {
|
|
48
|
+
expect((0, timeout_1.positiveTimeout)(1, 'sensor timeout')).toBe(1);
|
|
49
|
+
expect((0, timeout_1.resolveTimeout)({ project: 90_000, pack: 30_000, fast: true })).toEqual({ timeoutMs: 90_000, source: 'project' });
|
|
50
|
+
expect((0, timeout_1.resolveTimeout)({ pack: 30_000, fast: true })).toEqual({ timeoutMs: 30_000, source: 'pack' });
|
|
51
|
+
expect((0, timeout_1.resolveTimeout)({ fast: true })).toEqual({ timeoutMs: 10_000, source: 'fallback' });
|
|
52
|
+
expect((0, timeout_1.resolveTimeout)({ fast: false })).toEqual({ timeoutMs: 120_000, source: 'fallback' });
|
|
53
|
+
});
|
|
54
|
+
test.each([0, -1, 1.5, Number.MAX_SAFE_INTEGER + 1, '1000'])('rejects invalid resolved timeout %p (R3.3)', timeout => {
|
|
55
|
+
expect(() => (0, timeout_1.positiveTimeout)(timeout, 'sensor timeout')).toThrow(/sensor timeout.*positive safe integer/);
|
|
56
|
+
expect(() => (0, timeout_1.resolveTimeout)({ project: timeout, fast: true })).toThrow(/project timeout.*positive safe integer/);
|
|
57
|
+
});
|
|
58
|
+
it('rejects malformed timeout helper inputs loudly (R3.4)', () => {
|
|
59
|
+
expect(() => (0, timeout_1.positiveTimeout)(1000, '')).toThrow('timeout location must be a nonempty string');
|
|
60
|
+
expect(() => (0, timeout_1.resolveTimeout)(null)).toThrow('timeout resolution input is invalid');
|
|
61
|
+
expect(() => (0, timeout_1.resolveTimeout)({ fast: 'true' })).toThrow('timeout resolution input is invalid');
|
|
62
|
+
});
|
|
46
63
|
it('derives Semgrep compatibility from a contained shared policy reference', () => {
|
|
47
64
|
const sensorPacks = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-semgrep-policy-'));
|
|
48
65
|
const packDir = path_1.default.join(sensorPacks, 'python');
|
|
@@ -79,6 +96,40 @@ describe('sensor pack v2 contract', () => {
|
|
|
79
96
|
it('parses a valid versioned pack', () => {
|
|
80
97
|
expect((0, contract_1.parseSensorPack)(validPack(), 'pack.json')).toMatchObject({ kind: 'v2', pack: validPack() });
|
|
81
98
|
});
|
|
99
|
+
it('accepts one standalone files placeholder in changedCommand (R4)', () => {
|
|
100
|
+
const pack = validPack();
|
|
101
|
+
pack.sensors.lint.variants[0].changedCommand = {
|
|
102
|
+
executable: 'eslint', resolution: 'node-modules-bin',
|
|
103
|
+
args: ['--format', 'json', '{files}'],
|
|
104
|
+
fileInput: { placeholder: '{files}', extensions: ['.js', '.ts'] },
|
|
105
|
+
};
|
|
106
|
+
expect((0, contract_1.parseSensorPack)(pack, '/registry/sensor-packs/js-ts/pack.json')).toMatchObject({
|
|
107
|
+
kind: 'v2', pack: { sensors: { lint: { variants: [{ changedCommand: { args: ['--format', 'json', '{files}'] } }] } } },
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
test.each([
|
|
111
|
+
{ args: ['{files}', '{files}'], fileInput: { placeholder: '{files}', extensions: ['.ts'] } },
|
|
112
|
+
{ args: ['prefix-{files}'], fileInput: { placeholder: '{files}', extensions: ['.ts'] } },
|
|
113
|
+
{ args: ['{files}'], fileInput: { placeholder: '{files}', extensions: [] } },
|
|
114
|
+
])('rejects unsafe changedCommand %# (R4)', changedCommand => {
|
|
115
|
+
const pack = validPack();
|
|
116
|
+
pack.sensors.lint.variants[0].changedCommand = {
|
|
117
|
+
executable: 'eslint', resolution: 'node-modules-bin', ...changedCommand,
|
|
118
|
+
};
|
|
119
|
+
expect(() => (0, contract_1.parseSensorPack)(pack, '/registry/sensor-packs/js-ts/pack.json')).toThrow(/changedCommand|fileInput|\{files\}/);
|
|
120
|
+
});
|
|
121
|
+
test.each([0, -1, 1.5, Number.MAX_SAFE_INTEGER + 1, '1000'])('rejects pack sensor timeout %p (R3.3)', timeout => {
|
|
122
|
+
const pack = validPack();
|
|
123
|
+
pack.sensors.lint.timeout = timeout;
|
|
124
|
+
expect(() => (0, contract_1.parseSensorPack)(pack, '/registry/sensor-packs/js-ts/pack.json')).toThrow(/timeout.*positive safe integer/);
|
|
125
|
+
});
|
|
126
|
+
it('accepts an optional positive pack sensor timeout (R3.1)', () => {
|
|
127
|
+
const pack = validPack();
|
|
128
|
+
pack.sensors.lint.timeout = 30_000;
|
|
129
|
+
expect((0, contract_1.parseSensorPack)(pack, '/registry/sensor-packs/js-ts/pack.json')).toMatchObject({
|
|
130
|
+
kind: 'v2', pack: { sensors: { lint: { timeout: 30_000 } } },
|
|
131
|
+
});
|
|
132
|
+
});
|
|
82
133
|
it('accepts an opt-in hardening asset while variants may require no assets', () => {
|
|
83
134
|
const pack = {
|
|
84
135
|
...validPack(),
|
|
@@ -6,6 +6,19 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
const manifest_1 = require("../../../../src/commands/sensors/compatibility/manifest");
|
|
7
7
|
const fs_1 = __importDefault(require("fs"));
|
|
8
8
|
const path_1 = __importDefault(require("path"));
|
|
9
|
+
function validV2Manifest() {
|
|
10
|
+
return {
|
|
11
|
+
schemaVersion: 2,
|
|
12
|
+
pack: 'js-ts',
|
|
13
|
+
sensors: {
|
|
14
|
+
lint: {
|
|
15
|
+
enabled: true, variantId: 'eslint-9',
|
|
16
|
+
command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.', '--format', 'json'] },
|
|
17
|
+
initializedCompatibility: { state: 'certified', reason: 'range-and-probe', variantId: 'eslint-9', toolVersion: '9.0.0', runtimeVersion: '24.0.0', certifiedRange: '>=9 <10', evidence: [] },
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
}
|
|
9
22
|
describe('sensor manifest contract', () => {
|
|
10
23
|
it('keeps compatibility contracts on an acyclic import boundary', () => {
|
|
11
24
|
const source = (relative) => fs_1.default.readFileSync(path_1.default.join(__dirname, '../../../../src/commands/sensors', relative), 'utf8');
|
|
@@ -21,20 +34,23 @@ describe('sensor manifest contract', () => {
|
|
|
21
34
|
} });
|
|
22
35
|
});
|
|
23
36
|
it('accepts a v2 selected variant and structured command', () => {
|
|
24
|
-
const manifest =
|
|
25
|
-
schemaVersion: 2,
|
|
26
|
-
pack: 'js-ts',
|
|
27
|
-
sensors: {
|
|
28
|
-
lint: {
|
|
29
|
-
enabled: true, variantId: 'eslint-9',
|
|
30
|
-
command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.', '--format', 'json'] },
|
|
31
|
-
initializedCompatibility: { state: 'certified', reason: 'range-and-probe', variantId: 'eslint-9', toolVersion: '9.0.0', runtimeVersion: '24.0.0', certifiedRange: '>=9 <10', evidence: [] },
|
|
32
|
-
},
|
|
33
|
-
},
|
|
34
|
-
};
|
|
37
|
+
const manifest = validV2Manifest();
|
|
35
38
|
expect((0, manifest_1.parseSensorManifest)(manifest, 'sensors.json')).toMatchObject({ kind: 'v2', pack: manifest });
|
|
36
39
|
expect(JSON.parse((0, manifest_1.serializeManifestV2)(manifest))).toEqual(manifest);
|
|
37
40
|
});
|
|
41
|
+
it('accepts and serializes a positive v2 project timeout (R3)', () => {
|
|
42
|
+
const manifest = validV2Manifest();
|
|
43
|
+
manifest.sensors.lint.timeout = 45_000;
|
|
44
|
+
expect((0, manifest_1.parseSensorManifest)(manifest, '/project/.awm/sensors.json')).toMatchObject({
|
|
45
|
+
kind: 'v2', pack: { sensors: { lint: { timeout: 45_000 } } },
|
|
46
|
+
});
|
|
47
|
+
expect(JSON.parse((0, manifest_1.serializeManifestV2)(manifest))).toEqual(manifest);
|
|
48
|
+
});
|
|
49
|
+
test.each([0, -1, 1.5, Number.MAX_SAFE_INTEGER + 1, '1000'])('rejects v2 timeout %p before execution (R3.3)', timeout => {
|
|
50
|
+
const manifest = validV2Manifest();
|
|
51
|
+
manifest.sensors.lint.timeout = timeout;
|
|
52
|
+
expect(() => (0, manifest_1.parseSensorManifest)(manifest, '/project/.awm/sensors.json')).toThrow(/timeout.*positive safe integer/);
|
|
53
|
+
});
|
|
38
54
|
it('persists only an explicit v2 pack selection as applicability provenance', () => {
|
|
39
55
|
const manifest = {
|
|
40
56
|
schemaVersion: 2, pack: 'generic', packSelection: 'explicit',
|
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const probe_1 = require("../../../../src/commands/sensors/compatibility/probe");
|
|
4
|
-
const
|
|
4
|
+
const execResult = (overrides = {}) => ({
|
|
5
|
+
code: 0,
|
|
6
|
+
signal: null,
|
|
7
|
+
timedOut: false,
|
|
8
|
+
overflowed: false,
|
|
9
|
+
elapsedMs: 0,
|
|
10
|
+
stdout: 'SECRET_VALUE\nmatched',
|
|
11
|
+
stderr: '',
|
|
12
|
+
...overrides,
|
|
13
|
+
});
|
|
14
|
+
const fakeExecutor = jest.fn(async () => execResult());
|
|
5
15
|
const evidence = { cwd: process.cwd(), configFiles: ['eslint.config.js'], scripts: ['lint'] };
|
|
6
16
|
describe('runCompatibilityProbe', () => {
|
|
7
17
|
beforeEach(() => fakeExecutor.mockClear());
|
|
@@ -11,9 +21,9 @@ describe('runCompatibilityProbe', () => {
|
|
|
11
21
|
expect(JSON.stringify(result)).not.toContain('SECRET_VALUE');
|
|
12
22
|
});
|
|
13
23
|
it('never treats a timeout or overflow as a match', async () => {
|
|
14
|
-
fakeExecutor.mockResolvedValueOnce({ code: null, signal: 'SIGKILL', timedOut: true,
|
|
24
|
+
fakeExecutor.mockResolvedValueOnce(execResult({ code: null, signal: 'SIGKILL', timedOut: true, stdout: '' }));
|
|
15
25
|
await expect((0, probe_1.runCompatibilityProbe)({ kind: 'version' }, evidence, fakeExecutor)).resolves.toMatchObject({ status: 'unverifiable' });
|
|
16
|
-
fakeExecutor.mockResolvedValueOnce({
|
|
26
|
+
fakeExecutor.mockResolvedValueOnce(execResult({ overflowed: true, stdout: 'ok' }));
|
|
17
27
|
await expect((0, probe_1.runCompatibilityProbe)({ kind: 'version' }, evidence, fakeExecutor)).resolves.toMatchObject({ status: 'unverifiable' });
|
|
18
28
|
});
|
|
19
29
|
it('binds tool probes to the project node_modules executable instead of PATH', async () => {
|
|
@@ -6,7 +6,7 @@ exports.spawnFailed = exports.overflowed = exports.timedOut = exports.exited = e
|
|
|
6
6
|
* mock the exec boundary rather than `child_process` directly: `runCommand`
|
|
7
7
|
* never throws, so a mocked run is a value, not an exception.
|
|
8
8
|
*/
|
|
9
|
-
const base = { stdout: '', stderr: '', code: null, signal: null, timedOut: false, overflowed: false };
|
|
9
|
+
const base = { stdout: '', stderr: '', code: null, signal: null, timedOut: false, overflowed: false, elapsedMs: 0 };
|
|
10
10
|
/** Clean run: exit 0. */
|
|
11
11
|
const ok = (stdout = '') => ({ ...base, stdout, code: 0 });
|
|
12
12
|
exports.ok = ok;
|
|
@@ -7,6 +7,18 @@ const fs_1 = __importDefault(require("fs"));
|
|
|
7
7
|
const os_1 = __importDefault(require("os"));
|
|
8
8
|
const path_1 = __importDefault(require("path"));
|
|
9
9
|
const exec_1 = require("../../../src/commands/sensors/exec");
|
|
10
|
+
const result_1 = require("../../../src/commands/sensors/result");
|
|
11
|
+
const prepare_1 = require("../../../src/commands/sensors/prepare");
|
|
12
|
+
const sensor = (overrides = {}) => ({
|
|
13
|
+
name: 'lint',
|
|
14
|
+
command: { kind: 'legacy', value: 'node -e "setTimeout(() => {}, 1000)"' },
|
|
15
|
+
formatter: 'generic',
|
|
16
|
+
timeoutMs: 5_000,
|
|
17
|
+
timeoutSource: 'fallback',
|
|
18
|
+
requestedScope: 'full',
|
|
19
|
+
effectiveScope: 'full',
|
|
20
|
+
...overrides,
|
|
21
|
+
});
|
|
10
22
|
const onPosix = process.platform !== 'win32' ? describe : describe.skip;
|
|
11
23
|
const itPosix = process.platform !== 'win32' ? it : it.skip;
|
|
12
24
|
/** Poll until `fn()` is true or the budget runs out. Avoids fixed sleeps. */
|
|
@@ -20,6 +32,12 @@ async function until(fn, budgetMs = 4000) {
|
|
|
20
32
|
return fn();
|
|
21
33
|
}
|
|
22
34
|
describe('runCommand — exit codes and output', () => {
|
|
35
|
+
it('records bounded execution evidence on timeout (R3.2,R3.4,R7.1)', async () => {
|
|
36
|
+
const result = await (0, result_1.executePrepared)(sensor({ timeoutMs: 25, timeoutSource: 'project' }));
|
|
37
|
+
expect(result.status).toBe('inconclusive');
|
|
38
|
+
expect(result.execution).toMatchObject({ timeoutMs: 25, timeoutSource: 'project', effectiveScope: 'full' });
|
|
39
|
+
expect(result.execution.elapsedMs).toBeGreaterThanOrEqual(0);
|
|
40
|
+
});
|
|
23
41
|
it('passes structured metacharacters literally without a shell', async () => {
|
|
24
42
|
const dir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-exec-argv-'));
|
|
25
43
|
const marker = path_1.default.join(dir, 'must-not-exist');
|
|
@@ -131,6 +149,24 @@ describe('runCommand — spawn failure', () => {
|
|
|
131
149
|
});
|
|
132
150
|
});
|
|
133
151
|
describe('runStructuredCommand — public boundary validation', () => {
|
|
152
|
+
it('dispatches argv materialized from a validated changed-command template without requiring its placeholder again', async () => {
|
|
153
|
+
const dir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-exec-changed-argv-'));
|
|
154
|
+
const received = path_1.default.join(dir, 'received.json');
|
|
155
|
+
try {
|
|
156
|
+
const command = (0, prepare_1.expandFileInput)({
|
|
157
|
+
executable: 'node',
|
|
158
|
+
resolution: 'path',
|
|
159
|
+
args: ['-e', "require('fs').writeFileSync(process.argv[1], JSON.stringify(process.argv.slice(2)))", received, '{files}'],
|
|
160
|
+
fileInput: { placeholder: '{files}', extensions: ['.ts'] },
|
|
161
|
+
}, ['src/a.ts', 'src/with space.ts']);
|
|
162
|
+
const result = await (0, exec_1.runStructuredCommand)(command, { timeout: 5_000, cwd: dir });
|
|
163
|
+
expect(result.code).toBe(0);
|
|
164
|
+
expect(JSON.parse(fs_1.default.readFileSync(received, 'utf8'))).toEqual(['src/a.ts', 'src/with space.ts']);
|
|
165
|
+
}
|
|
166
|
+
finally {
|
|
167
|
+
fs_1.default.rmSync(dir, { recursive: true, force: true });
|
|
168
|
+
}
|
|
169
|
+
});
|
|
134
170
|
it.each(['sh', 'cmd.exe', '/usr/bin/node', 'nested/tool', 'nested\\tool'])('rejects unsafe executable %j before resolution', executable => {
|
|
135
171
|
expect(() => (0, exec_1.runStructuredCommand)({ executable, resolution: 'path', args: ['--version'] }, { timeout: 5000, cwd: process.cwd() }))
|
|
136
172
|
.toThrow(/safe executable name|shell/i);
|
|
@@ -15,18 +15,56 @@ const prompts_1 = require("@clack/prompts");
|
|
|
15
15
|
const coverage_1 = require("../../../src/commands/sensors/coverage");
|
|
16
16
|
const render_1 = require("../../../src/commands/sensors/coverage/render");
|
|
17
17
|
const index_1 = require("../../../src/commands/sensors/index");
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
it
|
|
23
|
-
|
|
18
|
+
const verdict_1 = require("../../../src/commands/sensors/verdict");
|
|
19
|
+
const processExit = jest.spyOn(process, 'exit').mockImplementation((() => undefined));
|
|
20
|
+
const stdoutWrite = jest.spyOn(process.stdout, 'write').mockImplementation(() => true);
|
|
21
|
+
describe('exitCodeForVerdict — sensor run verdict → exit code', () => {
|
|
22
|
+
it.each([
|
|
23
|
+
['pass', 0],
|
|
24
|
+
['fail', 1],
|
|
25
|
+
['skipped', 1],
|
|
26
|
+
['not_certified', 1],
|
|
27
|
+
])('%s → %i', (overall, code) => {
|
|
28
|
+
expect((0, verdict_1.exitCodeForVerdict)(overall)).toBe(code);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
describe('sensors run Commander wiring', () => {
|
|
32
|
+
const originalExitCode = process.exitCode;
|
|
33
|
+
beforeEach(() => {
|
|
34
|
+
jest.clearAllMocks();
|
|
35
|
+
process.exitCode = undefined;
|
|
36
|
+
});
|
|
37
|
+
afterAll(() => {
|
|
38
|
+
process.exitCode = originalExitCode;
|
|
39
|
+
});
|
|
40
|
+
const programWithSensors = () => {
|
|
41
|
+
const program = new commander_1.Command();
|
|
42
|
+
program.exitOverride();
|
|
43
|
+
(0, index_1.registerSensorsCommand)(program);
|
|
44
|
+
return program;
|
|
45
|
+
};
|
|
46
|
+
it.each([
|
|
47
|
+
['pass', 0],
|
|
48
|
+
['fail', 1],
|
|
49
|
+
['skipped', 1],
|
|
50
|
+
['not_certified', 1],
|
|
51
|
+
])('writes full %s JSON before assigning exit code %i without process.exit', async (overall, code) => {
|
|
52
|
+
require('../../../src/commands/sensors/run').runSensors.mockResolvedValue({ sensors: [], overall });
|
|
53
|
+
const calls = [];
|
|
54
|
+
stdoutWrite.mockImplementation(() => {
|
|
55
|
+
calls.push(`stdout:${process.exitCode ?? 0}`);
|
|
56
|
+
return true;
|
|
57
|
+
});
|
|
58
|
+
await programWithSensors().parseAsync(['node', 'awm', 'sensors', 'run']);
|
|
59
|
+
expect(JSON.parse(String(stdoutWrite.mock.calls[0][0]))).toEqual({ sensors: [], overall });
|
|
60
|
+
expect(calls).toEqual(['stdout:0']);
|
|
61
|
+
expect(process.exitCode).toBe(code);
|
|
62
|
+
expect(processExit).not.toHaveBeenCalled();
|
|
63
|
+
});
|
|
24
64
|
});
|
|
25
65
|
describe('sensors coverage Commander wiring', () => {
|
|
26
66
|
const report = { schemaVersion: 1, pack: 'js-ts', registry: 'baseline', overall: 'gaps',
|
|
27
67
|
static: { status: 'gaps', reason: null, classes: [] } };
|
|
28
|
-
const stdoutWrite = jest.spyOn(process.stdout, 'write').mockImplementation(() => true);
|
|
29
|
-
const processExit = jest.spyOn(process, 'exit').mockImplementation((() => undefined));
|
|
30
68
|
beforeEach(() => {
|
|
31
69
|
jest.clearAllMocks();
|
|
32
70
|
coverage_1.runCoverage.mockReturnValue(report);
|
|
@@ -310,7 +310,7 @@ describe('initSensors', () => {
|
|
|
310
310
|
const written = JSON.parse(fs_1.default.readFileSync(path_1.default.join(tmpDir, '.awm', 'sensors.json'), 'utf8'));
|
|
311
311
|
expect(explicit.manifest).toMatchObject({ schemaVersion: 2, pack: 'generic', packSelection: 'explicit', sensors: { security: { variantId: 'eslint-10' } } });
|
|
312
312
|
expect(written.packSelection).toBe('explicit');
|
|
313
|
-
await expect((0, status_1.computeSensorStatus)(tmpDir)).resolves.toMatchObject({ overall: '
|
|
313
|
+
await expect((0, status_1.computeSensorStatus)(tmpDir)).resolves.toMatchObject({ overall: 'DEGRADED', checks: { security: { ok: false } } });
|
|
314
314
|
}
|
|
315
315
|
finally {
|
|
316
316
|
fs_1.default.rmSync(v2Registry, { recursive: true, force: true });
|
|
@@ -370,6 +370,35 @@ describe('initSensors', () => {
|
|
|
370
370
|
fs_1.default.rmSync(v2Registry, { recursive: true, force: true });
|
|
371
371
|
}
|
|
372
372
|
});
|
|
373
|
+
it('preserves only the project timeout override, never prior executable authority (R3, R10)', async () => {
|
|
374
|
+
const v2Registry = makeV2Registry();
|
|
375
|
+
try {
|
|
376
|
+
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'package.json'), JSON.stringify({ devDependencies: { eslint: '^10.0.0' } }));
|
|
377
|
+
fs_1.default.mkdirSync(path_1.default.join(tmpDir, 'node_modules', 'eslint'), { recursive: true });
|
|
378
|
+
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'node_modules', 'eslint', 'package.json'), JSON.stringify({ version: '10.0.0' }));
|
|
379
|
+
await (0, init_1.initSensors)({ cwd: tmpDir, registryRoot: v2Registry });
|
|
380
|
+
const manifestPath = path_1.default.join(tmpDir, '.awm', 'sensors.json');
|
|
381
|
+
const selected = JSON.parse(fs_1.default.readFileSync(manifestPath, 'utf8'));
|
|
382
|
+
selected.sensors.lint.timeout = 45_000;
|
|
383
|
+
selected.sensors.lint.command = {
|
|
384
|
+
executable: 'otherlint', resolution: 'path', args: ['--custom'],
|
|
385
|
+
environment: { ESLINT_USE_FLAT_CONFIG: 'false' },
|
|
386
|
+
};
|
|
387
|
+
selected.sensors.lint.assets = ['prior-owned.config'];
|
|
388
|
+
fs_1.default.writeFileSync(manifestPath, JSON.stringify(selected));
|
|
389
|
+
await (0, init_1.initSensors)({ cwd: tmpDir, registryRoot: v2Registry });
|
|
390
|
+
const written = JSON.parse(fs_1.default.readFileSync(manifestPath, 'utf8'));
|
|
391
|
+
expect(written.sensors.lint).toMatchObject({
|
|
392
|
+
timeout: 45_000,
|
|
393
|
+
command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] },
|
|
394
|
+
assets: ['eslint.config.awm.mjs'],
|
|
395
|
+
});
|
|
396
|
+
expect(written.sensors.lint.command.environment).toBeUndefined();
|
|
397
|
+
}
|
|
398
|
+
finally {
|
|
399
|
+
fs_1.default.rmSync(v2Registry, { recursive: true, force: true });
|
|
400
|
+
}
|
|
401
|
+
});
|
|
373
402
|
it('rejects a symlinked v2 pack source instead of reading it through init', async () => {
|
|
374
403
|
const v2Registry = makeV2Registry();
|
|
375
404
|
try {
|