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
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* install-integrity — is THIS installation complete?
|
|
3
|
+
*
|
|
4
|
+
* npm >= 12 blocks dependency lifecycle scripts by default, so a global
|
|
5
|
+
* install can succeed while our postinstall never ran. The install-state
|
|
6
|
+
* receipt written by scripts/postinstall-guard.cjs (or the sidecar bundler)
|
|
7
|
+
* is the signal; everything else here turns that signal into a command the
|
|
8
|
+
* user can actually paste.
|
|
9
|
+
*
|
|
10
|
+
* Two deliberately different files, two deliberately different schemas:
|
|
11
|
+
* - <installRoot>/.jaw-install-state.json — what the install's lifecycle
|
|
12
|
+
* script did (writers: postinstall-guard.cjs, bundle-sidecar.sh)
|
|
13
|
+
* - JAW_HOME/.setup-state.json — whether the user finished setup via
|
|
14
|
+
* `jaw init` (writer: bin/commands/init.ts). Lives in the user's home so
|
|
15
|
+
* a read-only global tree can still clear the warning.
|
|
16
|
+
*
|
|
17
|
+
* Both carry packageVersion: a receipt from a previous version never hides a
|
|
18
|
+
* blocked upgrade (it degrades to `stale`).
|
|
19
|
+
*/
|
|
20
|
+
import fs from 'node:fs';
|
|
21
|
+
import path from 'node:path';
|
|
22
|
+
import { createRequire } from 'node:module';
|
|
23
|
+
export const SCRIPT_DEPENDENT_PACKAGES = ['cli-jaw'];
|
|
24
|
+
export const INSTALL_STATE_FILE = '.jaw-install-state.json';
|
|
25
|
+
export const SETUP_STATE_FILE = '.setup-state.json';
|
|
26
|
+
function readJson(file) {
|
|
27
|
+
try {
|
|
28
|
+
const parsed = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
29
|
+
return typeof parsed === 'object' && parsed !== null ? parsed : null;
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export function readInstallState(installRoot) {
|
|
36
|
+
const raw = readJson(path.join(installRoot, INSTALL_STATE_FILE));
|
|
37
|
+
if (!raw || typeof raw["state"] !== 'string')
|
|
38
|
+
return null;
|
|
39
|
+
return raw;
|
|
40
|
+
}
|
|
41
|
+
export function readSetupState(jawHome) {
|
|
42
|
+
const raw = readJson(path.join(jawHome, SETUP_STATE_FILE));
|
|
43
|
+
if (!raw || typeof raw["schema"] !== 'number')
|
|
44
|
+
return null;
|
|
45
|
+
return raw;
|
|
46
|
+
}
|
|
47
|
+
export function writeSetupState(jawHome, packageVersion) {
|
|
48
|
+
fs.mkdirSync(jawHome, { recursive: true });
|
|
49
|
+
fs.writeFileSync(path.join(jawHome, SETUP_STATE_FILE), JSON.stringify({
|
|
50
|
+
schema: 1,
|
|
51
|
+
packageVersion,
|
|
52
|
+
doneAt: new Date().toISOString(),
|
|
53
|
+
}, null, 2));
|
|
54
|
+
}
|
|
55
|
+
export function readPackageVersion(installRoot) {
|
|
56
|
+
const raw = readJson(path.join(installRoot, 'package.json'));
|
|
57
|
+
return raw && typeof raw["version"] === 'string' ? raw["version"] : null;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Which package manager owns this installation?
|
|
61
|
+
* npm_config_user_agent is only present while the manager itself runs us, so
|
|
62
|
+
* the install-root path is the durable signal for an installed CLI.
|
|
63
|
+
*/
|
|
64
|
+
export function detectPackageManager(installRoot, env = process.env) {
|
|
65
|
+
const agent = env["npm_config_user_agent"] || '';
|
|
66
|
+
if (agent.startsWith('pnpm/'))
|
|
67
|
+
return 'pnpm';
|
|
68
|
+
if (agent.startsWith('bun'))
|
|
69
|
+
return 'bun';
|
|
70
|
+
if (agent.startsWith('npm/'))
|
|
71
|
+
return 'npm';
|
|
72
|
+
if (/[\\/]\.bun[\\/]/.test(installRoot))
|
|
73
|
+
return 'bun';
|
|
74
|
+
if (/[\\/]pnpm[\\/]|[\\/]\.pnpm[\\/]/.test(installRoot))
|
|
75
|
+
return 'pnpm';
|
|
76
|
+
if (installRoot)
|
|
77
|
+
return 'npm';
|
|
78
|
+
return 'unknown';
|
|
79
|
+
}
|
|
80
|
+
function canWriteInstallTree(installRoot) {
|
|
81
|
+
try {
|
|
82
|
+
fs.accessSync(path.join(installRoot, 'node_modules'), fs.constants.W_OK);
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
export function inspectInstallIntegrity(installRoot, jawHome) {
|
|
90
|
+
const pkgVersion = readPackageVersion(installRoot);
|
|
91
|
+
const receipt = readInstallState(installRoot);
|
|
92
|
+
const setup = readSetupState(jawHome);
|
|
93
|
+
let installScriptState;
|
|
94
|
+
if (!receipt) {
|
|
95
|
+
installScriptState = 'blocked';
|
|
96
|
+
}
|
|
97
|
+
else if (pkgVersion && receipt.packageVersion !== pkgVersion) {
|
|
98
|
+
installScriptState = 'stale';
|
|
99
|
+
}
|
|
100
|
+
else if (receipt.state === 'completed' || receipt.state === 'safe-mode' || receipt.state === 'failed') {
|
|
101
|
+
installScriptState = receipt.state;
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
installScriptState = 'blocked';
|
|
105
|
+
}
|
|
106
|
+
const userSetupDone = Boolean(setup && pkgVersion && setup.packageVersion === pkgVersion);
|
|
107
|
+
let nativeLoadable = true;
|
|
108
|
+
try {
|
|
109
|
+
const require_ = createRequire(path.join(installRoot, 'package.json'));
|
|
110
|
+
const Database = require_('better-sqlite3');
|
|
111
|
+
new Database(':memory:').close();
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
nativeLoadable = false;
|
|
115
|
+
}
|
|
116
|
+
return {
|
|
117
|
+
installScriptState,
|
|
118
|
+
userSetupDone,
|
|
119
|
+
nativeLoadable,
|
|
120
|
+
packageManager: detectPackageManager(installRoot),
|
|
121
|
+
installRoot,
|
|
122
|
+
writableInstallTree: canWriteInstallTree(installRoot),
|
|
123
|
+
scriptDependents: SCRIPT_DEPENDENT_PACKAGES,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Exact, working recovery commands. npm's own printed remediation omits the
|
|
128
|
+
* package argument and fails with ENOENT (npm/cli#9835); never echo that form.
|
|
129
|
+
* `--dangerously-allow-all-scripts` is deliberately never suggested.
|
|
130
|
+
*/
|
|
131
|
+
export function formatRecoveryCommands(integrity) {
|
|
132
|
+
const allow = integrity.scriptDependents.join(',');
|
|
133
|
+
switch (integrity.packageManager) {
|
|
134
|
+
case 'bun':
|
|
135
|
+
return ['bun add -g --trust cli-jaw'];
|
|
136
|
+
case 'pnpm':
|
|
137
|
+
return [
|
|
138
|
+
`pnpm add -g --allow-build=${allow} cli-jaw`,
|
|
139
|
+
'pnpm approve-builds -g # pnpm <= 10',
|
|
140
|
+
];
|
|
141
|
+
case 'npm':
|
|
142
|
+
return [
|
|
143
|
+
`npm install -g cli-jaw --allow-scripts=${allow}`,
|
|
144
|
+
`npm config set allow-scripts=${allow} --location=user`,
|
|
145
|
+
];
|
|
146
|
+
default:
|
|
147
|
+
return [
|
|
148
|
+
`npm install -g cli-jaw --allow-scripts=${allow}`,
|
|
149
|
+
`pnpm add -g --allow-build=${allow} cli-jaw`,
|
|
150
|
+
'bun add -g --trust cli-jaw',
|
|
151
|
+
];
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
/** Problem → cause → command, Puppeteer-style. Plain text, pipe-safe. */
|
|
155
|
+
export function formatIntegrityReport(integrity) {
|
|
156
|
+
const lines = [];
|
|
157
|
+
if (integrity.installScriptState === 'failed') {
|
|
158
|
+
lines.push('[jaw:install] the postinstall step ran but failed — run `jaw doctor` for details.');
|
|
159
|
+
}
|
|
160
|
+
else if (integrity.installScriptState === 'safe-mode') {
|
|
161
|
+
lines.push('[jaw:install] this install used safe mode, so setup was skipped on purpose.');
|
|
162
|
+
lines.push('[jaw:install] finish it with: jaw init');
|
|
163
|
+
return lines.join('\n');
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
lines.push('[jaw:install] this installation is incomplete: the postinstall step never ran.');
|
|
167
|
+
lines.push('[jaw:install] npm >= 12 blocks dependency install scripts unless allowed.');
|
|
168
|
+
}
|
|
169
|
+
lines.push('[jaw:install] fix it with one of:');
|
|
170
|
+
for (const cmd of formatRecoveryCommands(integrity)) {
|
|
171
|
+
lines.push(`[jaw:install] ${cmd}`);
|
|
172
|
+
}
|
|
173
|
+
lines.push('[jaw:install] then re-run: jaw doctor');
|
|
174
|
+
return lines.join('\n');
|
|
175
|
+
}
|
|
176
|
+
//# sourceMappingURL=install-integrity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"install-integrity.js","sourceRoot":"","sources":["../../../src/core/install-integrity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AA+B5C,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,SAAS,CAAU,CAAC;AAC9D,MAAM,CAAC,MAAM,kBAAkB,GAAG,yBAAyB,CAAC;AAC5D,MAAM,CAAC,MAAM,gBAAgB,GAAG,mBAAmB,CAAC;AAEpD,SAAS,QAAQ,CAAC,IAAY;IAC1B,IAAI,CAAC;QACD,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;QAClE,OAAO,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,CAAC,CAAC,CAAC,MAAiC,CAAC,CAAC,CAAC,IAAI,CAAC;IACpG,CAAC;IAAC,MAAM,CAAC;QACL,OAAO,IAAI,CAAC;IAChB,CAAC;AACL,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,WAAmB;IAChD,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,kBAAkB,CAAC,CAAC,CAAC;IACjE,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,CAAC,OAAO,CAAC,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IAC1D,OAAO,GAAqC,CAAC;AACjD,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,OAAe;IAC1C,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,gBAAgB,CAAC,CAAC,CAAC;IAC3D,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,CAAC,QAAQ,CAAC,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IAC3D,OAAO,GAAkC,CAAC;AAC9C,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,OAAe,EAAE,cAAsB;IACnE,EAAE,CAAC,SAAS,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3C,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,gBAAgB,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC;QAClE,MAAM,EAAE,CAAC;QACT,cAAc;QACd,MAAM,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KACR,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;AAC5C,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,WAAmB;IAClD,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,cAAc,CAAC,CAAC,CAAC;IAC7D,OAAO,GAAG,IAAI,OAAO,GAAG,CAAC,SAAS,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AAC7E,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAChC,WAAmB,EACnB,MAAyB,OAAO,CAAC,GAAG;IAEpC,MAAM,KAAK,GAAG,GAAG,CAAC,uBAAuB,CAAC,IAAI,EAAE,CAAC;IACjD,IAAI,KAAK,CAAC,UAAU,CAAC,OAAO,CAAC;QAAE,OAAO,MAAM,CAAC;IAC7C,IAAI,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAC1C,IAAI,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC;QAAE,OAAO,KAAK,CAAC;IAC3C,IAAI,iBAAiB,CAAC,IAAI,CAAC,WAAW,CAAC;QAAE,OAAO,KAAK,CAAC;IACtD,IAAI,iCAAiC,CAAC,IAAI,CAAC,WAAW,CAAC;QAAE,OAAO,MAAM,CAAC;IACvE,IAAI,WAAW;QAAE,OAAO,KAAK,CAAC;IAC9B,OAAO,SAAS,CAAC;AACrB,CAAC;AAED,SAAS,mBAAmB,CAAC,WAAmB;IAC5C,IAAI,CAAC;QACD,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,cAAc,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;QACzE,OAAO,IAAI,CAAC;IAChB,CAAC;IAAC,MAAM,CAAC;QACL,OAAO,KAAK,CAAC;IACjB,CAAC;AACL,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,WAAmB,EAAE,OAAe;IACxE,MAAM,UAAU,GAAG,kBAAkB,CAAC,WAAW,CAAC,CAAC;IACnD,MAAM,OAAO,GAAG,gBAAgB,CAAC,WAAW,CAAC,CAAC;IAC9C,MAAM,KAAK,GAAG,cAAc,CAAC,OAAO,CAAC,CAAC;IAEtC,IAAI,kBAAsC,CAAC;IAC3C,IAAI,CAAC,OAAO,EAAE,CAAC;QACX,kBAAkB,GAAG,SAAS,CAAC;IACnC,CAAC;SAAM,IAAI,UAAU,IAAI,OAAO,CAAC,cAAc,KAAK,UAAU,EAAE,CAAC;QAC7D,kBAAkB,GAAG,OAAO,CAAC;IACjC,CAAC;SAAM,IAAI,OAAO,CAAC,KAAK,KAAK,WAAW,IAAI,OAAO,CAAC,KAAK,KAAK,WAAW,IAAI,OAAO,CAAC,KAAK,KAAK,QAAQ,EAAE,CAAC;QACtG,kBAAkB,GAAG,OAAO,CAAC,KAAK,CAAC;IACvC,CAAC;SAAM,CAAC;QACJ,kBAAkB,GAAG,SAAS,CAAC;IACnC,CAAC;IAED,MAAM,aAAa,GAAG,OAAO,CAAC,KAAK,IAAI,UAAU,IAAI,KAAK,CAAC,cAAc,KAAK,UAAU,CAAC,CAAC;IAE1F,IAAI,cAAc,GAAG,IAAI,CAAC;IAC1B,IAAI,CAAC;QACD,MAAM,QAAQ,GAAG,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,cAAc,CAAC,CAAC,CAAC;QACvE,MAAM,QAAQ,GAAG,QAAQ,CAAC,gBAAgB,CAA4C,CAAC;QACvF,IAAI,QAAQ,CAAC,UAAU,CAAC,CAAC,KAAK,EAAE,CAAC;IACrC,CAAC;IAAC,MAAM,CAAC;QACL,cAAc,GAAG,KAAK,CAAC;IAC3B,CAAC;IAED,OAAO;QACH,kBAAkB;QAClB,aAAa;QACb,cAAc;QACd,cAAc,EAAE,oBAAoB,CAAC,WAAW,CAAC;QACjD,WAAW;QACX,mBAAmB,EAAE,mBAAmB,CAAC,WAAW,CAAC;QACrD,gBAAgB,EAAE,yBAAyB;KAC9C,CAAC;AACN,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,sBAAsB,CAAC,SAA2B;IAC9D,MAAM,KAAK,GAAG,SAAS,CAAC,gBAAgB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACnD,QAAQ,SAAS,CAAC,cAAc,EAAE,CAAC;QAC/B,KAAK,KAAK;YACN,OAAO,CAAC,4BAA4B,CAAC,CAAC;QAC1C,KAAK,MAAM;YACP,OAAO;gBACH,6BAA6B,KAAK,UAAU;gBAC5C,uCAAuC;aAC1C,CAAC;QACN,KAAK,KAAK;YACN,OAAO;gBACH,0CAA0C,KAAK,EAAE;gBACjD,gCAAgC,KAAK,kBAAkB;aAC1D,CAAC;QACN;YACI,OAAO;gBACH,0CAA0C,KAAK,EAAE;gBACjD,6BAA6B,KAAK,UAAU;gBAC5C,4BAA4B;aAC/B,CAAC;IACV,CAAC;AACL,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,qBAAqB,CAAC,SAA2B;IAC7D,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,SAAS,CAAC,kBAAkB,KAAK,QAAQ,EAAE,CAAC;QAC5C,KAAK,CAAC,IAAI,CAAC,mFAAmF,CAAC,CAAC;IACpG,CAAC;SAAM,IAAI,SAAS,CAAC,kBAAkB,KAAK,WAAW,EAAE,CAAC;QACtD,KAAK,CAAC,IAAI,CAAC,6EAA6E,CAAC,CAAC;QAC1F,KAAK,CAAC,IAAI,CAAC,wCAAwC,CAAC,CAAC;QACrD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC5B,CAAC;SAAM,CAAC;QACJ,KAAK,CAAC,IAAI,CAAC,gFAAgF,CAAC,CAAC;QAC7F,KAAK,CAAC,IAAI,CAAC,2EAA2E,CAAC,CAAC;IAC5F,CAAC;IACD,KAAK,CAAC,IAAI,CAAC,mCAAmC,CAAC,CAAC;IAChD,KAAK,MAAM,GAAG,IAAI,sBAAsB,CAAC,SAAS,CAAC,EAAE,CAAC;QAClD,KAAK,CAAC,IAAI,CAAC,mBAAmB,GAAG,EAAE,CAAC,CAAC;IACzC,CAAC;IACD,KAAK,CAAC,IAAI,CAAC,uCAAuC,CAAC,CAAC;IACpD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC5B,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cli-jaw",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.16",
|
|
4
4
|
"description": "Personal AI assistant powered by Pi, Antigravity, AI-E, Claude, Claude E, Codex, Codex App, Cursor, Grok, Kiro, OpenCode, and Copilot — Web, Terminal, Telegram, and Discord interfaces with 107 built-in skills",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
@@ -34,6 +34,9 @@
|
|
|
34
34
|
"engines": {
|
|
35
35
|
"node": ">=22.4.0"
|
|
36
36
|
},
|
|
37
|
+
"allowScripts": {
|
|
38
|
+
"better-sqlite3": false
|
|
39
|
+
},
|
|
37
40
|
"bin": {
|
|
38
41
|
"cli-jaw": "dist/bin/cli-jaw.js",
|
|
39
42
|
"jaw": "dist/bin/cli-jaw.js"
|
|
@@ -114,6 +117,7 @@
|
|
|
114
117
|
"gate:sidecar-prune-safety": "node scripts/release-gates.mjs sidecar-prune-safety",
|
|
115
118
|
"gate:native-load": "node scripts/release-gates.mjs native-load",
|
|
116
119
|
"gate:sidecar-smoke": "node scripts/release-gates.mjs sidecar-smoke",
|
|
120
|
+
"gate:install-integrity": "node scripts/release-gates.mjs install-integrity",
|
|
117
121
|
"check:sidecar-prune-safety": "node scripts/check-sidecar-prune-safety.mjs",
|
|
118
122
|
"sync:electron-version": "node scripts/sync-electron-version.cjs",
|
|
119
123
|
"gate:all": "node scripts/release-gates.mjs",
|
|
@@ -150,7 +154,7 @@
|
|
|
150
154
|
"@uiw/react-codemirror": "^4.25.9",
|
|
151
155
|
"@xterm/addon-fit": "^0.11.0",
|
|
152
156
|
"@xterm/xterm": "^6.0.0",
|
|
153
|
-
"better-sqlite3": "^
|
|
157
|
+
"better-sqlite3": "^13.0.2",
|
|
154
158
|
"d3": "^7.9.0",
|
|
155
159
|
"discord.js": "^14.25.1",
|
|
156
160
|
"dompurify": "^3.3.3",
|
|
@@ -183,9 +187,6 @@
|
|
|
183
187
|
"ws": "^8.18.0",
|
|
184
188
|
"yaml": "^2.8.4"
|
|
185
189
|
},
|
|
186
|
-
"optionalDependencies": {
|
|
187
|
-
"claude-e": "latest"
|
|
188
|
-
},
|
|
189
190
|
"devDependencies": {
|
|
190
191
|
"@types/better-sqlite3": "^7.6.13",
|
|
191
192
|
"@types/d3": "^7.4.3",
|
|
@@ -111,6 +111,16 @@ fi
|
|
|
111
111
|
echo "Rebuilding better-sqlite3 for bundled Node $NODE_VERSION..."
|
|
112
112
|
while IFS= read -r pkg_json; do
|
|
113
113
|
pkg_dir="$(dirname "$pkg_json")"
|
|
114
|
+
# better-sqlite3 >= 13 is Node-API: it ships prebuilds/ inside the package,
|
|
115
|
+
# has NO scripts.install (`npm run install` dies with "Missing script"), and
|
|
116
|
+
# the prebuild is ABI-independent, so no per-Node rebuild is needed at all.
|
|
117
|
+
# v12 keeps the old install script ("prebuild-install || node-gyp rebuild").
|
|
118
|
+
# The verification step below opens the DB with the bundled Node either way.
|
|
119
|
+
has_install_script="$("$NODE_BIN" -e 'const p=require(process.argv[1]);process.stdout.write(p.scripts&&p.scripts.install?"yes":"no")' "$pkg_json")"
|
|
120
|
+
if [ "$has_install_script" = "no" ]; then
|
|
121
|
+
echo " skip rebuild (v13+ bundled prebuilds): ${pkg_dir#$SIDECAR_DIR/}"
|
|
122
|
+
continue
|
|
123
|
+
fi
|
|
114
124
|
echo " rebuild: ${pkg_dir#$SIDECAR_DIR/}"
|
|
115
125
|
(
|
|
116
126
|
cd "$pkg_dir"
|
|
@@ -126,8 +136,23 @@ done < <(find "$SIDECAR_DIR/node_modules" -path '*/better-sqlite3/package.json'
|
|
|
126
136
|
|
|
127
137
|
echo "Verifying better-sqlite3 opens with bundled Node..."
|
|
128
138
|
"$NODE_BIN" -e "const Database = require('better-sqlite3'); new Database(':memory:').close()" && echo " better-sqlite3 OK" || {
|
|
129
|
-
echo "
|
|
130
|
-
|
|
139
|
+
echo " bundled prebuild failed to load — building from source (v13 build-release)..."
|
|
140
|
+
sidecar_bsql_dir="$SIDECAR_DIR/node_modules/better-sqlite3"
|
|
141
|
+
if [ -d "$sidecar_bsql_dir" ]; then
|
|
142
|
+
(
|
|
143
|
+
cd "$sidecar_bsql_dir"
|
|
144
|
+
PYTHON="$PYTHON_BIN" \
|
|
145
|
+
npm_config_python="$PYTHON_BIN" \
|
|
146
|
+
npm_config_runtime=node \
|
|
147
|
+
npm_config_target="$NODE_VERSION" \
|
|
148
|
+
npm_config_disturl="https://nodejs.org/dist" \
|
|
149
|
+
npm run build-release --foreground-scripts
|
|
150
|
+
)
|
|
151
|
+
fi
|
|
152
|
+
"$NODE_BIN" -e "const Database = require('better-sqlite3'); new Database(':memory:').close()" && echo " better-sqlite3 OK (source build)" || {
|
|
153
|
+
echo "ERROR: better-sqlite3 failed to open with bundled Node"
|
|
154
|
+
exit 1
|
|
155
|
+
}
|
|
131
156
|
}
|
|
132
157
|
|
|
133
158
|
echo "Cleaning up Node extract..."
|
|
@@ -173,5 +198,28 @@ node "$PROJECT_ROOT/scripts/check-electron-sidecar-no-jwc.cjs" --server-root "$S
|
|
|
173
198
|
# Telegram bot could load.
|
|
174
199
|
node "$PROJECT_ROOT/scripts/check-sidecar-smoke.mjs" --server-root "$SIDECAR_DIR"
|
|
175
200
|
|
|
201
|
+
# Sidecar install-state receipt. The sidecar is deliberately built with
|
|
202
|
+
# --ignore-scripts, so postinstall-guard never runs here and its receipt would
|
|
203
|
+
# be absent — which the runtime integrity check would misread as a blocked
|
|
204
|
+
# install and nag every desktop user. This is a controlled build: writing the
|
|
205
|
+
# receipt ourselves, with the sidecar's own package version, is the honest
|
|
206
|
+
# record of what happened.
|
|
207
|
+
echo "Writing sidecar install-state receipt..."
|
|
208
|
+
"$NODE_BIN" -e '
|
|
209
|
+
const fs = require("fs"), path = require("path");
|
|
210
|
+
const root = process.argv[1];
|
|
211
|
+
const packageVersion = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8")).version;
|
|
212
|
+
fs.writeFileSync(path.join(root, ".jaw-install-state.json"), JSON.stringify({
|
|
213
|
+
schema: 1,
|
|
214
|
+
state: "completed",
|
|
215
|
+
sidecar: true,
|
|
216
|
+
packageVersion,
|
|
217
|
+
ranAt: new Date().toISOString(),
|
|
218
|
+
node: process.version,
|
|
219
|
+
platform: process.platform,
|
|
220
|
+
arch: process.arch,
|
|
221
|
+
}, null, 2));
|
|
222
|
+
' "$SIDECAR_DIR"
|
|
223
|
+
|
|
176
224
|
echo "=== Sidecar ready ==="
|
|
177
225
|
du -sh "$SIDECAR_DIR"
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* rebuild it in-place before build/test steps continue.
|
|
8
8
|
*/
|
|
9
9
|
const { execFileSync } = require('child_process');
|
|
10
|
-
const { existsSync, mkdirSync, readFileSync, realpathSync, rmSync, statSync, writeFileSync } = require('fs');
|
|
10
|
+
const { accessSync, constants, existsSync, mkdirSync, readFileSync, realpathSync, rmSync, statSync, writeFileSync } = require('fs');
|
|
11
11
|
const { delimiter, dirname, join } = require('path');
|
|
12
12
|
const { createRequire } = require('module');
|
|
13
13
|
const { createHash } = require('crypto');
|
|
@@ -58,7 +58,13 @@ function rebuildBetterSqlite3() {
|
|
|
58
58
|
const betterSqliteDir = join(root, 'node_modules', 'better-sqlite3');
|
|
59
59
|
if (existsSync(join(betterSqliteDir, 'package.json'))) {
|
|
60
60
|
rmSync(join(betterSqliteDir, 'build'), { recursive: true, force: true });
|
|
61
|
-
|
|
61
|
+
// v12 has scripts.install ("prebuild-install || node-gyp rebuild").
|
|
62
|
+
// v13+ removed it (prebuilds ship in the package), and `npm rebuild`
|
|
63
|
+
// returns a no-op success there, so the real source-build script is
|
|
64
|
+
// build-release. Pick by reading the installed package's manifest.
|
|
65
|
+
const manifest = JSON.parse(readFileSync(join(betterSqliteDir, 'package.json'), 'utf8'));
|
|
66
|
+
const script = manifest.scripts && manifest.scripts.install ? 'install' : 'build-release';
|
|
67
|
+
runNpm(['run', script, '--foreground-scripts'], {
|
|
62
68
|
stdio: 'inherit',
|
|
63
69
|
cwd: betterSqliteDir,
|
|
64
70
|
env: nativeBuildEnv(),
|
|
@@ -239,10 +245,51 @@ function releaseRepairLock() {
|
|
|
239
245
|
try { rmSync(LOCK_DIR, { recursive: true, force: true }); } catch { /* best effort */ }
|
|
240
246
|
}
|
|
241
247
|
|
|
248
|
+
/**
|
|
249
|
+
* Can this process write into the install tree at all? A global npm prefix is
|
|
250
|
+
* often root-owned; telling that user to "rebuild" is advice that cannot work.
|
|
251
|
+
*/
|
|
252
|
+
function canWriteInstallTree() {
|
|
253
|
+
try {
|
|
254
|
+
accessSync(join(root, 'node_modules'), constants.W_OK);
|
|
255
|
+
return true;
|
|
256
|
+
} catch {
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Package-manager-aware recovery commands. npm >= 12 blocks unreviewed
|
|
263
|
+
* dependency install scripts, which is the likeliest reason a fresh global
|
|
264
|
+
* install is missing its native addon. npm's own printed remediation omits
|
|
265
|
+
* the package argument (npm/cli#9835), so we print the working form.
|
|
266
|
+
*/
|
|
267
|
+
function recoveryHints() {
|
|
268
|
+
const lines = [];
|
|
269
|
+
if (/[\\/]\.bun[\\/]/.test(root)) {
|
|
270
|
+
lines.push('[jaw:native] reinstall with lifecycle scripts trusted (bun):');
|
|
271
|
+
lines.push('[jaw:native] bun add -g --trust cli-jaw');
|
|
272
|
+
} else if (/[\\/]pnpm[\\/]|[\\/]\.pnpm[\\/]/.test(root)) {
|
|
273
|
+
lines.push('[jaw:native] reinstall with build scripts allowed (pnpm 11+):');
|
|
274
|
+
lines.push('[jaw:native] pnpm add -g --allow-build=cli-jaw cli-jaw');
|
|
275
|
+
lines.push('[jaw:native] (pnpm <= 10: pnpm approve-builds -g)');
|
|
276
|
+
} else {
|
|
277
|
+
lines.push('[jaw:native] reinstall with install scripts allowed (npm >= 11.16):');
|
|
278
|
+
lines.push('[jaw:native] npm install -g cli-jaw --allow-scripts=cli-jaw');
|
|
279
|
+
lines.push('[jaw:native] npm config set allow-scripts=cli-jaw --location=user');
|
|
280
|
+
}
|
|
281
|
+
return lines;
|
|
282
|
+
}
|
|
283
|
+
|
|
242
284
|
function reportMissing() {
|
|
243
285
|
console.error('[jaw:native] ❌ dependencies are not installed (cannot resolve better-sqlite3).');
|
|
244
286
|
console.error(`[jaw:native] install root: ${root}`);
|
|
245
|
-
|
|
287
|
+
if (!canWriteInstallTree()) {
|
|
288
|
+
console.error('[jaw:native] install tree is not writable — a rebuild cannot help here.');
|
|
289
|
+
for (const line of recoveryHints()) console.error(line);
|
|
290
|
+
} else {
|
|
291
|
+
console.error('[jaw:native] run: npm install');
|
|
292
|
+
}
|
|
246
293
|
}
|
|
247
294
|
|
|
248
295
|
module.exports = {
|
|
@@ -67,6 +67,7 @@ function runPackageContentsCheck() {
|
|
|
67
67
|
const required = [
|
|
68
68
|
'scripts/install.sh',
|
|
69
69
|
'scripts/install-wsl.sh',
|
|
70
|
+
'scripts/install.ps1',
|
|
70
71
|
'scripts/verify-fresh-install.sh',
|
|
71
72
|
'scripts/collect-fresh-install-evidence.sh',
|
|
72
73
|
'scripts/audit-fresh-install-evidence.mjs',
|
|
@@ -94,6 +95,13 @@ function runPackageContentsCheck() {
|
|
|
94
95
|
return false;
|
|
95
96
|
}
|
|
96
97
|
|
|
98
|
+
// The install-state receipt is written at install time; if it ever ships in
|
|
99
|
+
// the tarball it always exists, which makes blocked-install detection blind.
|
|
100
|
+
if ([...files].some((file) => file.endsWith('.jaw-install-state.json'))) {
|
|
101
|
+
console.error(`[install-risk] FAIL ${label}: .jaw-install-state.json must never ship in the package`);
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
|
|
97
105
|
console.log(`[install-risk] PASS ${label}`);
|
|
98
106
|
return true;
|
|
99
107
|
}
|
package/scripts/install-wsl.sh
CHANGED
|
@@ -37,6 +37,29 @@ HAS_SUDO=false
|
|
|
37
37
|
NPM_PREFIX="$HOME/.local"
|
|
38
38
|
NPM_PATH_LINE='export PATH="$HOME/.local/bin:$PATH"'
|
|
39
39
|
|
|
40
|
+
# npm >= 11.16 understands --allow-scripts; npm 12 blocks unreviewed dependency
|
|
41
|
+
# lifecycle scripts by default, so without this flag a global install can
|
|
42
|
+
# succeed while cli-jaw's postinstall silently never runs. Older npm rejects
|
|
43
|
+
# unknown config, so the flag is attached conditionally. (Duplicated from
|
|
44
|
+
# install.sh — both files are standalone curl-run scripts.)
|
|
45
|
+
JAW_ALLOW_SCRIPTS="cli-jaw"
|
|
46
|
+
|
|
47
|
+
jaw_npm_supports_allow_scripts() {
|
|
48
|
+
local npm_version major minor
|
|
49
|
+
npm_version="$(npm --version 2>/dev/null || true)"
|
|
50
|
+
major="$(printf '%s' "$npm_version" | cut -d. -f1)"
|
|
51
|
+
minor="$(printf '%s' "$npm_version" | cut -d. -f2)"
|
|
52
|
+
case "$major" in (''|*[!0-9]*) return 1 ;; esac
|
|
53
|
+
case "$minor" in (''|*[!0-9]*) minor=0 ;; esac
|
|
54
|
+
[ "$major" -gt 11 ] || { [ "$major" -eq 11 ] && [ "$minor" -ge 16 ]; }
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
jaw_allow_scripts_flag() {
|
|
58
|
+
if jaw_npm_supports_allow_scripts; then
|
|
59
|
+
printf '%s' "--allow-scripts=${JAW_ALLOW_SCRIPTS}"
|
|
60
|
+
fi
|
|
61
|
+
}
|
|
62
|
+
|
|
40
63
|
# Guard: reject Windows HOME (e.g. /mnt/c/Users/...)
|
|
41
64
|
case "$HOME" in
|
|
42
65
|
/mnt/*) fail "HOME points to Windows path: $HOME — launch a proper WSL shell (wsl.exe -d Ubuntu)" ;;
|
|
@@ -315,15 +338,17 @@ verify_jaw_command() {
|
|
|
315
338
|
# Step 3: Install cli-jaw
|
|
316
339
|
# ═══════════════════════════════════════
|
|
317
340
|
install_jaw() {
|
|
341
|
+
local allow_flag
|
|
342
|
+
allow_flag="$(jaw_allow_scripts_flag)"
|
|
318
343
|
if command -v jaw &>/dev/null; then
|
|
319
344
|
ok "cli-jaw already installed ($(jaw --version 2>/dev/null || echo 'unknown version'))"
|
|
320
345
|
info "Updating to latest..."
|
|
321
346
|
CLI_JAW_INSTALL_CLI_TOOLS=1 \
|
|
322
|
-
npm install -g cli-jaw@latest
|
|
347
|
+
npm install -g cli-jaw@latest ${allow_flag:+$allow_flag}
|
|
323
348
|
else
|
|
324
349
|
info "Installing cli-jaw globally..."
|
|
325
350
|
CLI_JAW_INSTALL_CLI_TOOLS=1 \
|
|
326
|
-
npm install -g cli-jaw
|
|
351
|
+
npm install -g cli-jaw ${allow_flag:+$allow_flag}
|
|
327
352
|
fi
|
|
328
353
|
|
|
329
354
|
verify_jaw_command
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
#Requires -Version 5.1
|
|
2
|
+
<#
|
|
3
|
+
.SYNOPSIS
|
|
4
|
+
CLI-JAW one-click installer for native Windows (beta).
|
|
5
|
+
|
|
6
|
+
.DESCRIPTION
|
|
7
|
+
Installs cli-jaw globally with npm, attaching --allow-scripts when the npm
|
|
8
|
+
version supports it (npm >= 11.16; npm 12 blocks unreviewed dependency
|
|
9
|
+
lifecycle scripts by default). Verifies PATH and the installed binary, and
|
|
10
|
+
prints exact fixes instead of mutating user PATH or system policy.
|
|
11
|
+
|
|
12
|
+
This script never changes the PowerShell execution policy, never requires
|
|
13
|
+
elevation, and never edits the user PATH — it prints the command instead.
|
|
14
|
+
|
|
15
|
+
.PARAMETER TarballPath
|
|
16
|
+
Install from a local .tgz produced by `npm pack` instead of the registry.
|
|
17
|
+
Used by CI to validate the exact artifact that will ship.
|
|
18
|
+
|
|
19
|
+
.PARAMETER Prefix
|
|
20
|
+
Use an isolated npm prefix instead of the default global prefix. Used by CI
|
|
21
|
+
to avoid mutating the runner's real global tree.
|
|
22
|
+
|
|
23
|
+
.PARAMETER IgnoreScripts
|
|
24
|
+
Pass --ignore-scripts to npm install. Used by CI to reproduce the
|
|
25
|
+
blocked-postinstall scenario deliberately.
|
|
26
|
+
|
|
27
|
+
.EXAMPLE
|
|
28
|
+
irm https://raw.githubusercontent.com/lidge-jun/cli-jaw/main/scripts/install.ps1 | iex
|
|
29
|
+
#>
|
|
30
|
+
[CmdletBinding()]
|
|
31
|
+
param(
|
|
32
|
+
[string]$TarballPath = '',
|
|
33
|
+
[string]$Prefix = '',
|
|
34
|
+
[switch]$IgnoreScripts
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
Set-StrictMode -Version Latest
|
|
38
|
+
$ErrorActionPreference = 'Stop'
|
|
39
|
+
|
|
40
|
+
$JawAllowScripts = 'cli-jaw'
|
|
41
|
+
|
|
42
|
+
function Write-Info([string]$Message) { Write-Host " $Message" -ForegroundColor Cyan }
|
|
43
|
+
function Write-Ok([string]$Message) { Write-Host " $Message" -ForegroundColor Green }
|
|
44
|
+
function Write-Warn2([string]$Message) { Write-Host " $Message" -ForegroundColor Yellow }
|
|
45
|
+
|
|
46
|
+
Write-Host ''
|
|
47
|
+
Write-Host ' CLI-JAW Windows Installer (beta)' -ForegroundColor Cyan
|
|
48
|
+
Write-Host ''
|
|
49
|
+
|
|
50
|
+
# --- 1. Node.js >= 22 ---------------------------------------------------
|
|
51
|
+
$nodeCmd = Get-Command node -ErrorAction SilentlyContinue
|
|
52
|
+
if (-not $nodeCmd) {
|
|
53
|
+
Write-Warn2 'Node.js not found on PATH.'
|
|
54
|
+
Write-Warn2 'Install it first, then re-run this script:'
|
|
55
|
+
Write-Warn2 ' winget install OpenJS.NodeJS.LTS'
|
|
56
|
+
exit 1
|
|
57
|
+
}
|
|
58
|
+
$nodeVersion = (& node --version) -replace '^v', ''
|
|
59
|
+
$nodeMajor = [int]($nodeVersion.Split('.')[0])
|
|
60
|
+
if ($nodeMajor -lt 22) {
|
|
61
|
+
Write-Warn2 "Node.js >= 22 required (current: v$nodeVersion)."
|
|
62
|
+
Write-Warn2 ' winget install OpenJS.NodeJS.LTS'
|
|
63
|
+
exit 1
|
|
64
|
+
}
|
|
65
|
+
Write-Ok "Node.js v$nodeVersion"
|
|
66
|
+
|
|
67
|
+
# --- 2. npm allow-scripts support ---------------------------------------
|
|
68
|
+
# npm >= 11.16 understands --allow-scripts; npm 12 blocks unreviewed
|
|
69
|
+
# dependency lifecycle scripts by default. Older npm rejects unknown config,
|
|
70
|
+
# so the flag is attached conditionally.
|
|
71
|
+
$npmVersion = (& npm --version).Trim()
|
|
72
|
+
$npmParts = $npmVersion.Split('.')
|
|
73
|
+
$npmMajor = [int]$npmParts[0]
|
|
74
|
+
$npmMinor = [int]$npmParts[1]
|
|
75
|
+
$supportsAllowScripts = ($npmMajor -gt 11) -or (($npmMajor -eq 11) -and ($npmMinor -ge 16))
|
|
76
|
+
Write-Ok "npm $npmVersion (allow-scripts: $supportsAllowScripts)"
|
|
77
|
+
|
|
78
|
+
# --- 3. Install ----------------------------------------------------------
|
|
79
|
+
$packageSpec = if ($TarballPath) { $TarballPath } else { 'cli-jaw' }
|
|
80
|
+
$npmArgs = @('install', '-g', $packageSpec)
|
|
81
|
+
if ($supportsAllowScripts -and -not $IgnoreScripts) {
|
|
82
|
+
$npmArgs += "--allow-scripts=$JawAllowScripts"
|
|
83
|
+
}
|
|
84
|
+
if ($IgnoreScripts) { $npmArgs += '--ignore-scripts' }
|
|
85
|
+
if ($Prefix) { $npmArgs += @('--prefix', $Prefix) }
|
|
86
|
+
|
|
87
|
+
Write-Info "npm $($npmArgs -join ' ')"
|
|
88
|
+
& npm @npmArgs
|
|
89
|
+
if ($LASTEXITCODE -ne 0) {
|
|
90
|
+
Write-Warn2 "npm install failed (exit $LASTEXITCODE)."
|
|
91
|
+
Write-Warn2 'If the error mentions blocked install scripts, run:'
|
|
92
|
+
Write-Warn2 " npm install -g cli-jaw --allow-scripts=$JawAllowScripts"
|
|
93
|
+
exit 1
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
# --- 4. PATH check --------------------------------------------------------
|
|
97
|
+
$globalBin = if ($Prefix) { $Prefix } else { (& npm prefix -g).Trim() }
|
|
98
|
+
$pathEntries = $env:Path -split ';'
|
|
99
|
+
$onPath = $pathEntries -contains $globalBin
|
|
100
|
+
if (-not $onPath -and -not $Prefix) {
|
|
101
|
+
Write-Warn2 "npm global bin dir is not on PATH: $globalBin"
|
|
102
|
+
Write-Warn2 'Add it for the current user (then open a new terminal):'
|
|
103
|
+
Write-Warn2 " [Environment]::SetEnvironmentVariable('Path', `$env:Path + ';$globalBin', 'User')"
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
# --- 5. Verify ------------------------------------------------------------
|
|
107
|
+
$jawCmd = Join-Path $globalBin 'jaw.cmd'
|
|
108
|
+
if (-not (Test-Path $jawCmd)) {
|
|
109
|
+
# default-prefix installs may resolve via PATH instead
|
|
110
|
+
$resolved = Get-Command jaw -ErrorAction SilentlyContinue
|
|
111
|
+
if ($resolved) { $jawCmd = $resolved.Source }
|
|
112
|
+
}
|
|
113
|
+
if (Test-Path $jawCmd) {
|
|
114
|
+
$jawVersion = (& $jawCmd --version)
|
|
115
|
+
Write-Ok "cli-jaw installed: $jawVersion"
|
|
116
|
+
Write-Info 'Next: jaw doctor (diagnose) | jaw init (finish setup)'
|
|
117
|
+
} else {
|
|
118
|
+
Write-Warn2 'Install finished but the jaw command was not found.'
|
|
119
|
+
Write-Warn2 'Diagnose with: npm ls -g cli-jaw ; then check the PATH note above.'
|
|
120
|
+
exit 1
|
|
121
|
+
}
|
package/scripts/install.sh
CHANGED
|
@@ -39,6 +39,28 @@ extract_semver() {
|
|
|
39
39
|
printf '%s' "${1:-}" | grep -Eo '[0-9]+\.[0-9]+\.[0-9]+' | head -n1 || true
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
# npm >= 11.16 understands --allow-scripts; npm 12 blocks unreviewed dependency
|
|
43
|
+
# lifecycle scripts by default, so without this flag a global install can
|
|
44
|
+
# succeed while cli-jaw's postinstall silently never runs. Older npm rejects
|
|
45
|
+
# unknown config, so the flag is attached conditionally.
|
|
46
|
+
JAW_ALLOW_SCRIPTS="cli-jaw"
|
|
47
|
+
|
|
48
|
+
jaw_npm_supports_allow_scripts() {
|
|
49
|
+
local npm_version major minor
|
|
50
|
+
npm_version="$(npm --version 2>/dev/null || true)"
|
|
51
|
+
major="$(printf '%s' "$npm_version" | cut -d. -f1)"
|
|
52
|
+
minor="$(printf '%s' "$npm_version" | cut -d. -f2)"
|
|
53
|
+
case "$major" in (''|*[!0-9]*) return 1 ;; esac
|
|
54
|
+
case "$minor" in (''|*[!0-9]*) minor=0 ;; esac
|
|
55
|
+
[ "$major" -gt 11 ] || { [ "$major" -eq 11 ] && [ "$minor" -ge 16 ]; }
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
jaw_allow_scripts_flag() {
|
|
59
|
+
if jaw_npm_supports_allow_scripts; then
|
|
60
|
+
printf '%s' "--allow-scripts=${JAW_ALLOW_SCRIPTS}"
|
|
61
|
+
fi
|
|
62
|
+
}
|
|
63
|
+
|
|
42
64
|
resolve_cmd() {
|
|
43
65
|
command -v "$1" 2>/dev/null || true
|
|
44
66
|
}
|
|
@@ -433,11 +455,15 @@ install_cli_jaw() {
|
|
|
433
455
|
fi
|
|
434
456
|
|
|
435
457
|
# Detect package manager from existing install path to avoid shared-path contamination
|
|
436
|
-
local pkg_cmd
|
|
458
|
+
local allow_flag pkg_cmd
|
|
459
|
+
allow_flag="$(jaw_allow_scripts_flag)"
|
|
460
|
+
pkg_cmd="npm install -g cli-jaw${allow_flag:+ $allow_flag}"
|
|
437
461
|
if [ -n "$installed_bin" ]; then
|
|
438
462
|
case "$installed_bin" in
|
|
439
463
|
*"/.bun/bin/"*)
|
|
440
|
-
|
|
464
|
+
# bun skips lifecycle scripts for untrusted packages; --trust is the
|
|
465
|
+
# only surface that runs them for a global add.
|
|
466
|
+
pkg_cmd="bun add -g --trust cli-jaw"
|
|
441
467
|
info "Detected bun-managed install — using bun"
|
|
442
468
|
;;
|
|
443
469
|
*)
|