cli-jaw 2.2.14 → 2.2.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -1
- package/dist/bin/cli-jaw.js +39 -0
- package/dist/bin/cli-jaw.js.map +1 -1
- package/dist/bin/commands/doctor.js +23 -1
- package/dist/bin/commands/doctor.js.map +1 -1
- package/dist/bin/commands/init.js +18 -0
- package/dist/bin/commands/init.js.map +1 -1
- package/dist/bin/commands/provider.js +23 -0
- package/dist/bin/commands/provider.js.map +1 -1
- package/dist/src/core/install-integrity.js +176 -0
- package/dist/src/core/install-integrity.js.map +1 -0
- package/package.json +6 -5
- package/scripts/bundle-sidecar.sh +50 -2
- package/scripts/ensure-native-modules.cjs +50 -3
- package/scripts/install-risk-gate.mjs +8 -0
- package/scripts/install-wsl.sh +27 -2
- package/scripts/install.ps1 +121 -0
- package/scripts/install.sh +28 -2
- package/scripts/postinstall-guard.cjs +44 -2
- package/scripts/release-gates.mjs +25 -8
|
@@ -48,6 +48,41 @@ function ensurePackageBinExecutable(rootDir) {
|
|
|
48
48
|
|
|
49
49
|
ensurePackageBinExecutable(root);
|
|
50
50
|
|
|
51
|
+
// ─── 2.5 install state receipt ──────────────────────
|
|
52
|
+
// This file records what the install actually did. Its ABSENCE is the signal
|
|
53
|
+
// that npm/pnpm/bun blocked our lifecycle script (npm >= 12 blocks unreviewed
|
|
54
|
+
// dependency scripts by default); its `state` distinguishes a real setup from
|
|
55
|
+
// a safe-mode install that deliberately skipped it.
|
|
56
|
+
//
|
|
57
|
+
// It lives at the package root, NOT under dist/: scripts/atomic-build.sh
|
|
58
|
+
// replaces dist/ wholesale on every build, which would erase the receipt and
|
|
59
|
+
// make ordinary development look like a blocked install. The root path is
|
|
60
|
+
// also outside the `files` allowlist, so it can never leak into the tarball.
|
|
61
|
+
const INSTALL_STATE_FILE = '.jaw-install-state.json';
|
|
62
|
+
|
|
63
|
+
/** @param {'completed'|'safe-mode'|'failed'} state */
|
|
64
|
+
function writeInstallState(rootDir, state, extra) {
|
|
65
|
+
try {
|
|
66
|
+
// packageVersion is REQUIRED by the reader: a receipt whose version
|
|
67
|
+
// differs from the installed package is classified `stale`, so an
|
|
68
|
+
// upgrade whose scripts were blocked is never hidden by an old receipt.
|
|
69
|
+
const packageVersion = JSON.parse(
|
|
70
|
+
fs.readFileSync(path.join(rootDir, 'package.json'), 'utf8')).version;
|
|
71
|
+
fs.writeFileSync(path.join(rootDir, INSTALL_STATE_FILE), JSON.stringify({
|
|
72
|
+
schema: 1,
|
|
73
|
+
state,
|
|
74
|
+
packageVersion,
|
|
75
|
+
ranAt: new Date().toISOString(),
|
|
76
|
+
node: process.version,
|
|
77
|
+
platform: process.platform,
|
|
78
|
+
arch: process.arch,
|
|
79
|
+
...extra,
|
|
80
|
+
}, null, 2));
|
|
81
|
+
} catch {
|
|
82
|
+
// A missing receipt only downgrades diagnostics; never fail the install.
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
51
86
|
// ─── 3. Safe mode: no build/install work ────────────
|
|
52
87
|
const safeMode =
|
|
53
88
|
process.env.JAW_SAFE === '1'
|
|
@@ -59,6 +94,7 @@ const safeMode =
|
|
|
59
94
|
|
|
60
95
|
if (safeMode) {
|
|
61
96
|
console.log('[jaw:init] safe mode — skipping postinstall build and installers');
|
|
97
|
+
writeInstallState(root, 'safe-mode');
|
|
62
98
|
process.exit(0);
|
|
63
99
|
}
|
|
64
100
|
|
|
@@ -103,8 +139,14 @@ if (!fs.existsSync(target)) {
|
|
|
103
139
|
ensurePackageBinExecutable(root);
|
|
104
140
|
}
|
|
105
141
|
|
|
106
|
-
runCompiledPostinstall().
|
|
142
|
+
runCompiledPostinstall().then(() => {
|
|
143
|
+
writeInstallState(root, 'completed');
|
|
144
|
+
}).catch((error) => {
|
|
145
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
107
146
|
console.error('[jaw:init] ⚠️ postinstall error (non-fatal):');
|
|
108
|
-
console.error(
|
|
147
|
+
console.error(message);
|
|
148
|
+
// Record the failure so `jaw doctor` can explain it later instead of the
|
|
149
|
+
// exit(0) silently swallowing it.
|
|
150
|
+
writeInstallState(root, 'failed', { error: message });
|
|
109
151
|
process.exit(0);
|
|
110
152
|
});
|
|
@@ -82,7 +82,7 @@ const GATES = {
|
|
|
82
82
|
];
|
|
83
83
|
const r = run('npx', args);
|
|
84
84
|
if (r.status !== 0) {
|
|
85
|
-
return { ok: false, detail: `tests failed:\n${
|
|
85
|
+
return { ok: false, detail: `tests failed:\n${[r.stdout, r.stderr].filter(Boolean).join('\n').slice(-2000)}` };
|
|
86
86
|
}
|
|
87
87
|
return { ok: true, detail: `passed ${targets.length} suite(s): ${targets.join(', ')}` };
|
|
88
88
|
},
|
|
@@ -382,7 +382,7 @@ const GATES = {
|
|
|
382
382
|
for (const s of steps) {
|
|
383
383
|
const r = run(s.cmd, s.args, { timeout: s.timeout });
|
|
384
384
|
if (r.status !== 0) {
|
|
385
|
-
return { ok: false, detail: `${s.name} failed:\n${
|
|
385
|
+
return { ok: false, detail: `${s.name} failed:\n${[r.stdout, r.stderr].filter(Boolean).join('\n').slice(-1500)}` };
|
|
386
386
|
}
|
|
387
387
|
}
|
|
388
388
|
return { ok: true, detail: 'docs:check + check-doc-drift.sh clean' };
|
|
@@ -393,7 +393,7 @@ const GATES = {
|
|
|
393
393
|
check() {
|
|
394
394
|
const r = run('node', ['scripts/check-strict-baseline.mjs'], { timeout: 120_000 });
|
|
395
395
|
if (r.status !== 0) {
|
|
396
|
-
return { ok: false, detail: `strict baseline regressed:\n${
|
|
396
|
+
return { ok: false, detail: `strict baseline regressed:\n${[r.stdout, r.stderr].filter(Boolean).join('\n').slice(-1200)}` };
|
|
397
397
|
}
|
|
398
398
|
return { ok: true, detail: 'live any/debt/allow counts within frozen baseline' };
|
|
399
399
|
},
|
|
@@ -403,7 +403,7 @@ const GATES = {
|
|
|
403
403
|
check() {
|
|
404
404
|
const r = run('node', ['scripts/check-redaction-sinks.mjs'], { timeout: 60_000 });
|
|
405
405
|
if (r.status !== 0) {
|
|
406
|
-
return { ok: false, detail: `unmasked channel sink:\n${
|
|
406
|
+
return { ok: false, detail: `unmasked channel sink:\n${[r.stdout, r.stderr].filter(Boolean).join('\n').slice(-1500)}` };
|
|
407
407
|
}
|
|
408
408
|
return { ok: true, detail: 'no unmasked sink in telegram/discord/slack/telegram-hub' };
|
|
409
409
|
},
|
|
@@ -413,7 +413,7 @@ const GATES = {
|
|
|
413
413
|
check() {
|
|
414
414
|
const r = run('node', ['scripts/sync-electron-version.cjs', '--check'], { timeout: 60_000 });
|
|
415
415
|
if (r.status !== 0) {
|
|
416
|
-
return { ok: false, detail:
|
|
416
|
+
return { ok: false, detail: [r.stdout, r.stderr].filter(Boolean).join('\n').trim().slice(-500) };
|
|
417
417
|
}
|
|
418
418
|
return { ok: true, detail: 'desktop artifacts will carry the release version' };
|
|
419
419
|
},
|
|
@@ -423,7 +423,7 @@ const GATES = {
|
|
|
423
423
|
check() {
|
|
424
424
|
const r = run('node', ['scripts/check-sidecar-prune-safety.mjs'], { timeout: 60_000 });
|
|
425
425
|
if (r.status !== 0) {
|
|
426
|
-
return { ok: false, detail:
|
|
426
|
+
return { ok: false, detail: [r.stdout, r.stderr].filter(Boolean).join('\n').trim().slice(-800) };
|
|
427
427
|
}
|
|
428
428
|
return { ok: true, detail: 'packaged sidecar keeps every runtime dependency' };
|
|
429
429
|
},
|
|
@@ -439,7 +439,12 @@ const GATES = {
|
|
|
439
439
|
return { ok: true, detail: 'SKIPPED — electron/node_modules absent, nothing probed' };
|
|
440
440
|
}
|
|
441
441
|
if (r.status !== 0) {
|
|
442
|
-
|
|
442
|
+
// The probe prints its notes on stdout and its reasons on
|
|
443
|
+
// stderr. `stdout || stderr` therefore threw the reasons away
|
|
444
|
+
// whenever a single note had been printed, which is how a Linux
|
|
445
|
+
// failure reached CI showing only "prebuilds present" and
|
|
446
|
+
// "round-trip ok" with no stated cause.
|
|
447
|
+
return { ok: false, detail: [r.stdout, r.stderr].filter(Boolean).join('\n').trim().slice(-800) };
|
|
443
448
|
}
|
|
444
449
|
return { ok: true, detail: 'node-pty loads under this runtime' };
|
|
445
450
|
},
|
|
@@ -452,11 +457,23 @@ const GATES = {
|
|
|
452
457
|
return { ok: true, detail: 'SKIPPED — sidecar not bundled, nothing imported' };
|
|
453
458
|
}
|
|
454
459
|
if (r.status !== 0) {
|
|
455
|
-
return { ok: false, detail:
|
|
460
|
+
return { ok: false, detail: [r.stdout, r.stderr].filter(Boolean).join('\n').trim().slice(-800) };
|
|
456
461
|
}
|
|
457
462
|
return { ok: true, detail: 'bundled sidecar imports its critical entry surfaces' };
|
|
458
463
|
},
|
|
459
464
|
},
|
|
465
|
+
'install-integrity': {
|
|
466
|
+
description: 'scriptless install contract + allow-scripts recovery guidance stay pinned',
|
|
467
|
+
check() {
|
|
468
|
+
const r = run('npx', ['tsx', '--experimental-test-module-mocks', 'tests/run.mts',
|
|
469
|
+
'tests/unit/install-integrity.test.ts',
|
|
470
|
+
'tests/unit/scriptless-install-contract.test.ts'], { timeout: 180_000 });
|
|
471
|
+
if (r.status !== 0) {
|
|
472
|
+
return { ok: false, detail: [r.stdout, r.stderr].filter(Boolean).join('\n').trim().slice(-800) };
|
|
473
|
+
}
|
|
474
|
+
return { ok: true, detail: 'install-integrity + scriptless contract suites green' };
|
|
475
|
+
},
|
|
476
|
+
},
|
|
460
477
|
'gate-docs': {
|
|
461
478
|
description: 'structure/INDEX.md documents exactly the gates that exist, and each is npm-addressable',
|
|
462
479
|
check() {
|