agentic-workflow-manager 9.3.0 → 9.4.0
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/plan/index.js +98 -0
- package/dist/src/commands/preflight/checks.js +64 -4
- package/dist/src/commands/preflight/index.js +5 -1
- package/dist/src/core/currentness/check.js +224 -0
- package/dist/src/core/currentness/types.js +2 -0
- package/dist/src/core/plan/json.js +111 -0
- package/dist/src/core/plan/types.js +2 -0
- package/dist/src/core/plan/validate.js +393 -0
- package/dist/src/index.js +7 -1
- package/dist/tests/commands/plan/index.test.js +154 -0
- package/dist/tests/commands/preflight/preflight.test.js +93 -2
- package/dist/tests/core/currentness/check.test.js +293 -0
- package/dist/tests/core/plan/validate.test.js +437 -0
- package/dist/tests/integration/preflight-json-pipe.e2e.test.js +38 -0
- package/dist/tests/structural/r4-plan-currentness-docs.test.js +37 -0
- package/package.json +1 -1
|
@@ -10,6 +10,7 @@ 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
12
|
const run_1 = require("../../../src/commands/sensors/run");
|
|
13
|
+
const check_1 = require("../../../src/core/currentness/check");
|
|
13
14
|
// Only `execSync` (used by `resolveOnPath` to check for `gh`/`glab`) is mocked — `git
|
|
14
15
|
// remote get-url origin` runs for real via `execFileSync` against real tmpdir git repos,
|
|
15
16
|
// same as every other check in this file exercises the real filesystem.
|
|
@@ -21,8 +22,12 @@ jest.mock('../../../src/commands/sensors/run', () => ({
|
|
|
21
22
|
...jest.requireActual('../../../src/commands/sensors/run'),
|
|
22
23
|
runSensors: jest.fn(),
|
|
23
24
|
}));
|
|
25
|
+
jest.mock('../../../src/core/currentness/check', () => ({
|
|
26
|
+
checkCurrentness: jest.fn(),
|
|
27
|
+
}));
|
|
24
28
|
const mockExecSync = child_process_1.execSync;
|
|
25
29
|
const mockRunSensors = run_1.runSensors;
|
|
30
|
+
const mockCheckCurrentness = check_1.checkCurrentness;
|
|
26
31
|
/** Turn a tmpdir into a real git repo with (optionally) an `origin` remote. */
|
|
27
32
|
function gitRepo(dir, remoteUrl) {
|
|
28
33
|
(0, child_process_1.execFileSync)('git', ['init'], { cwd: dir, stdio: 'pipe' });
|
|
@@ -99,7 +104,22 @@ function writePartialKernel(dir) {
|
|
|
99
104
|
}
|
|
100
105
|
const check = (r, id) => r.checks.find(c => c.id === id);
|
|
101
106
|
describe('preflight', () => {
|
|
102
|
-
|
|
107
|
+
beforeEach(() => {
|
|
108
|
+
mockCheckCurrentness.mockResolvedValue({
|
|
109
|
+
checkedAt: '2026-08-26T00:00:00.000Z',
|
|
110
|
+
compatibility: { status: 'not-checked' },
|
|
111
|
+
components: [{
|
|
112
|
+
component: 'cli', installed: '9.3.0', latest: '9.3.0', channel: 'stable',
|
|
113
|
+
source: 'https://registry.npmjs.org/agentic-workflow-manager',
|
|
114
|
+
checkedAt: '2026-08-26T00:00:00.000Z', status: 'current',
|
|
115
|
+
detail: 'Installed version matches npm dist-tags.latest.', remedy: 'No action required.',
|
|
116
|
+
}],
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
afterEach(() => {
|
|
120
|
+
mockRunSensors.mockReset();
|
|
121
|
+
mockCheckCurrentness.mockReset();
|
|
122
|
+
});
|
|
103
123
|
it('does not add a row when no registry declares the schema', async () => {
|
|
104
124
|
installRegistry({ minCliVersion: '9.2.1' });
|
|
105
125
|
const report = await (0, checks_1.preflight)(make({
|
|
@@ -205,10 +225,81 @@ describe('preflight', () => {
|
|
|
205
225
|
});
|
|
206
226
|
await (0, checks_1.preflight)(dir);
|
|
207
227
|
expect(mockRunSensors).not.toHaveBeenCalled();
|
|
228
|
+
expect(mockCheckCurrentness).not.toHaveBeenCalled();
|
|
229
|
+
});
|
|
230
|
+
it('adds authoritative currentness only when strict mode is explicitly requested', async () => {
|
|
231
|
+
const dir = make({ manifest: { pack: 'generic', sensors: { security: { enabled: false } } } });
|
|
232
|
+
const report = await (0, checks_1.preflight)(dir, { requireCurrent: true });
|
|
233
|
+
expect(mockCheckCurrentness).toHaveBeenCalledWith(dir);
|
|
234
|
+
expect(report).toMatchObject({
|
|
235
|
+
status: 'ready',
|
|
236
|
+
currentness: expect.objectContaining({
|
|
237
|
+
components: [expect.objectContaining({ component: 'cli', status: 'current' })],
|
|
238
|
+
}),
|
|
239
|
+
});
|
|
240
|
+
expect(report.checks).toContainEqual(expect.objectContaining({ id: 'currentness', ok: true }));
|
|
241
|
+
expect((0, preflight_1.formatReport)(report)).toContain('Currentness: current');
|
|
242
|
+
expect(JSON.parse(JSON.stringify(report))).toHaveProperty('currentness.components');
|
|
243
|
+
});
|
|
244
|
+
it('returns a complete strict report instead of throwing for a malformed local registry inventory', async () => {
|
|
245
|
+
fs_1.default.writeFileSync(path_1.default.join(process.env.AWM_HOME, 'registries.json'), '{not-json');
|
|
246
|
+
mockCheckCurrentness.mockResolvedValue({
|
|
247
|
+
checkedAt: '2026-08-26T00:00:00.000Z', compatibility: { status: 'not-checked' },
|
|
248
|
+
components: [{
|
|
249
|
+
component: 'registry:inventory', installed: null, latest: null, channel: 'stable',
|
|
250
|
+
source: '[configured registry inventory]', checkedAt: '2026-08-26T00:00:00.000Z', status: 'unverifiable',
|
|
251
|
+
detail: 'Authoritative currentness could not be verified.',
|
|
252
|
+
remedy: 'Repair the local registry inventory and rerun strict preflight.',
|
|
253
|
+
}],
|
|
254
|
+
});
|
|
255
|
+
const report = await (0, checks_1.preflight)(make({ manifest: { pack: 'generic', sensors: { security: { enabled: false } } } }), { requireCurrent: true });
|
|
256
|
+
expect(report.status).toBe('degraded');
|
|
257
|
+
expect(report.currentness?.components).toEqual(expect.arrayContaining([
|
|
258
|
+
expect.objectContaining({ component: 'registry:inventory', status: 'unverifiable', remedy: expect.stringMatching(/Repair the local registry inventory/) }),
|
|
259
|
+
]));
|
|
260
|
+
expect(JSON.parse(JSON.stringify(report))).toHaveProperty('currentness.components');
|
|
261
|
+
});
|
|
262
|
+
it.each(['stale', 'pinned-behind', 'unverifiable'])('fails strict preflight for a %s component with its exact remedy', async (status) => {
|
|
263
|
+
const dir = make({ manifest: { pack: 'generic', sensors: { security: { enabled: false } } } });
|
|
264
|
+
mockCheckCurrentness.mockResolvedValue({
|
|
265
|
+
checkedAt: '2026-08-26T00:00:00.000Z', compatibility: { status: 'not-checked' },
|
|
266
|
+
components: [{
|
|
267
|
+
component: 'registry:baseline', installed: 'v1.0.0', latest: 'v1.1.0', channel: 'stable',
|
|
268
|
+
source: 'https://example.test/baseline.git', checkedAt: '2026-08-26T00:00:00.000Z', status,
|
|
269
|
+
detail: 'Authoritative currentness failed.',
|
|
270
|
+
remedy: status === 'pinned-behind' ? 'awm unpin baseline && awm update --yes' : 'awm update --yes',
|
|
271
|
+
}],
|
|
272
|
+
});
|
|
273
|
+
const report = await (0, checks_1.preflight)(dir, { requireCurrent: true });
|
|
274
|
+
expect(report.status).toBe('degraded');
|
|
275
|
+
expect((0, preflight_1.exitCodeFor)(report)).toBe(1);
|
|
276
|
+
expect(report.checks).toContainEqual(expect.objectContaining({ id: 'currentness', ok: false }));
|
|
277
|
+
expect((0, preflight_1.formatReport)(report)).toContain(status === 'pinned-behind' ? 'awm unpin baseline && awm update --yes' : 'awm update --yes');
|
|
278
|
+
});
|
|
279
|
+
it('keeps compatibility failure distinct when authoritative currentness is current', async () => {
|
|
280
|
+
installRegistry({ minCliVersion: '999.0.0' }, 'requires-new-cli');
|
|
281
|
+
const dir = make({ manifest: { pack: 'generic', sensors: { security: { enabled: false } } } });
|
|
282
|
+
const report = await (0, checks_1.preflight)(dir, { requireCurrent: true });
|
|
283
|
+
expect(report.status).toBe('degraded');
|
|
284
|
+
expect(report.checks).toEqual(expect.arrayContaining([
|
|
285
|
+
expect.objectContaining({ id: 'compatibility', ok: false, detail: expect.stringContaining('requires-new-cli requires CLI >= 999.0.0') }),
|
|
286
|
+
expect.objectContaining({ id: 'currentness', ok: true }),
|
|
287
|
+
]));
|
|
288
|
+
});
|
|
289
|
+
it('retains both empirical sensor and currentness verdicts when both gates are requested', async () => {
|
|
290
|
+
const dir = make({ manifest: { pack: 'generic', sensors: { security: { enabled: false } } } });
|
|
291
|
+
mockRunSensors.mockResolvedValue({ overall: 'pass', sensors: [] });
|
|
292
|
+
const report = await (0, checks_1.preflight)(dir, { verifySensors: true, requireCurrent: true });
|
|
293
|
+
expect(mockRunSensors).toHaveBeenCalledWith({ cwd: dir, all: true });
|
|
294
|
+
expect(mockCheckCurrentness).toHaveBeenCalledWith(dir);
|
|
295
|
+
expect(report.checks).toEqual(expect.arrayContaining([
|
|
296
|
+
expect.objectContaining({ id: 'sensors-execution', ok: true }),
|
|
297
|
+
expect.objectContaining({ id: 'currentness', ok: true }),
|
|
298
|
+
]));
|
|
208
299
|
});
|
|
209
300
|
it('rejects an array passed as public preflight options before filesystem work', async () => {
|
|
210
301
|
await expect((0, checks_1.preflight)(process.cwd(), []))
|
|
211
|
-
.rejects.toThrow('preflight options must contain
|
|
302
|
+
.rejects.toThrow('preflight options must contain optional boolean verifySensors and requireCurrent');
|
|
212
303
|
});
|
|
213
304
|
it('requires an empirical sensor pass when verification is requested', async () => {
|
|
214
305
|
const dir = make({
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const fs_1 = __importDefault(require("fs"));
|
|
7
|
+
const os_1 = __importDefault(require("os"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
describe('checkCurrentness', () => {
|
|
10
|
+
let root;
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-currentness-'));
|
|
13
|
+
jest.resetModules();
|
|
14
|
+
});
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
jest.useRealTimers();
|
|
17
|
+
fs_1.default.rmSync(root, { recursive: true, force: true });
|
|
18
|
+
});
|
|
19
|
+
function deps(overrides = {}) {
|
|
20
|
+
return {
|
|
21
|
+
cliVersion: () => '1.2.3',
|
|
22
|
+
now: () => 1_700_000_000_000,
|
|
23
|
+
fetch: jest.fn().mockResolvedValue({
|
|
24
|
+
ok: true,
|
|
25
|
+
text: async () => JSON.stringify({ 'dist-tags': { latest: '1.2.3' } }),
|
|
26
|
+
}),
|
|
27
|
+
readPreferences: () => ({ pins: {} }),
|
|
28
|
+
listRegistries: () => [{ name: 'baseline', remote: 'https://example.test/team/repo.git', contentRoot: '/registry' }],
|
|
29
|
+
git: jest.fn().mockImplementation(async (_cwd, args) => {
|
|
30
|
+
if (args[0] === 'ls-remote')
|
|
31
|
+
return `${'a'.repeat(40)}\trefs/tags/v1.2.3\n`;
|
|
32
|
+
if (args[0] === 'remote')
|
|
33
|
+
return 'https://example.test/team/repo.git\n';
|
|
34
|
+
if (args[0] === 'rev-parse')
|
|
35
|
+
return `${'a'.repeat(40)}\n`;
|
|
36
|
+
if (args[0] === 'describe')
|
|
37
|
+
return 'v1.2.3\n';
|
|
38
|
+
throw new Error(`unexpected git invocation: ${args.join(' ')}`);
|
|
39
|
+
}),
|
|
40
|
+
...overrides,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
it('reports authoritative current CLI and registry without exposing URL credentials', async () => {
|
|
44
|
+
const { checkCurrentness } = require('../../../src/core/currentness/check');
|
|
45
|
+
const result = await checkCurrentness(root, deps());
|
|
46
|
+
expect(result.components).toEqual([
|
|
47
|
+
expect.objectContaining({ component: 'cli', installed: '1.2.3', latest: '1.2.3', status: 'current', channel: 'stable' }),
|
|
48
|
+
expect.objectContaining({ component: 'registry:baseline', installed: 'v1.2.3', latest: 'v1.2.3', status: 'current', channel: 'stable' }),
|
|
49
|
+
]);
|
|
50
|
+
expect(JSON.stringify(result)).not.toContain('secret');
|
|
51
|
+
expect(result.components[1].source).toBe('https://example.test/team/repo.git');
|
|
52
|
+
});
|
|
53
|
+
it('marks an older pinned registry as pinned-behind with the unpin remedy', async () => {
|
|
54
|
+
const { checkCurrentness } = require('../../../src/core/currentness/check');
|
|
55
|
+
const result = await checkCurrentness(root, deps({
|
|
56
|
+
readPreferences: () => ({ pins: { baseline: '1.0.0' } }),
|
|
57
|
+
git: jest.fn().mockImplementation(async (_cwd, args) => {
|
|
58
|
+
if (args[0] === 'ls-remote')
|
|
59
|
+
return `${'b'.repeat(40)}\trefs/tags/v1.1.0\n${'a'.repeat(40)}\trefs/tags/v1.0.0\n`;
|
|
60
|
+
if (args[0] === 'remote')
|
|
61
|
+
return 'https://example.test/team/repo.git\n';
|
|
62
|
+
if (args[0] === 'rev-parse')
|
|
63
|
+
return `${'a'.repeat(40)}\n`;
|
|
64
|
+
if (args[0] === 'describe')
|
|
65
|
+
return 'v1.0.0\n';
|
|
66
|
+
throw new Error('unexpected');
|
|
67
|
+
}),
|
|
68
|
+
}));
|
|
69
|
+
expect(result.components[1]).toEqual(expect.objectContaining({
|
|
70
|
+
status: 'pinned-behind',
|
|
71
|
+
pin: '1.0.0',
|
|
72
|
+
remedy: 'awm unpin baseline && awm update --yes',
|
|
73
|
+
}));
|
|
74
|
+
});
|
|
75
|
+
it('marks an npm latest version above the max-safe numeric boundary as stale', async () => {
|
|
76
|
+
const { checkCurrentness } = require('../../../src/core/currentness/check');
|
|
77
|
+
const result = await checkCurrentness(root, deps({
|
|
78
|
+
cliVersion: () => '1.9007199254740992.0',
|
|
79
|
+
fetch: jest.fn().mockResolvedValue({
|
|
80
|
+
ok: true,
|
|
81
|
+
text: async () => JSON.stringify({ 'dist-tags': { latest: '1.9007199254740993.0' } }),
|
|
82
|
+
}),
|
|
83
|
+
}));
|
|
84
|
+
expect(result.components[0]).toEqual(expect.objectContaining({
|
|
85
|
+
installed: '1.9007199254740992.0',
|
|
86
|
+
latest: '1.9007199254740993.0',
|
|
87
|
+
status: 'stale',
|
|
88
|
+
}));
|
|
89
|
+
});
|
|
90
|
+
it('marks a registry tag above the max-safe numeric boundary as stale', async () => {
|
|
91
|
+
const installed = 'v1.9007199254740992.0';
|
|
92
|
+
const latest = 'v1.9007199254740993.0';
|
|
93
|
+
const { checkCurrentness } = require('../../../src/core/currentness/check');
|
|
94
|
+
const result = await checkCurrentness(root, deps({
|
|
95
|
+
git: jest.fn().mockImplementation(async (_cwd, args) => {
|
|
96
|
+
if (args[0] === 'ls-remote')
|
|
97
|
+
return `${'b'.repeat(40)}\trefs/tags/${latest}\n${'a'.repeat(40)}\trefs/tags/${installed}\n`;
|
|
98
|
+
if (args[0] === 'remote')
|
|
99
|
+
return 'https://example.test/team/repo.git\n';
|
|
100
|
+
if (args[0] === 'rev-parse')
|
|
101
|
+
return `${'a'.repeat(40)}\n`;
|
|
102
|
+
if (args[0] === 'describe')
|
|
103
|
+
return `${installed}\n`;
|
|
104
|
+
throw new Error('unexpected');
|
|
105
|
+
}),
|
|
106
|
+
}));
|
|
107
|
+
expect(result.components[1]).toEqual(expect.objectContaining({ installed, latest, status: 'stale' }));
|
|
108
|
+
});
|
|
109
|
+
it('discovers an unprefixed stable SemVer tag while ignoring prereleases', async () => {
|
|
110
|
+
const { checkCurrentness } = require('../../../src/core/currentness/check');
|
|
111
|
+
const result = await checkCurrentness(root, deps({
|
|
112
|
+
git: jest.fn().mockImplementation(async (_cwd, args) => {
|
|
113
|
+
if (args[0] === 'ls-remote')
|
|
114
|
+
return `${'a'.repeat(40)}\trefs/tags/1.2.4\n${'b'.repeat(40)}\trefs/tags/1.2.5-rc.1\n`;
|
|
115
|
+
if (args[0] === 'remote')
|
|
116
|
+
return 'https://example.test/team/repo.git\n';
|
|
117
|
+
if (args[0] === 'rev-parse')
|
|
118
|
+
return `${'a'.repeat(40)}\n`;
|
|
119
|
+
if (args[0] === 'describe')
|
|
120
|
+
return '1.2.4\n';
|
|
121
|
+
throw new Error('unexpected');
|
|
122
|
+
}),
|
|
123
|
+
}));
|
|
124
|
+
expect(result.components[1]).toEqual(expect.objectContaining({
|
|
125
|
+
installed: '1.2.4', latest: '1.2.4', status: 'current',
|
|
126
|
+
}));
|
|
127
|
+
});
|
|
128
|
+
it('marks a lower installed version stale even when it shares the latest tag SHA', async () => {
|
|
129
|
+
const sharedSha = 'a'.repeat(40);
|
|
130
|
+
const { checkCurrentness } = require('../../../src/core/currentness/check');
|
|
131
|
+
const result = await checkCurrentness(root, deps({
|
|
132
|
+
git: jest.fn().mockImplementation(async (_cwd, args) => {
|
|
133
|
+
if (args[0] === 'ls-remote')
|
|
134
|
+
return `${sharedSha}\trefs/tags/v1.2.4\n`;
|
|
135
|
+
if (args[0] === 'remote')
|
|
136
|
+
return 'https://example.test/team/repo.git\n';
|
|
137
|
+
if (args[0] === 'rev-parse')
|
|
138
|
+
return `${sharedSha}\n`;
|
|
139
|
+
if (args[0] === 'describe')
|
|
140
|
+
return 'v1.2.3\n';
|
|
141
|
+
throw new Error('unexpected');
|
|
142
|
+
}),
|
|
143
|
+
}));
|
|
144
|
+
expect(result.components[1]).toEqual(expect.objectContaining({
|
|
145
|
+
installed: 'v1.2.3', latest: 'v1.2.4', status: 'stale', remedy: 'awm update --yes',
|
|
146
|
+
}));
|
|
147
|
+
});
|
|
148
|
+
it.each([
|
|
149
|
+
['branch provenance', async (_cwd, args) => args[0] === 'describe' ? Promise.reject(new Error('not a tag')) : args[0] === 'ls-remote' ? `${'a'.repeat(40)}\trefs/tags/v1.2.3\n` : args[0] === 'remote' ? 'https://example.test/team/repo.git\n' : `${'a'.repeat(40)}\n`],
|
|
150
|
+
['malformed npm semver', undefined],
|
|
151
|
+
])('fails closed for %s', async (name, git) => {
|
|
152
|
+
const { checkCurrentness } = require('../../../src/core/currentness/check');
|
|
153
|
+
const result = await checkCurrentness(root, deps(name === 'malformed npm semver'
|
|
154
|
+
? { fetch: jest.fn().mockResolvedValue({ ok: true, text: async () => JSON.stringify({ 'dist-tags': { latest: 'latest' } }) }) }
|
|
155
|
+
: { git }));
|
|
156
|
+
expect(result.components.some((c) => c.status === 'unverifiable')).toBe(true);
|
|
157
|
+
});
|
|
158
|
+
it('bypasses passive update controls and makes no writes', async () => {
|
|
159
|
+
const { checkCurrentness } = require('../../../src/core/currentness/check');
|
|
160
|
+
const before = fs_1.default.readdirSync(root);
|
|
161
|
+
const fetch = jest.fn().mockResolvedValue({ ok: false, text: async () => '' });
|
|
162
|
+
const result = await checkCurrentness(root, deps({ fetch, env: { AWM_NO_UPDATE_CHECK: '1' } }));
|
|
163
|
+
expect(fetch).toHaveBeenCalledTimes(1);
|
|
164
|
+
expect(result.components[0]).toEqual(expect.objectContaining({ status: 'unverifiable' }));
|
|
165
|
+
expect(fs_1.default.readdirSync(root)).toEqual(before);
|
|
166
|
+
});
|
|
167
|
+
it('returns a complete strict report when the local registry inventory is malformed', async () => {
|
|
168
|
+
const { checkCurrentness } = require('../../../src/core/currentness/check');
|
|
169
|
+
const result = await checkCurrentness(root, deps({
|
|
170
|
+
listRegistries: () => { throw new Error('Invalid registries config at /private/registries.json'); },
|
|
171
|
+
}));
|
|
172
|
+
expect(result.components).toEqual(expect.arrayContaining([
|
|
173
|
+
expect.objectContaining({ component: 'cli', status: 'current' }),
|
|
174
|
+
expect.objectContaining({
|
|
175
|
+
component: 'registry:inventory', status: 'unverifiable',
|
|
176
|
+
source: '[configured registry inventory]',
|
|
177
|
+
remedy: 'Repair the local registry inventory and rerun strict preflight.',
|
|
178
|
+
}),
|
|
179
|
+
]));
|
|
180
|
+
expect(JSON.stringify(result)).not.toContain('/private/registries.json');
|
|
181
|
+
});
|
|
182
|
+
it('settles deterministically when an injected fetch ignores abort and later rejects', async () => {
|
|
183
|
+
jest.useFakeTimers();
|
|
184
|
+
let rejectFetch;
|
|
185
|
+
const fetch = jest.fn(() => new Promise((_resolve, reject) => { rejectFetch = reject; }));
|
|
186
|
+
const { checkCurrentness } = require('../../../src/core/currentness/check');
|
|
187
|
+
const pending = checkCurrentness(root, deps({ fetch }));
|
|
188
|
+
await jest.advanceTimersByTimeAsync(2_000);
|
|
189
|
+
await expect(pending).resolves.toEqual(expect.objectContaining({
|
|
190
|
+
components: expect.arrayContaining([expect.objectContaining({ component: 'cli', status: 'unverifiable' })]),
|
|
191
|
+
}));
|
|
192
|
+
rejectFetch(new Error('late fetch failure'));
|
|
193
|
+
await Promise.resolve();
|
|
194
|
+
});
|
|
195
|
+
it('settles deterministically when an injected Git transport ignores its timeout and later rejects', async () => {
|
|
196
|
+
jest.useFakeTimers();
|
|
197
|
+
let rejectGit;
|
|
198
|
+
const git = jest.fn().mockImplementation(async (_cwd, args) => {
|
|
199
|
+
if (args[0] === 'ls-remote')
|
|
200
|
+
return new Promise((_resolve, reject) => { rejectGit = reject; });
|
|
201
|
+
if (args[0] === 'remote')
|
|
202
|
+
return 'https://example.test/team/repo.git\n';
|
|
203
|
+
if (args[0] === 'rev-parse')
|
|
204
|
+
return `${'a'.repeat(40)}\n`;
|
|
205
|
+
if (args[0] === 'describe')
|
|
206
|
+
return 'v1.2.3\n';
|
|
207
|
+
throw new Error('unexpected');
|
|
208
|
+
});
|
|
209
|
+
const { checkCurrentness } = require('../../../src/core/currentness/check');
|
|
210
|
+
const pending = checkCurrentness(root, deps({ git }));
|
|
211
|
+
await jest.advanceTimersByTimeAsync(2_000);
|
|
212
|
+
await expect(pending).resolves.toEqual(expect.objectContaining({
|
|
213
|
+
components: expect.arrayContaining([expect.objectContaining({ component: 'registry:baseline', status: 'unverifiable' })]),
|
|
214
|
+
}));
|
|
215
|
+
rejectGit(new Error('late Git failure'));
|
|
216
|
+
await Promise.resolve();
|
|
217
|
+
});
|
|
218
|
+
it('rejects oversized npm metadata before attempting JSON parsing', async () => {
|
|
219
|
+
const parse = jest.fn(() => ({ 'dist-tags': { latest: '1.2.3' } }));
|
|
220
|
+
const { checkCurrentness } = require('../../../src/core/currentness/check');
|
|
221
|
+
const result = await checkCurrentness(root, deps({
|
|
222
|
+
fetch: jest.fn().mockResolvedValue({
|
|
223
|
+
ok: true,
|
|
224
|
+
text: async () => 'x'.repeat(64 * 1024 + 1),
|
|
225
|
+
json: parse,
|
|
226
|
+
}),
|
|
227
|
+
}));
|
|
228
|
+
expect(result.components[0]).toEqual(expect.objectContaining({ status: 'unverifiable' }));
|
|
229
|
+
expect(parse).not.toHaveBeenCalled();
|
|
230
|
+
});
|
|
231
|
+
it.each([
|
|
232
|
+
'http://example.test/team/repo.git',
|
|
233
|
+
'ftp://example.test/team/repo.git',
|
|
234
|
+
'https://username:password@example.test/team/repo.git',
|
|
235
|
+
'ssh://git:secret@example.test/team/repo.git',
|
|
236
|
+
'git@example.test:team/repo.git?token=secret',
|
|
237
|
+
])('rejects non-authoritative transport %s before Git invocation', async (remote) => {
|
|
238
|
+
const git = jest.fn();
|
|
239
|
+
const { checkCurrentness } = require('../../../src/core/currentness/check');
|
|
240
|
+
const result = await checkCurrentness(root, deps({
|
|
241
|
+
listRegistries: () => [{ name: 'baseline', remote, contentRoot: '/registry' }],
|
|
242
|
+
git,
|
|
243
|
+
}));
|
|
244
|
+
expect(result.components[1]).toEqual(expect.objectContaining({
|
|
245
|
+
source: '[configured remote]',
|
|
246
|
+
status: 'unverifiable',
|
|
247
|
+
}));
|
|
248
|
+
expect(git).not.toHaveBeenCalled();
|
|
249
|
+
});
|
|
250
|
+
it.each([
|
|
251
|
+
['ssh://username@example.test/team/repo.git', 'ssh://example.test/team/repo.git'],
|
|
252
|
+
['username@github.example:team/repo.git', 'github.example:team/repo.git'],
|
|
253
|
+
])('accepts auth-free SSH and SCP remotes while rendering a safe source for %s', async (remote, expectedSource) => {
|
|
254
|
+
const { checkCurrentness } = require('../../../src/core/currentness/check');
|
|
255
|
+
const result = await checkCurrentness(root, deps({
|
|
256
|
+
listRegistries: () => [{ name: 'baseline', remote, contentRoot: '/registry' }],
|
|
257
|
+
git: jest.fn().mockImplementation(async (_cwd, args) => {
|
|
258
|
+
if (args[0] === 'ls-remote')
|
|
259
|
+
return `${'a'.repeat(40)}\trefs/tags/v1.2.3\n`;
|
|
260
|
+
if (args[0] === 'remote')
|
|
261
|
+
return `${remote}\n`;
|
|
262
|
+
if (args[0] === 'rev-parse')
|
|
263
|
+
return `${'a'.repeat(40)}\n`;
|
|
264
|
+
if (args[0] === 'describe')
|
|
265
|
+
return 'v1.2.3\n';
|
|
266
|
+
throw new Error('unexpected');
|
|
267
|
+
}),
|
|
268
|
+
}));
|
|
269
|
+
expect(result.components[1].source).toBe(expectedSource);
|
|
270
|
+
expect(JSON.stringify(result)).not.toContain('username');
|
|
271
|
+
expect(JSON.stringify(result)).not.toContain('password');
|
|
272
|
+
expect(JSON.stringify(result)).not.toContain('secret');
|
|
273
|
+
});
|
|
274
|
+
it('treats the peeled commit of an annotated stable tag as authoritative', async () => {
|
|
275
|
+
const tagObject = 'b'.repeat(40);
|
|
276
|
+
const peeledCommit = 'a'.repeat(40);
|
|
277
|
+
const { checkCurrentness } = require('../../../src/core/currentness/check');
|
|
278
|
+
const result = await checkCurrentness(root, deps({
|
|
279
|
+
git: jest.fn().mockImplementation(async (_cwd, args) => {
|
|
280
|
+
if (args[0] === 'ls-remote')
|
|
281
|
+
return `${tagObject}\trefs/tags/v1.2.3\n${peeledCommit}\trefs/tags/v1.2.3^{}\n`;
|
|
282
|
+
if (args[0] === 'remote')
|
|
283
|
+
return 'https://example.test/team/repo.git\n';
|
|
284
|
+
if (args[0] === 'rev-parse')
|
|
285
|
+
return `${peeledCommit}\n`;
|
|
286
|
+
if (args[0] === 'describe')
|
|
287
|
+
return 'v1.2.3\n';
|
|
288
|
+
throw new Error('unexpected');
|
|
289
|
+
}),
|
|
290
|
+
}));
|
|
291
|
+
expect(result.components[1]).toEqual(expect.objectContaining({ status: 'current', latest: 'v1.2.3' }));
|
|
292
|
+
});
|
|
293
|
+
});
|