agentic-workflow-manager 9.5.0 → 9.6.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/dist/src/commands/context/index.js +3 -1
- package/dist/src/commands/preflight/checks.js +2 -2
- package/dist/src/commands/sensors/bootstrap.js +171 -0
- package/dist/src/commands/sensors/compatibility/contract.js +18 -15
- package/dist/src/commands/sensors/compatibility/manifest.js +29 -1
- package/dist/src/commands/sensors/compatibility/materialize.js +256 -54
- package/dist/src/commands/sensors/compatibility/safe-file.js +240 -5
- package/dist/src/commands/sensors/coverage/index.js +2 -2
- package/dist/src/commands/sensors/coverage/resolve.js +20 -10
- package/dist/src/commands/sensors/detection.js +31 -0
- package/dist/src/commands/sensors/exec.js +116 -11
- package/dist/src/commands/sensors/index.js +51 -10
- package/dist/src/commands/sensors/init.js +17 -170
- package/dist/src/commands/sensors/migrate.js +209 -0
- package/dist/src/commands/sensors/run.js +2 -2
- package/dist/src/core/discovery.js +79 -4
- package/dist/src/core/init/steps.js +24 -11
- package/dist/src/core/orchestrators.js +34 -10
- package/dist/src/core/secure-fs/native-bridge.js +209 -0
- package/dist/src/core/text.js +7 -0
- package/dist/tests/commands/context.test.js +9 -1
- package/dist/tests/commands/hooks/install.test.js +4 -0
- package/dist/tests/commands/hooks/resync.test.js +2 -0
- package/dist/tests/commands/sensors/bootstrap-observational.test.js +24 -0
- package/dist/tests/commands/sensors/bootstrap.test.js +175 -0
- package/dist/tests/commands/sensors/compatibility/contract.test.js +38 -0
- package/dist/tests/commands/sensors/compatibility/manifest.test.js +67 -0
- package/dist/tests/commands/sensors/compatibility/materialize.test.js +410 -0
- package/dist/tests/commands/sensors/compatibility/pack-source.test.js +34 -1
- package/dist/tests/commands/sensors/compatibility/safe-file.test.js +52 -0
- package/dist/tests/commands/sensors/coverage/index.test.js +20 -0
- package/dist/tests/commands/sensors/coverage/resolve.test.js +10 -0
- package/dist/tests/commands/sensors/exec.test.js +86 -0
- package/dist/tests/commands/sensors/index.test.js +54 -0
- package/dist/tests/commands/sensors/init-pack-unavailable.test.js +21 -84
- package/dist/tests/commands/sensors/init.test.js +56 -490
- package/dist/tests/commands/sensors/migrate.test.js +513 -0
- package/dist/tests/core/context/orchestrator.test.js +2 -0
- package/dist/tests/core/discovery-multiroot.test.js +5 -0
- package/dist/tests/core/discovery-parent-race.test.js +95 -0
- package/dist/tests/core/discovery-safe-read.test.js +115 -0
- package/dist/tests/core/discovery.test.js +28 -1
- package/dist/tests/core/init/orchestrator.test.js +3 -1
- package/dist/tests/core/orchestrators.test.js +171 -14
- package/dist/tests/core/secure-fs/native-bridge.test.js +733 -0
- package/dist/tests/core/text.test.js +10 -0
- package/dist/tests/integration/context-orchestrators-e2e.test.js +57 -7
- package/dist/tests/integration/sensor-bootstrap.e2e.test.js +165 -0
- package/dist/tests/integration/sensor-compatibility.e2e.test.js +10 -2
- package/dist/tests/integration/sensor-structured-run.e2e.test.js +3 -1
- package/dist/tests/structural/native-release-artifacts.test.js +22 -0
- package/dist/tests/structural/release-flags-are-reachable-from-ci.test.js +10 -0
- package/dist/tests/structural/release-registry-provenance.test.js +4 -0
- package/dist/tests/structural/sensor-portability-contract.test.js +44 -0
- package/package.json +6 -2
- package/prebuilds/darwin-arm64/secure_fs.node +0 -0
- package/prebuilds/darwin-x64/secure_fs.node +0 -0
- package/prebuilds/linux-arm64/secure_fs.node +0 -0
- package/prebuilds/linux-x64/secure_fs.node +0 -0
- package/prebuilds/win32-arm64/secure_fs.node +0 -0
- package/prebuilds/win32-x64/secure_fs.node +0 -0
|
@@ -4,528 +4,94 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
const fs_1 = __importDefault(require("fs"));
|
|
7
|
-
const path_1 = __importDefault(require("path"));
|
|
8
7
|
const os_1 = __importDefault(require("os"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
jest.mock('../../../src/commands/sensors/bootstrap', () => ({
|
|
10
|
+
planSensorBootstrap: jest.fn(),
|
|
11
|
+
applySensorBootstrap: jest.fn(),
|
|
12
|
+
}));
|
|
9
13
|
const init_1 = require("../../../src/commands/sensors/init");
|
|
10
|
-
const
|
|
11
|
-
// Build a throwaway registry with a js-ts pack.json (the single source of truth
|
|
12
|
-
// init now reads from). `defaultCmd` uses the {{SOURCE_DIRS}} placeholder for depcheck.
|
|
14
|
+
const bootstrap_1 = require("../../../src/commands/sensors/bootstrap");
|
|
13
15
|
function makeRegistry() {
|
|
14
16
|
const registryRoot = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-reg-'));
|
|
15
17
|
const packDir = path_1.default.join(registryRoot, 'sensor-packs', 'js-ts');
|
|
16
18
|
fs_1.default.mkdirSync(packDir, { recursive: true });
|
|
17
19
|
fs_1.default.writeFileSync(path_1.default.join(packDir, 'pack.json'), JSON.stringify({
|
|
18
20
|
name: 'js-ts',
|
|
19
|
-
sensors: {
|
|
20
|
-
typecheck: { fast: true, defaultCmd: 'npx tsc --noEmit', formatter: 'tsc' },
|
|
21
|
-
lint: { fast: true, defaultCmd: 'npx eslint . --config eslint.config.awm.mjs --cache --format json', formatter: 'eslint-llm' },
|
|
22
|
-
depcheck: { fast: false, defaultCmd: 'npx depcruise --config .dep-cruiser.awm.js {{SOURCE_DIRS}}' },
|
|
23
|
-
mutation: { fast: false, enabled: false, defaultCmd: 'npx stryker run' },
|
|
24
|
-
},
|
|
25
|
-
}));
|
|
26
|
-
return registryRoot;
|
|
27
|
-
}
|
|
28
|
-
// Mirrors makeRegistry()'s js-ts shape but for a python pack.json that declares
|
|
29
|
-
// `formatter` on `typecheck` — needed for the buildManifest per-field-merge
|
|
30
|
-
// regression test (a pre-`formatter`-era existing manifest must still inherit it).
|
|
31
|
-
function makePythonRegistry() {
|
|
32
|
-
const registryRoot = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-reg-py-'));
|
|
33
|
-
const packDir = path_1.default.join(registryRoot, 'sensor-packs', 'python');
|
|
34
|
-
fs_1.default.mkdirSync(packDir, { recursive: true });
|
|
35
|
-
fs_1.default.writeFileSync(path_1.default.join(packDir, 'pack.json'), JSON.stringify({
|
|
36
|
-
name: 'python',
|
|
37
|
-
sensors: {
|
|
38
|
-
typecheck: { fast: true, defaultCmd: 'mypy .', formatter: 'mypy' },
|
|
39
|
-
lint: { fast: true, defaultCmd: 'ruff check --output-format=json .', formatter: 'ruff' },
|
|
40
|
-
},
|
|
41
|
-
}));
|
|
42
|
-
return registryRoot;
|
|
43
|
-
}
|
|
44
|
-
function makeV2Registry() {
|
|
45
|
-
const registryRoot = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-reg-v2-'));
|
|
46
|
-
const packDir = path_1.default.join(registryRoot, 'sensor-packs', 'js-ts');
|
|
47
|
-
fs_1.default.mkdirSync(packDir, { recursive: true });
|
|
48
|
-
fs_1.default.writeFileSync(path_1.default.join(packDir, 'eslint.config.awm.mjs'), 'export default []\n');
|
|
49
|
-
fs_1.default.writeFileSync(path_1.default.join(packDir, 'tsconfig.awm.json'), '{ "compilerOptions": { "strict": true } }\n');
|
|
50
|
-
fs_1.default.writeFileSync(path_1.default.join(packDir, 'pack.json'), JSON.stringify({
|
|
51
|
-
schemaVersion: 2, name: 'js-ts', description: 'fixture', detects: ['package.json'],
|
|
52
|
-
coverage: { schemaVersion: 1, classes: { lint: { description: 'lint', detectors: [{ sensor: 'lint' }], remedy: { summary: 'fix lint', command: 'awm sensors init --pack js-ts' } } } },
|
|
53
|
-
hardening: { 'typescript-strict': { assets: ['tsconfig.awm.json'] } },
|
|
54
|
-
sensors: { lint: { applicability: { allFiles: ['package.json'] }, variants: [{
|
|
55
|
-
id: 'eslint-10', priority: 10, certifiedRange: '>=10.0.0 <11.0.0',
|
|
56
|
-
requirements: { tool: 'eslint', toolRange: '>=10.0.0 <11.0.0', runtime: 'node', runtimeRange: '>=0.0.0' },
|
|
57
|
-
assets: ['eslint.config.awm.mjs'], formatter: 'eslint-llm', probe: { kind: 'config-present' },
|
|
58
|
-
command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] },
|
|
59
|
-
}] } },
|
|
60
|
-
}));
|
|
61
|
-
return registryRoot;
|
|
62
|
-
}
|
|
63
|
-
function makeGenericV2Registry() {
|
|
64
|
-
const registryRoot = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-reg-generic-v2-'));
|
|
65
|
-
const packDir = path_1.default.join(registryRoot, 'sensor-packs', 'generic');
|
|
66
|
-
fs_1.default.mkdirSync(packDir, { recursive: true });
|
|
67
|
-
fs_1.default.writeFileSync(path_1.default.join(packDir, 'generic.config'), 'fixture\n');
|
|
68
|
-
fs_1.default.writeFileSync(path_1.default.join(packDir, 'pack.json'), JSON.stringify({
|
|
69
|
-
schemaVersion: 2, name: 'generic', description: 'fixture', detects: ['README.md'],
|
|
70
|
-
coverage: { schemaVersion: 1, classes: { security: { description: 'security', detectors: [{ sensor: 'security' }], remedy: { summary: 'run security', command: 'awm sensors init --pack generic' } } } },
|
|
71
|
-
sensors: { security: { applicability: { kind: 'explicit-or-supported-language' }, variants: [{
|
|
72
|
-
id: 'eslint-10', priority: 10, certifiedRange: '>=10.0.0 <11.0.0',
|
|
73
|
-
requirements: { tool: 'eslint', toolRange: '>=10.0.0 <11.0.0', runtime: 'node', runtimeRange: '>=0.0.0', configFiles: ['generic.config'] },
|
|
74
|
-
assets: ['generic.config'], formatter: 'eslint-llm', probe: { kind: 'config-present' },
|
|
75
|
-
command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] },
|
|
76
|
-
}] } },
|
|
21
|
+
sensors: { typecheck: { fast: true, defaultCmd: 'npx tsc --noEmit', formatter: 'tsc' } },
|
|
77
22
|
}));
|
|
78
23
|
return registryRoot;
|
|
79
24
|
}
|
|
80
|
-
describe('
|
|
25
|
+
describe('legacy detection helpers', () => {
|
|
81
26
|
let tmpDir;
|
|
82
27
|
beforeEach(() => { tmpDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-init-')); });
|
|
83
|
-
afterEach(() => { fs_1.default.rmSync(tmpDir, { recursive: true }); });
|
|
84
|
-
it('detects
|
|
85
|
-
fs_1.default.writeFileSync(path_1.default.join(tmpDir, '
|
|
86
|
-
expect((0, init_1.detectStack)(tmpDir).pack).toBe('
|
|
87
|
-
});
|
|
88
|
-
it('detects python when pyproject.toml exists', async () => {
|
|
28
|
+
afterEach(() => { fs_1.default.rmSync(tmpDir, { recursive: true, force: true }); });
|
|
29
|
+
it('detects the supported project stacks in priority order', () => {
|
|
30
|
+
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'deploy.sh'), '#!/bin/sh\n');
|
|
31
|
+
expect((0, init_1.detectStack)(tmpDir).pack).toBe('shell');
|
|
89
32
|
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'pyproject.toml'), '');
|
|
90
33
|
expect((0, init_1.detectStack)(tmpDir).pack).toBe('python');
|
|
91
|
-
});
|
|
92
|
-
it('falls back to generic when no indicators found', async () => {
|
|
93
|
-
expect((0, init_1.detectStack)(tmpDir).pack).toBe('generic');
|
|
94
|
-
});
|
|
95
|
-
it('detects shell from a root-level *.sh file when no js-ts/python marker exists', async () => {
|
|
96
|
-
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'deploy.sh'), '#!/bin/sh\n');
|
|
97
|
-
const result = (0, init_1.detectStack)(tmpDir);
|
|
98
|
-
expect(result.pack).toBe('shell');
|
|
99
|
-
expect(result.indicators).toEqual(['deploy.sh']);
|
|
100
|
-
});
|
|
101
|
-
it('detects shell from a scripts/*.sh file when root has nothing', async () => {
|
|
102
|
-
fs_1.default.mkdirSync(path_1.default.join(tmpDir, 'scripts'));
|
|
103
|
-
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'scripts', 'build.sh'), '#!/bin/sh\n');
|
|
104
|
-
const result = (0, init_1.detectStack)(tmpDir);
|
|
105
|
-
expect(result.pack).toBe('shell');
|
|
106
|
-
expect(result.indicators).toEqual([path_1.default.join('scripts', 'build.sh')]);
|
|
107
|
-
});
|
|
108
|
-
it('js-ts wins over shell when both package.json and a root .sh file exist', async () => {
|
|
109
34
|
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'package.json'), '{}');
|
|
110
|
-
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'deploy.sh'), '#!/bin/sh\n');
|
|
111
35
|
expect((0, init_1.detectStack)(tmpDir).pack).toBe('js-ts');
|
|
112
36
|
});
|
|
113
|
-
it('
|
|
114
|
-
|
|
115
|
-
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'deploy.sh'), '#!/bin/sh\n');
|
|
116
|
-
expect((0, init_1.detectStack)(tmpDir).pack).toBe('python');
|
|
117
|
-
});
|
|
118
|
-
it('falls through to generic when scripts/ has only non-.sh files (glob must not over-match)', async () => {
|
|
119
|
-
fs_1.default.mkdirSync(path_1.default.join(tmpDir, 'scripts'));
|
|
120
|
-
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'scripts', 'notes.txt'), 'not shell');
|
|
121
|
-
expect((0, init_1.detectStack)(tmpDir).pack).toBe('generic');
|
|
122
|
-
});
|
|
123
|
-
it('detects python from requirements.txt alone', async () => {
|
|
124
|
-
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'requirements.txt'), '');
|
|
125
|
-
expect((0, init_1.detectStack)(tmpDir).pack).toBe('python');
|
|
126
|
-
});
|
|
127
|
-
it('detects python from Pipfile alone', async () => {
|
|
128
|
-
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'Pipfile'), '');
|
|
129
|
-
expect((0, init_1.detectStack)(tmpDir).pack).toBe('python');
|
|
130
|
-
});
|
|
131
|
-
it('python (via Pipfile) wins over shell when both a Pipfile and a root .sh file exist', async () => {
|
|
132
|
-
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'Pipfile'), '');
|
|
133
|
-
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'deploy.sh'), '#!/bin/sh\n');
|
|
134
|
-
expect((0, init_1.detectStack)(tmpDir).pack).toBe('python');
|
|
135
|
-
});
|
|
136
|
-
it('does not report a directory named "*.sh" as a shell indicator', async () => {
|
|
137
|
-
// Directory literally named `something.sh` (not a file) — findShellIndicators'
|
|
138
|
-
// `entry.isFile()` guard must exclude it. Nothing else present → generic.
|
|
139
|
-
fs_1.default.mkdirSync(path_1.default.join(tmpDir, 'something.sh'));
|
|
140
|
-
expect((0, init_1.detectStack)(tmpDir).pack).toBe('generic');
|
|
141
|
-
});
|
|
142
|
-
it('ignores a directory named "*.sh" but still finds a real .sh file alongside it', async () => {
|
|
143
|
-
fs_1.default.mkdirSync(path_1.default.join(tmpDir, 'notreal.sh'));
|
|
144
|
-
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'deploy.sh'), '#!/bin/sh\n');
|
|
145
|
-
const result = (0, init_1.detectStack)(tmpDir);
|
|
146
|
-
expect(result.pack).toBe('shell');
|
|
147
|
-
expect(result.indicators).toEqual(['deploy.sh']);
|
|
148
|
-
});
|
|
149
|
-
});
|
|
150
|
-
describe('detectSourceDirs', () => {
|
|
151
|
-
let tmpDir;
|
|
152
|
-
beforeEach(() => { tmpDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-src-')); });
|
|
153
|
-
afterEach(() => { fs_1.default.rmSync(tmpDir, { recursive: true }); });
|
|
154
|
-
it('returns the App-Router-style dirs that exist', async () => {
|
|
155
|
-
for (const d of ['app', 'lib', 'components'])
|
|
156
|
-
fs_1.default.mkdirSync(path_1.default.join(tmpDir, d));
|
|
157
|
-
expect((0, init_1.detectSourceDirs)(tmpDir)).toEqual(['app', 'lib', 'components']);
|
|
158
|
-
});
|
|
159
|
-
it('returns src when it exists', async () => {
|
|
160
|
-
fs_1.default.mkdirSync(path_1.default.join(tmpDir, 'src'));
|
|
161
|
-
expect((0, init_1.detectSourceDirs)(tmpDir)).toEqual(['src']);
|
|
162
|
-
});
|
|
163
|
-
it('falls back to ["src"] when no known source dir exists', async () => {
|
|
164
|
-
expect((0, init_1.detectSourceDirs)(tmpDir)).toEqual(['src']);
|
|
165
|
-
});
|
|
166
|
-
});
|
|
167
|
-
describe('buildManifest', () => {
|
|
168
|
-
let registryRoot;
|
|
169
|
-
let cwd;
|
|
170
|
-
beforeEach(() => {
|
|
171
|
-
registryRoot = makeRegistry();
|
|
172
|
-
cwd = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-cwd-'));
|
|
173
|
-
});
|
|
174
|
-
afterEach(() => {
|
|
175
|
-
fs_1.default.rmSync(registryRoot, { recursive: true });
|
|
176
|
-
fs_1.default.rmSync(cwd, { recursive: true });
|
|
177
|
-
});
|
|
178
|
-
it('builds manifest from the pack.json single source', async () => {
|
|
179
|
-
const m = (0, init_1.buildManifest)('js-ts', undefined, registryRoot, cwd);
|
|
180
|
-
expect(m.pack).toBe('js-ts');
|
|
181
|
-
expect(m.sensors.typecheck.cmd).toBe('npx tsc --noEmit');
|
|
182
|
-
expect(m.sensors.lint.cmd).toContain('--config eslint.config.awm.mjs');
|
|
183
|
-
});
|
|
184
|
-
it('substitutes {{SOURCE_DIRS}} in depcheck with the detected dirs', async () => {
|
|
185
|
-
for (const d of ['app', 'lib'])
|
|
186
|
-
fs_1.default.mkdirSync(path_1.default.join(cwd, d));
|
|
187
|
-
const m = (0, init_1.buildManifest)('js-ts', undefined, registryRoot, cwd);
|
|
188
|
-
expect(m.sensors.depcheck.cmd).toBe('npx depcruise --config .dep-cruiser.awm.js app lib');
|
|
189
|
-
expect(m.sensors.depcheck.cmd).not.toContain('{{SOURCE_DIRS}}');
|
|
190
|
-
});
|
|
191
|
-
it('merges conservatively — existing sensor commands are preserved', async () => {
|
|
192
|
-
const existing = { pack: 'js-ts', sensors: { typecheck: { cmd: 'custom-tsc', fast: true } } };
|
|
193
|
-
const m = (0, init_1.buildManifest)('js-ts', existing, registryRoot, cwd);
|
|
194
|
-
expect(m.sensors.typecheck.cmd).toBe('custom-tsc');
|
|
195
|
-
expect(m.sensors.lint).toBeDefined();
|
|
196
|
-
});
|
|
197
|
-
it('carries the formatter field through from pack.json into the built manifest', async () => {
|
|
198
|
-
// readPackDefaults must copy `formatter` the same way it already copies
|
|
199
|
-
// `changedCmd`/`changedExtensions` — this is what lets run.ts's getFormatter
|
|
200
|
-
// dispatch by real tool (ruff/mypy/shellcheck) instead of guessing from the
|
|
201
|
-
// sensor name. Without this carry-through the field is read from pack.json but
|
|
202
|
-
// silently dropped before it ever reaches the manifest run.ts consumes.
|
|
203
|
-
const m = (0, init_1.buildManifest)('js-ts', undefined, registryRoot, cwd);
|
|
204
|
-
expect(m.sensors.typecheck.formatter).toBe('tsc');
|
|
205
|
-
expect(m.sensors.lint.formatter).toBe('eslint-llm');
|
|
206
|
-
});
|
|
207
|
-
it('returns an empty sensors object when the pack has no pack.json in the registry', async () => {
|
|
208
|
-
// No FALLBACK_DEFAULTS anymore: `python` has no pack dir in this fixture
|
|
209
|
-
// registry (only js-ts does — see makeRegistry) → the honest floor is `{}`,
|
|
210
|
-
// never CLI-hardcoded commands that can drift from what the registry ships.
|
|
211
|
-
const m = (0, init_1.buildManifest)('python', undefined, registryRoot, cwd);
|
|
212
|
-
expect(m.sensors).toEqual({});
|
|
213
|
-
});
|
|
214
|
-
it('per-field merge: an existing sensor missing a newer pack field still inherits it', async () => {
|
|
215
|
-
// Regression for Finding 1: a manifest written by the old FALLBACK_DEFAULTS-era
|
|
216
|
-
// CLI has `typecheck: { cmd: 'mypy .', fast: true }` — no `formatter`, because
|
|
217
|
-
// that field didn't exist yet. A naive `{ ...defaults, ...existingSensors }`
|
|
218
|
-
// whole-sensor-object merge would replace `defaults.typecheck` wholesale,
|
|
219
|
-
// permanently dropping `formatter` even though the (upgraded) pack now declares
|
|
220
|
-
// it. The fix merges per FIELD within each sensor, so `formatter` — a field the
|
|
221
|
-
// existing manifest never specified — is inherited from the pack default.
|
|
222
|
-
const pyRegistryRoot = makePythonRegistry();
|
|
37
|
+
it('uses detected source directories when reading a legacy pack default', () => {
|
|
38
|
+
const registryRoot = makeRegistry();
|
|
223
39
|
try {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
expect(m.sensors.typecheck.formatter).toBe('mypy');
|
|
230
|
-
expect(m.sensors.typecheck.cmd).toBe('mypy .');
|
|
231
|
-
expect(m.sensors.typecheck.fast).toBe(true);
|
|
40
|
+
fs_1.default.mkdirSync(path_1.default.join(tmpDir, 'app'));
|
|
41
|
+
expect((0, init_1.detectSourceDirs)(tmpDir)).toEqual(['app']);
|
|
42
|
+
expect((0, init_1.buildManifest)('js-ts', undefined, registryRoot, tmpDir).sensors.typecheck).toMatchObject({
|
|
43
|
+
cmd: 'npx tsc --noEmit', formatter: 'tsc', fast: true,
|
|
44
|
+
});
|
|
232
45
|
}
|
|
233
46
|
finally {
|
|
234
|
-
fs_1.default.rmSync(
|
|
47
|
+
fs_1.default.rmSync(registryRoot, { recursive: true, force: true });
|
|
235
48
|
}
|
|
236
49
|
});
|
|
237
50
|
});
|
|
238
|
-
describe('initSensors', () => {
|
|
51
|
+
describe('initSensors compatibility API', () => {
|
|
239
52
|
let tmpDir;
|
|
240
53
|
let registryRoot;
|
|
241
54
|
beforeEach(() => {
|
|
55
|
+
jest.clearAllMocks();
|
|
242
56
|
tmpDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-init-'));
|
|
243
57
|
registryRoot = makeRegistry();
|
|
244
|
-
});
|
|
245
|
-
afterEach(() => {
|
|
246
|
-
fs_1.default.rmSync(tmpDir, { recursive: true });
|
|
247
|
-
fs_1.default.rmSync(registryRoot, { recursive: true });
|
|
248
|
-
});
|
|
249
|
-
it('creates .awm/sensors.json for js-ts project', async () => {
|
|
250
|
-
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'package.json'), '{}');
|
|
251
|
-
const result = await (0, init_1.initSensors)({ cwd: tmpDir, registryRoot });
|
|
252
|
-
expect(fs_1.default.existsSync(path_1.default.join(tmpDir, '.awm', 'sensors.json'))).toBe(true);
|
|
253
|
-
expect(result.detection.pack).toBe('js-ts');
|
|
254
|
-
expect(result.manifest.sensors.typecheck).toBeDefined();
|
|
255
|
-
});
|
|
256
|
-
it('is idempotent — existing sensor commands survive re-init', async () => {
|
|
257
|
-
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'package.json'), '{}');
|
|
258
|
-
fs_1.default.mkdirSync(path_1.default.join(tmpDir, '.awm'), { recursive: true });
|
|
259
|
-
const existing = { pack: 'js-ts', sensors: { typecheck: { cmd: 'my-tsc', fast: true } } };
|
|
260
|
-
fs_1.default.writeFileSync(path_1.default.join(tmpDir, '.awm', 'sensors.json'), JSON.stringify(existing));
|
|
261
|
-
await (0, init_1.initSensors)({ cwd: tmpDir, registryRoot });
|
|
262
|
-
const written = JSON.parse(fs_1.default.readFileSync(path_1.default.join(tmpDir, '.awm', 'sensors.json'), 'utf-8'));
|
|
263
|
-
expect(written.sensors.typecheck.cmd).toBe('my-tsc');
|
|
264
|
-
expect(written.sensors.lint).toBeDefined(); // new sensor added
|
|
265
|
-
});
|
|
266
|
-
it('copies pack config files into the repo by default (configure on)', async () => {
|
|
267
58
|
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'package.json'), '{}');
|
|
268
|
-
fs_1.default.writeFileSync(path_1.default.join(registryRoot, 'sensor-packs', 'js-ts', 'tsconfig.awm.json'), '{}');
|
|
269
|
-
const result = await (0, init_1.initSensors)({ cwd: tmpDir, registryRoot }); // no explicit configure → default true
|
|
270
|
-
expect(result.configured).toContain('tsconfig.awm.json');
|
|
271
|
-
expect(fs_1.default.existsSync(path_1.default.join(tmpDir, 'tsconfig.awm.json'))).toBe(true);
|
|
272
|
-
});
|
|
273
|
-
it('does NOT copy config files when configure is false', async () => {
|
|
274
|
-
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'package.json'), '{}');
|
|
275
|
-
fs_1.default.writeFileSync(path_1.default.join(registryRoot, 'sensor-packs', 'js-ts', 'tsconfig.awm.json'), '{}');
|
|
276
|
-
const result = await (0, init_1.initSensors)({ cwd: tmpDir, registryRoot, configure: false });
|
|
277
|
-
expect(result.configured).toEqual([]);
|
|
278
|
-
expect(fs_1.default.existsSync(path_1.default.join(tmpDir, 'tsconfig.awm.json'))).toBe(false);
|
|
279
|
-
});
|
|
280
|
-
it('awaits v2 compatibility probes and persists their materialized evidence', async () => {
|
|
281
|
-
const v2Registry = makeV2Registry();
|
|
282
|
-
try {
|
|
283
|
-
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'package.json'), JSON.stringify({ devDependencies: { eslint: '^10.0.0' } }));
|
|
284
|
-
fs_1.default.mkdirSync(path_1.default.join(tmpDir, 'node_modules', 'eslint'), { recursive: true });
|
|
285
|
-
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'node_modules', 'eslint', 'package.json'), JSON.stringify({ version: '10.0.0' }));
|
|
286
|
-
const result = await (0, init_1.initSensors)({ cwd: tmpDir, registryRoot: v2Registry });
|
|
287
|
-
const written = JSON.parse(fs_1.default.readFileSync(path_1.default.join(tmpDir, '.awm', 'sensors.json'), 'utf8'));
|
|
288
|
-
expect(result.manifest).toMatchObject({ schemaVersion: 2, sensors: { lint: { variantId: 'eslint-10' } } });
|
|
289
|
-
// `package.json` is a declared detection file, so config-present matches.
|
|
290
|
-
// Without init awaiting the probe the resolver stays `unverifiable`; this
|
|
291
|
-
// exact assertion therefore distinguishes the async probe integration.
|
|
292
|
-
expect(written.sensors.lint.initializedCompatibility).toMatchObject({ variantId: 'eslint-10', state: 'certified', reason: 'range-and-probe' });
|
|
293
|
-
expect(written.registryRoot).toBe(v2Registry);
|
|
294
|
-
expect(fs_1.default.existsSync(path_1.default.join(tmpDir, 'eslint.config.awm.mjs'))).toBe(true);
|
|
295
|
-
}
|
|
296
|
-
finally {
|
|
297
|
-
fs_1.default.rmSync(v2Registry, { recursive: true, force: true });
|
|
298
|
-
}
|
|
299
|
-
});
|
|
300
|
-
it('persists an explicit generic selection for live revalidation without promoting automatic generic fallback', async () => {
|
|
301
|
-
const v2Registry = makeGenericV2Registry();
|
|
302
|
-
try {
|
|
303
|
-
fs_1.default.mkdirSync(path_1.default.join(tmpDir, 'node_modules', 'eslint'), { recursive: true });
|
|
304
|
-
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'node_modules', 'eslint', 'package.json'), JSON.stringify({ version: '10.0.0' }));
|
|
305
|
-
const automatic = await (0, init_1.initSensors)({ cwd: tmpDir, registryRoot: v2Registry, configure: false });
|
|
306
|
-
expect(automatic.manifest).toMatchObject({ schemaVersion: 2, pack: 'generic', sensors: {} });
|
|
307
|
-
expect(automatic.manifest.packSelection).toBeUndefined();
|
|
308
|
-
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'generic.config'), 'fixture\n');
|
|
309
|
-
const explicit = await (0, init_1.initSensors)({ cwd: tmpDir, registryRoot: v2Registry, pack: 'generic' });
|
|
310
|
-
const written = JSON.parse(fs_1.default.readFileSync(path_1.default.join(tmpDir, '.awm', 'sensors.json'), 'utf8'));
|
|
311
|
-
expect(explicit.manifest).toMatchObject({ schemaVersion: 2, pack: 'generic', packSelection: 'explicit', sensors: { security: { variantId: 'eslint-10' } } });
|
|
312
|
-
expect(written.packSelection).toBe('explicit');
|
|
313
|
-
await expect((0, status_1.computeSensorStatus)(tmpDir)).resolves.toMatchObject({ overall: 'DEGRADED', checks: { security: { ok: false } } });
|
|
314
|
-
}
|
|
315
|
-
finally {
|
|
316
|
-
fs_1.default.rmSync(v2Registry, { recursive: true, force: true });
|
|
317
|
-
}
|
|
318
|
-
});
|
|
319
|
-
it('never materializes an opt-in hardening asset during ordinary v2 init', async () => {
|
|
320
|
-
const v2Registry = makeV2Registry();
|
|
321
|
-
try {
|
|
322
|
-
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'package.json'), JSON.stringify({ devDependencies: { eslint: '^10.0.0' } }));
|
|
323
|
-
fs_1.default.mkdirSync(path_1.default.join(tmpDir, 'node_modules', 'eslint'), { recursive: true });
|
|
324
|
-
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'node_modules', 'eslint', 'package.json'), JSON.stringify({ version: '10.0.0' }));
|
|
325
|
-
await (0, init_1.initSensors)({ cwd: tmpDir, registryRoot: v2Registry });
|
|
326
|
-
expect(fs_1.default.existsSync(path_1.default.join(tmpDir, 'eslint.config.awm.mjs'))).toBe(true);
|
|
327
|
-
expect(fs_1.default.existsSync(path_1.default.join(tmpDir, 'tsconfig.awm.json'))).toBe(false);
|
|
328
|
-
}
|
|
329
|
-
finally {
|
|
330
|
-
fs_1.default.rmSync(v2Registry, { recursive: true, force: true });
|
|
331
|
-
}
|
|
332
|
-
});
|
|
333
|
-
it('initializes a selected v2 variant with an explicitly empty assets list', async () => {
|
|
334
|
-
const v2Registry = makeV2Registry();
|
|
335
|
-
try {
|
|
336
|
-
const packPath = path_1.default.join(v2Registry, 'sensor-packs', 'js-ts', 'pack.json');
|
|
337
|
-
const pack = JSON.parse(fs_1.default.readFileSync(packPath, 'utf8'));
|
|
338
|
-
pack.sensors.lint.variants[0].assets = [];
|
|
339
|
-
fs_1.default.writeFileSync(packPath, JSON.stringify(pack));
|
|
340
|
-
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'package.json'), JSON.stringify({ devDependencies: { eslint: '^10.0.0' } }));
|
|
341
|
-
fs_1.default.mkdirSync(path_1.default.join(tmpDir, 'node_modules', 'eslint'), { recursive: true });
|
|
342
|
-
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'node_modules', 'eslint', 'package.json'), JSON.stringify({ version: '10.0.0' }));
|
|
343
|
-
await expect((0, init_1.initSensors)({ cwd: tmpDir, registryRoot: v2Registry })).resolves.toMatchObject({
|
|
344
|
-
manifest: { schemaVersion: 2, sensors: { lint: { assets: [] } } },
|
|
345
|
-
configured: [],
|
|
346
|
-
});
|
|
347
|
-
expect(fs_1.default.existsSync(path_1.default.join(tmpDir, 'eslint.config.awm.mjs'))).toBe(false);
|
|
348
|
-
}
|
|
349
|
-
finally {
|
|
350
|
-
fs_1.default.rmSync(v2Registry, { recursive: true, force: true });
|
|
351
|
-
}
|
|
352
|
-
});
|
|
353
|
-
it('preserves enabled and fast choices when a valid v2 manifest is re-initialized', async () => {
|
|
354
|
-
const v2Registry = makeV2Registry();
|
|
355
|
-
try {
|
|
356
|
-
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'package.json'), JSON.stringify({ devDependencies: { eslint: '^10.0.0' } }));
|
|
357
|
-
fs_1.default.mkdirSync(path_1.default.join(tmpDir, 'node_modules', 'eslint'), { recursive: true });
|
|
358
|
-
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'node_modules', 'eslint', 'package.json'), JSON.stringify({ version: '10.0.0' }));
|
|
359
|
-
await (0, init_1.initSensors)({ cwd: tmpDir, registryRoot: v2Registry });
|
|
360
|
-
const manifestPath = path_1.default.join(tmpDir, '.awm', 'sensors.json');
|
|
361
|
-
const selected = JSON.parse(fs_1.default.readFileSync(manifestPath, 'utf8'));
|
|
362
|
-
selected.sensors.lint.enabled = false;
|
|
363
|
-
selected.sensors.lint.fast = true;
|
|
364
|
-
fs_1.default.writeFileSync(manifestPath, JSON.stringify(selected));
|
|
365
|
-
await (0, init_1.initSensors)({ cwd: tmpDir, registryRoot: v2Registry });
|
|
366
|
-
const written = JSON.parse(fs_1.default.readFileSync(manifestPath, 'utf8'));
|
|
367
|
-
expect(written.sensors.lint).toMatchObject({ enabled: false, fast: true, variantId: 'eslint-10' });
|
|
368
|
-
}
|
|
369
|
-
finally {
|
|
370
|
-
fs_1.default.rmSync(v2Registry, { recursive: true, force: true });
|
|
371
|
-
}
|
|
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
|
-
});
|
|
402
|
-
it('rejects a symlinked v2 pack source instead of reading it through init', async () => {
|
|
403
|
-
const v2Registry = makeV2Registry();
|
|
404
|
-
try {
|
|
405
|
-
const packPath = path_1.default.join(v2Registry, 'sensor-packs', 'js-ts', 'pack.json');
|
|
406
|
-
const outside = path_1.default.join(v2Registry, 'outside-pack.json');
|
|
407
|
-
fs_1.default.renameSync(packPath, outside);
|
|
408
|
-
fs_1.default.symlinkSync(outside, packPath);
|
|
409
|
-
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'package.json'), '{}');
|
|
410
|
-
await expect((0, init_1.initSensors)({ cwd: tmpDir, registryRoot: v2Registry })).rejects.toThrow(/symbolic|regular|contain/i);
|
|
411
|
-
expect(fs_1.default.existsSync(path_1.default.join(tmpDir, '.awm', 'sensors.json'))).toBe(false);
|
|
412
|
-
}
|
|
413
|
-
finally {
|
|
414
|
-
fs_1.default.rmSync(v2Registry, { recursive: true, force: true });
|
|
415
|
-
}
|
|
416
|
-
});
|
|
417
|
-
it('migrates legacy overrides explicitly without silently certifying or re-enabling them', async () => {
|
|
418
|
-
const v2Registry = makeV2Registry();
|
|
419
|
-
try {
|
|
420
|
-
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'package.json'), JSON.stringify({ devDependencies: { eslint: '^10.0.0' } }));
|
|
421
|
-
fs_1.default.mkdirSync(path_1.default.join(tmpDir, 'node_modules', 'eslint'), { recursive: true });
|
|
422
|
-
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'node_modules', 'eslint', 'package.json'), JSON.stringify({ version: '10.0.0' }));
|
|
423
|
-
fs_1.default.mkdirSync(path_1.default.join(tmpDir, '.awm'));
|
|
424
|
-
fs_1.default.writeFileSync(path_1.default.join(tmpDir, '.awm', 'sensors.json'), JSON.stringify({
|
|
425
|
-
pack: 'js-ts', sensors: { lint: { cmd: 'my-company-eslint .', enabled: false, fast: true } },
|
|
426
|
-
}));
|
|
427
|
-
await (0, init_1.initSensors)({ cwd: tmpDir, registryRoot: v2Registry });
|
|
428
|
-
const written = JSON.parse(fs_1.default.readFileSync(path_1.default.join(tmpDir, '.awm', 'sensors.json'), 'utf8'));
|
|
429
|
-
expect(written.sensors.lint).toMatchObject({ enabled: false, fast: true });
|
|
430
|
-
expect(written.sensors.lint.initializedCompatibility).toMatchObject({ state: 'compatible-unverified', reason: expect.stringContaining('legacy custom command') });
|
|
431
|
-
}
|
|
432
|
-
finally {
|
|
433
|
-
fs_1.default.rmSync(v2Registry, { recursive: true, force: true });
|
|
434
|
-
}
|
|
435
|
-
});
|
|
436
|
-
});
|
|
437
|
-
describe('initSensors — packageRoot (monorepo)', () => {
|
|
438
|
-
let tmpDir;
|
|
439
|
-
beforeEach(() => {
|
|
440
|
-
tmpDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-init-monorepo-'));
|
|
441
59
|
});
|
|
442
60
|
afterEach(() => {
|
|
443
61
|
fs_1.default.rmSync(tmpDir, { recursive: true, force: true });
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
await (0, init_1.initSensors)({ cwd: tmpDir, registryRoot: v2Registry, packageRoot: 'cli' });
|
|
479
|
-
// The asset must land where the lint sensor will actually execute (cwd=cli),
|
|
480
|
-
// not at the manifest's own directory (repo root) — otherwise a config-relative
|
|
481
|
-
// tool invocation can never find it.
|
|
482
|
-
expect(fs_1.default.existsSync(path_1.default.join(packageDir, 'eslint.config.awm.mjs'))).toBe(true);
|
|
483
|
-
expect(fs_1.default.existsSync(path_1.default.join(tmpDir, 'eslint.config.awm.mjs'))).toBe(false);
|
|
484
|
-
}
|
|
485
|
-
finally {
|
|
486
|
-
fs_1.default.rmSync(v2Registry, { recursive: true, force: true });
|
|
487
|
-
}
|
|
488
|
-
});
|
|
489
|
-
});
|
|
490
|
-
describe('initSensors — --pack override', () => {
|
|
491
|
-
let tmpDir;
|
|
492
|
-
let registryRoot;
|
|
493
|
-
beforeEach(() => {
|
|
494
|
-
tmpDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-init-pack-'));
|
|
495
|
-
registryRoot = makeRegistry(); // only ships a js-ts pack dir — see makeRegistry
|
|
496
|
-
});
|
|
497
|
-
afterEach(() => {
|
|
498
|
-
fs_1.default.rmSync(tmpDir, { recursive: true });
|
|
499
|
-
fs_1.default.rmSync(registryRoot, { recursive: true });
|
|
500
|
-
});
|
|
501
|
-
it('skips detection and uses the override pack when it exists in the registry', async () => {
|
|
502
|
-
// No package.json/pyproject.toml here — if detection ran, this would be 'generic'.
|
|
503
|
-
const result = await (0, init_1.initSensors)({ pack: 'js-ts', registryRoot, cwd: tmpDir });
|
|
504
|
-
expect(result.detection.pack).toBe('js-ts');
|
|
505
|
-
// Indicators must reflect an override, not file-based detection.
|
|
506
|
-
expect(result.detection.indicators).not.toEqual(['package.json']);
|
|
507
|
-
expect(result.detection.indicators.join(' ')).toMatch(/pack override/i);
|
|
508
|
-
});
|
|
509
|
-
it('throws listing available packs when the override pack is not in the registry', async () => {
|
|
510
|
-
await expect((0, init_1.initSensors)({ pack: 'bogus', registryRoot, cwd: tmpDir })).rejects.toThrow(/js-ts/);
|
|
511
|
-
await expect((0, init_1.initSensors)({ pack: 'bogus', registryRoot, cwd: tmpDir })).rejects.toThrow(/bogus/);
|
|
512
|
-
});
|
|
513
|
-
it('does not throw when no registryRoot is given — nothing to validate against', async () => {
|
|
514
|
-
await expect((0, init_1.initSensors)({ pack: 'anything', cwd: tmpDir })).resolves.toBeDefined();
|
|
515
|
-
const result = await (0, init_1.initSensors)({ pack: 'anything', cwd: tmpDir });
|
|
516
|
-
expect(result.detection.pack).toBe('anything');
|
|
517
|
-
});
|
|
518
|
-
it('throws a distinct message when the registry root has no sensor-packs directory at all', async () => {
|
|
519
|
-
// Different failure shape from "pack not in the list": the registry root
|
|
520
|
-
// itself is missing sensor-packs/, so there's no list to show — must say
|
|
521
|
-
// so plainly instead of reporting an empty `available: `.
|
|
522
|
-
const emptyRegistryRoot = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-empty-reg-'));
|
|
523
|
-
try {
|
|
524
|
-
await expect((0, init_1.initSensors)({ pack: 'js-ts', registryRoot: emptyRegistryRoot, cwd: tmpDir }))
|
|
525
|
-
.rejects.toThrow(/no sensor-packs directory/);
|
|
526
|
-
}
|
|
527
|
-
finally {
|
|
528
|
-
fs_1.default.rmSync(emptyRegistryRoot, { recursive: true });
|
|
529
|
-
}
|
|
62
|
+
fs_1.default.rmSync(registryRoot, { recursive: true, force: true });
|
|
63
|
+
});
|
|
64
|
+
it('delegates compatibility creation to the portable bootstrap without persisting registryRoot', async () => {
|
|
65
|
+
const manifest = { schemaVersion: 3, mode: 'project-sensors', pack: 'js-ts', source: { registry: 'baseline' }, sensors: {} };
|
|
66
|
+
bootstrap_1.planSensorBootstrap.mockResolvedValue({
|
|
67
|
+
kind: 'create', projectRoot: tmpDir, manifestPath: path_1.default.join(tmpDir, '.awm', 'sensors.json'),
|
|
68
|
+
changes: [{ path: '.awm/sensors.json', action: 'create' }], dryRun: false, manifest,
|
|
69
|
+
});
|
|
70
|
+
bootstrap_1.applySensorBootstrap.mockReturnValue('created');
|
|
71
|
+
const result = await (0, init_1.initSensors)({ cwd: tmpDir, registryRoot, configure: false });
|
|
72
|
+
expect(bootstrap_1.planSensorBootstrap).toHaveBeenCalledWith(tmpDir, {
|
|
73
|
+
mode: 'project-sensors', registryRoot, configure: false, pack: undefined, packageRoot: undefined,
|
|
74
|
+
});
|
|
75
|
+
expect(bootstrap_1.applySensorBootstrap).toHaveBeenCalledWith(expect.objectContaining({ kind: 'create', manifest }));
|
|
76
|
+
expect(JSON.stringify(result.manifest)).not.toContain(registryRoot);
|
|
77
|
+
});
|
|
78
|
+
it('keeps an already portable declaration as a no-op', async () => {
|
|
79
|
+
bootstrap_1.planSensorBootstrap.mockResolvedValue({
|
|
80
|
+
kind: 'noop', projectRoot: tmpDir, manifestPath: path_1.default.join(tmpDir, '.awm', 'sensors.json'), changes: [], dryRun: false,
|
|
81
|
+
});
|
|
82
|
+
await expect((0, init_1.initSensors)({ cwd: tmpDir })).resolves.toMatchObject({ status: 'already-configured', configured: [] });
|
|
83
|
+
expect(bootstrap_1.applySensorBootstrap).not.toHaveBeenCalled();
|
|
84
|
+
});
|
|
85
|
+
it('surfaces blocked or migration plans instead of falling back to v2 materialization', async () => {
|
|
86
|
+
bootstrap_1.planSensorBootstrap.mockResolvedValueOnce({
|
|
87
|
+
kind: 'blocked', projectRoot: tmpDir, manifestPath: path_1.default.join(tmpDir, '.awm', 'sensors.json'), changes: [], dryRun: false,
|
|
88
|
+
reason: 'source-unavailable', remedy: 'install-registry-or-run-awm-update',
|
|
89
|
+
}).mockResolvedValueOnce({
|
|
90
|
+
kind: 'migrate', projectRoot: tmpDir, manifestPath: path_1.default.join(tmpDir, '.awm', 'sensors.json'),
|
|
91
|
+
changes: [{ path: '.awm/sensors.json', action: 'replace' }], dryRun: false, migration: {}, source: {}, originalDigest: 'digest',
|
|
92
|
+
});
|
|
93
|
+
await expect((0, init_1.initSensors)({ cwd: tmpDir })).rejects.toThrow('source-unavailable: install-registry-or-run-awm-update');
|
|
94
|
+
await expect((0, init_1.initSensors)({ cwd: tmpDir })).rejects.toThrow('does not migrate');
|
|
95
|
+
expect(bootstrap_1.applySensorBootstrap).not.toHaveBeenCalled();
|
|
530
96
|
});
|
|
531
97
|
});
|