@bobfrankston/npmglobalize 1.0.220 → 1.0.221
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 +51 -9
- package/cli.js +6 -1
- package/lib.d.ts +27 -0
- package/lib.js +245 -20
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -172,15 +172,57 @@ exits 0 — e.g. `@bobfrankston/msger`'s postinstall copies its native binary
|
|
|
172
172
|
into the per-user bin dir its launcher reads from, and skipping it leaves a
|
|
173
173
|
stale exe behind a green checkmark.
|
|
174
174
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
175
|
+
A **project** install is the opposite case: npm *rejects* `--allow-scripts`
|
|
176
|
+
there and reads the policy from the `allowScripts` field of the root
|
|
177
|
+
`package.json` (maintained by `npm approve-scripts` / `npm deny-scripts`),
|
|
178
|
+
falling back to `allow-scripts` in `.npmrc` only when the field is absent.
|
|
179
|
+
Until that field covers everything installed, every `npm install` ends with a
|
|
180
|
+
`npm warn allow-scripts … not yet covered by allowScripts` block.
|
|
181
|
+
|
|
182
|
+
npmglobalize keeps that field current, once per run, right after the
|
|
183
|
+
node_modules check:
|
|
184
|
+
|
|
185
|
+
- **Own-scope packages and `file:` deps are approved without asking** — they
|
|
186
|
+
are your source.
|
|
187
|
+
- **Each third-party package is asked about**, showing the script npm would
|
|
188
|
+
run and whether it is a prebuilt-binary fetcher:
|
|
189
|
+
|
|
190
|
+
```
|
|
191
|
+
npm gates install scripts for 2 third-party package(s) in @bobfrankston/whts. Approvals are recorded in package.json "allowScripts" (name-only) and honored on the global install too.
|
|
192
|
+
Allow install scripts for sharp@0.33.5 (install: node install/check)? [y]es / [n]o, deny / [a]ll remaining / [s]kip for now: y
|
|
193
|
+
Allow install scripts for koffi@2.14.1|2.16.1 (install: node src/cnoke/cnoke.js --prebuild) — prebuilt-binary fetcher, the package ships binaries? [y]es / [n]o, deny / [a]ll remaining / [s]kip for now: y
|
|
194
|
+
✓ allowScripts: approved @bobfrankston/msger, com-wrapper, sharp, koffi (2 own/local approved automatically)
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
`y` approves, `n` records a deny (`"pkg": false`, npm's own mechanism — the
|
|
198
|
+
question is not repeated; delete the entry to reconsider), `a` approves
|
|
199
|
+
everything remaining, `s` leaves it pending for next time. Approvals are
|
|
200
|
+
**name-only** (`--no-allow-scripts-pin`): a version pin would re-open the
|
|
201
|
+
question on every bump. Without a terminal (piped stdin) nothing is asked;
|
|
202
|
+
the pending packages are listed with the command to approve them.
|
|
203
|
+
- The field is written by npm itself, so a linked dep gets the key npm wants
|
|
204
|
+
for it — the `file:` spec, e.g. `"file:../../../../projects/com/com-wrapper": true`
|
|
205
|
+
(relative to the link's parent directory, not the project).
|
|
206
|
+
|
|
207
|
+
The **global** install then allowlists your own packages — it just built and
|
|
208
|
+
published them from your source, so they're trusted — plus whatever the
|
|
209
|
+
project's `allowScripts` approved (a `file:` key is mapped to that dep's
|
|
210
|
+
published name) and your user `.npmrc` `allow-scripts` list, minus anything
|
|
211
|
+
the project denied. Third-party packages you never approved stay gated:
|
|
212
|
+
|
|
213
|
+
```
|
|
214
|
+
> npm install -g @bobfrankston/winpos@2.0.51 --allow-scripts @bobfrankston/winpos,@bobfrankston/msger,@bobfrankston/msgcommon,sharp
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
npm takes the allowlist from the *first* source that has one rather than
|
|
218
|
+
merging — CLI flag, then `package.json`, then `.npmrc` — which is why the
|
|
219
|
+
`.npmrc` list is folded into the flag here, and why npm prints
|
|
220
|
+
`.npmrc allow-scripts setting is being ignored because package.json declares
|
|
221
|
+
its own allowScripts field` on project installs once the field exists. That
|
|
222
|
+
warning is npm's layering at work, not a problem.
|
|
223
|
+
|
|
224
|
+
Anything npm skips on the global install is reported rather than buried in
|
|
225
|
+
the captured output:
|
|
184
226
|
|
|
185
227
|
```
|
|
186
228
|
· Skipped koffi@2.16.3 install script — prebuilt-binary fetcher, the package ships binaries; normally harmless.
|
package/cli.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* npmglobalize CLI - Transform file: dependencies to npm versions for publishing
|
|
4
4
|
*/
|
|
5
|
-
import { globalize, globalizeWorkspace, installCleanupHandlers, readConfig, readPackageJson, readUserNpmConfig, writeConfig, writePackageJson, getBuildIssues, clearBuildIssues, ensureFileDepModules, buildProject, buildFileDepsTopologically, reportTs7Deprecations, fixTs7Deprecations, canonicalCase } from './lib.js';
|
|
5
|
+
import { globalize, globalizeWorkspace, installCleanupHandlers, readConfig, readPackageJson, readUserNpmConfig, writeConfig, writePackageJson, getBuildIssues, clearBuildIssues, ensureFileDepModules, ensureAllowScripts, buildProject, buildFileDepsTopologically, reportTs7Deprecations, fixTs7Deprecations, canonicalCase } from './lib.js';
|
|
6
6
|
import fs from 'fs';
|
|
7
7
|
import path from 'path';
|
|
8
8
|
import { colors } from './colors.js';
|
|
@@ -609,6 +609,11 @@ export async function main() {
|
|
|
609
609
|
}
|
|
610
610
|
if (!modulesOk)
|
|
611
611
|
console.log(colors.warn('Continuing with -force despite npm install failure...'));
|
|
612
|
+
// 2026-09-07 — Claude Code (Fable 5.1), at Bob's direction: keep package.json
|
|
613
|
+
// "allowScripts" current — own/file: deps approved, third-party ones asked about —
|
|
614
|
+
// so npm's install-script gate stops warning and the global install honors it.
|
|
615
|
+
if (modulesOk)
|
|
616
|
+
await ensureAllowScripts(cwd, { dryRun: !!cliOptions.dryRun, verbose: !!cliOptions.verbose });
|
|
612
617
|
const depsOk = await buildFileDepsTopologically(cwd, { verbose: !!cliOptions.verbose, force: !!cliOptions.force, forceBuild: !!cliOptions.forceBuild });
|
|
613
618
|
if (!depsOk && !cliOptions.force) {
|
|
614
619
|
printBuildSummary();
|
package/lib.d.ts
CHANGED
|
@@ -559,6 +559,33 @@ export declare function fixPuppeteerCorruptedCache(combined: string): {
|
|
|
559
559
|
* Returns [] for an unscoped package: there's no way to tell an unscoped
|
|
560
560
|
* package of ours from a third-party one by name alone. */
|
|
561
561
|
export declare function ownScopePackages(dir: string): string[];
|
|
562
|
+
/** The project's recorded install-script policy, as registry names. Keys in
|
|
563
|
+
* `allowScripts` are `name`, `name@version`, or a `file:` / absolute path;
|
|
564
|
+
* path keys are mapped to the target's manifest name so an approval given
|
|
565
|
+
* to a linked dep carries over to the global install, where that same dep
|
|
566
|
+
* arrives from the registry under its published name. */
|
|
567
|
+
export declare function projectAllowScripts(dir: string): {
|
|
568
|
+
allowed: string[];
|
|
569
|
+
denied: string[];
|
|
570
|
+
};
|
|
571
|
+
/** Bring the project's package.json `allowScripts` up to date with what is
|
|
572
|
+
* installed, so `npm install` stops warning and — once npm starts enforcing
|
|
573
|
+
* the policy — keeps running the scripts this project depends on.
|
|
574
|
+
*
|
|
575
|
+
* Own-scope packages and `file:` deps are approved without asking: they are
|
|
576
|
+
* this user's source. Anything else is asked about, one package at a time,
|
|
577
|
+
* showing the script npm would run. "no" records a deny (npm's own
|
|
578
|
+
* mechanism, `npm deny-scripts`) so the question isn't repeated; "skip"
|
|
579
|
+
* leaves it pending for next time. Approvals are name-only
|
|
580
|
+
* (`--no-allow-scripts-pin`): a version pin would re-open the question on
|
|
581
|
+
* every bump, which is exactly the churn this exists to remove.
|
|
582
|
+
*
|
|
583
|
+
* Runs once per project root, not per cascade dep: npm reads the policy
|
|
584
|
+
* from the root package.json only. ~1.5s (arborist loads the actual tree). */
|
|
585
|
+
export declare function ensureAllowScripts(dir: string, opts?: {
|
|
586
|
+
dryRun?: boolean;
|
|
587
|
+
verbose?: boolean;
|
|
588
|
+
}): Promise<void>;
|
|
562
589
|
/** Run a command and return success status */
|
|
563
590
|
export declare function runCommand(cmd: string, args: string[], options?: {
|
|
564
591
|
silent?: boolean;
|
package/lib.js
CHANGED
|
@@ -4214,20 +4214,238 @@ export function ownScopePackages(dir) {
|
|
|
4214
4214
|
walk(pkg, dir);
|
|
4215
4215
|
return [...found];
|
|
4216
4216
|
}
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
|
|
4217
|
+
// 2026-09-07 — Claude Code (Fable 5.1), at Bob's direction ("I want you to handle
|
|
4218
|
+
// this", after `npm un itemgen` in whts warned about com-wrapper, msger and sharp).
|
|
4219
|
+
// The project side of the policy. npm REJECTS `--allow-scripts` on a project-scoped
|
|
4220
|
+
// install (EALLOWSCRIPTS), so the only place a project's approvals can live is the
|
|
4221
|
+
// `allowScripts` field of its package.json, maintained by `npm approve-scripts`.
|
|
4222
|
+
// npmglobalize now keeps that field current (ensureAllowScripts) and feeds it back
|
|
4223
|
+
// into the global install's `--allow-scripts` flag (allowScriptsArgs), so a package
|
|
4224
|
+
// is trusted once, in one place, for both kinds of install.
|
|
4225
|
+
/** Names of the project's `file:` dependencies as npm displays them in the
|
|
4226
|
+
* allowScripts pending list. A linked dep has no registry identity, so npm
|
|
4227
|
+
* falls back to the link target's directory name (`com-wrapper` for
|
|
4228
|
+
* `@bobfrankston/com-wrapper` at ../../projects/com/com-wrapper); the
|
|
4229
|
+
* manifest name is included too in case that fallback ever changes. */
|
|
4230
|
+
function localDepDisplayNames(dir) {
|
|
4231
|
+
let pkg;
|
|
4232
|
+
try {
|
|
4233
|
+
pkg = readPackageJson(dir);
|
|
4234
|
+
}
|
|
4235
|
+
catch {
|
|
4236
|
+
return [];
|
|
4237
|
+
}
|
|
4238
|
+
const names = [];
|
|
4239
|
+
for (const key of ['dependencies', 'devDependencies', 'optionalDependencies']) {
|
|
4240
|
+
const deps = pkg[key];
|
|
4241
|
+
if (!deps || typeof deps !== 'object')
|
|
4242
|
+
continue;
|
|
4243
|
+
for (const spec of Object.values(deps)) {
|
|
4244
|
+
if (typeof spec !== 'string' || !spec.startsWith('file:'))
|
|
4245
|
+
continue;
|
|
4246
|
+
const target = path.resolve(dir, spec.slice('file:'.length));
|
|
4247
|
+
names.push(path.basename(target));
|
|
4248
|
+
try {
|
|
4249
|
+
const name = readPackageJson(target).name;
|
|
4250
|
+
if (name)
|
|
4251
|
+
names.push(name);
|
|
4252
|
+
}
|
|
4253
|
+
catch { /* target missing or unreadable — the basename alone still identifies it */ }
|
|
4254
|
+
}
|
|
4255
|
+
}
|
|
4256
|
+
return names;
|
|
4257
|
+
}
|
|
4258
|
+
/** The project's recorded install-script policy, as registry names. Keys in
|
|
4259
|
+
* `allowScripts` are `name`, `name@version`, or a `file:` / absolute path;
|
|
4260
|
+
* path keys are mapped to the target's manifest name so an approval given
|
|
4261
|
+
* to a linked dep carries over to the global install, where that same dep
|
|
4262
|
+
* arrives from the registry under its published name. */
|
|
4263
|
+
export function projectAllowScripts(dir) {
|
|
4264
|
+
const allowed = [];
|
|
4265
|
+
const denied = [];
|
|
4266
|
+
let pkg;
|
|
4267
|
+
try {
|
|
4268
|
+
pkg = readPackageJson(dir);
|
|
4269
|
+
}
|
|
4270
|
+
catch {
|
|
4271
|
+
return { allowed, denied };
|
|
4272
|
+
}
|
|
4273
|
+
const policy = pkg.allowScripts;
|
|
4274
|
+
if (!policy || typeof policy !== 'object')
|
|
4275
|
+
return { allowed, denied };
|
|
4276
|
+
// A `file:` key written by `npm approve-scripts` is the link's saved spec,
|
|
4277
|
+
// which npm records relative to the LINK's parent directory — verified on
|
|
4278
|
+
// whts: package.json says `file:../../projects/com/com-wrapper`, the policy
|
|
4279
|
+
// key came out `file:../../../../projects/com/com-wrapper`, i.e. relative to
|
|
4280
|
+
// node_modules/@bobfrankston/. A hand-written key is relative to the
|
|
4281
|
+
// project. Try both bases.
|
|
4282
|
+
const bases = new Set([dir]);
|
|
4283
|
+
for (const key of ['dependencies', 'devDependencies', 'optionalDependencies']) {
|
|
4284
|
+
for (const [name, spec] of Object.entries(pkg[key] || {})) {
|
|
4285
|
+
if (typeof spec === 'string' && spec.startsWith('file:'))
|
|
4286
|
+
bases.add(path.dirname(path.join(dir, 'node_modules', name)));
|
|
4287
|
+
}
|
|
4288
|
+
}
|
|
4289
|
+
const manifestName = (rel) => {
|
|
4290
|
+
for (const base of bases) {
|
|
4291
|
+
try {
|
|
4292
|
+
return readPackageJson(path.resolve(base, rel)).name;
|
|
4293
|
+
}
|
|
4294
|
+
catch { /* not this base — try the next; the caller skips the key when none has it */ }
|
|
4295
|
+
}
|
|
4296
|
+
return null;
|
|
4297
|
+
};
|
|
4298
|
+
for (const [key, value] of Object.entries(policy)) {
|
|
4299
|
+
let name;
|
|
4300
|
+
if (key.startsWith('file:') || path.isAbsolute(key)) {
|
|
4301
|
+
name = manifestName(key.startsWith('file:') ? key.slice('file:'.length) : key);
|
|
4302
|
+
}
|
|
4303
|
+
else {
|
|
4304
|
+
// strip a trailing @version; a scoped name keeps its leading @
|
|
4305
|
+
name = key.replace(/(.)@[^@]*$/, '$1');
|
|
4306
|
+
}
|
|
4307
|
+
if (!name)
|
|
4308
|
+
continue;
|
|
4309
|
+
(value === false ? denied : allowed).push(name);
|
|
4310
|
+
}
|
|
4311
|
+
return { allowed, denied };
|
|
4312
|
+
}
|
|
4313
|
+
/** Names from the user's own `allow-scripts` npm config (.npmrc layers).
|
|
4314
|
+
* npm's resolver takes the FIRST layer with any config and ignores the
|
|
4315
|
+
* rest, so a CLI flag would silently suppress these — merging them into the
|
|
4316
|
+
* flag keeps `npm config set allow-scripts=… --location=user` meaningful. */
|
|
4317
|
+
function npmrcAllowScripts() {
|
|
4318
|
+
const r = runCommand('npm', ['config', 'get', 'allow-scripts'], { silent: true });
|
|
4319
|
+
if (!r.success || isUnknownConfig(r.output))
|
|
4320
|
+
return [];
|
|
4321
|
+
return r.output.split(/[,\s]+/).map(s => s.trim()).filter(Boolean);
|
|
4322
|
+
}
|
|
4323
|
+
/** npm args allowing install scripts on a GLOBAL install: our own packages,
|
|
4324
|
+
* whatever the project's package.json `allowScripts` approves, and the
|
|
4325
|
+
* user's .npmrc list — minus anything the project denies. Empty when npm
|
|
4326
|
+
* predates the policy (it runs the scripts anyway) or nothing is allowed. */
|
|
4327
|
+
function allowScriptsArgs(dir) {
|
|
4227
4328
|
if (!npmSupportsAllowScripts())
|
|
4228
4329
|
return [];
|
|
4229
|
-
const
|
|
4230
|
-
|
|
4330
|
+
const project = projectAllowScripts(dir);
|
|
4331
|
+
const names = [...new Set([...ownScopePackages(dir), ...project.allowed, ...npmrcAllowScripts()])]
|
|
4332
|
+
.filter(n => !project.denied.includes(n));
|
|
4333
|
+
return names.length ? ['--allow-scripts', names.join(',')] : [];
|
|
4334
|
+
}
|
|
4335
|
+
/** Parse the text listing of `npm approve-scripts --allow-scripts-pending`:
|
|
4336
|
+
* one indented `name@version (event: cmd; event: cmd)` line per node,
|
|
4337
|
+
* the same package repeated when several versions are installed. */
|
|
4338
|
+
function parsePendingScripts(output) {
|
|
4339
|
+
const byName = new Map();
|
|
4340
|
+
for (const line of output.split('\n')) {
|
|
4341
|
+
const m = line.match(/^ (\S+?)(?:@([^@\s]+))? \((.*)\)\s*$/);
|
|
4342
|
+
if (!m)
|
|
4343
|
+
continue;
|
|
4344
|
+
const [, name, version, scripts] = m;
|
|
4345
|
+
const entry = byName.get(name) ?? { name, versions: [], scripts };
|
|
4346
|
+
if (version)
|
|
4347
|
+
entry.versions.push(version);
|
|
4348
|
+
byName.set(name, entry);
|
|
4349
|
+
}
|
|
4350
|
+
return [...byName.values()];
|
|
4351
|
+
}
|
|
4352
|
+
/** Bring the project's package.json `allowScripts` up to date with what is
|
|
4353
|
+
* installed, so `npm install` stops warning and — once npm starts enforcing
|
|
4354
|
+
* the policy — keeps running the scripts this project depends on.
|
|
4355
|
+
*
|
|
4356
|
+
* Own-scope packages and `file:` deps are approved without asking: they are
|
|
4357
|
+
* this user's source. Anything else is asked about, one package at a time,
|
|
4358
|
+
* showing the script npm would run. "no" records a deny (npm's own
|
|
4359
|
+
* mechanism, `npm deny-scripts`) so the question isn't repeated; "skip"
|
|
4360
|
+
* leaves it pending for next time. Approvals are name-only
|
|
4361
|
+
* (`--no-allow-scripts-pin`): a version pin would re-open the question on
|
|
4362
|
+
* every bump, which is exactly the churn this exists to remove.
|
|
4363
|
+
*
|
|
4364
|
+
* Runs once per project root, not per cascade dep: npm reads the policy
|
|
4365
|
+
* from the root package.json only. ~1.5s (arborist loads the actual tree). */
|
|
4366
|
+
export async function ensureAllowScripts(dir, opts = {}) {
|
|
4367
|
+
if (!npmSupportsAllowScripts())
|
|
4368
|
+
return;
|
|
4369
|
+
let pkgName;
|
|
4370
|
+
try {
|
|
4371
|
+
pkgName = readPackageJson(dir).name;
|
|
4372
|
+
}
|
|
4373
|
+
catch {
|
|
4374
|
+
return;
|
|
4375
|
+
}
|
|
4376
|
+
const listing = await runCommandAsync('npm', ['approve-scripts', '--allow-scripts-pending'], { cwd: dir, silent: true });
|
|
4377
|
+
if (!listing.success) {
|
|
4378
|
+
if (opts.verbose)
|
|
4379
|
+
console.log(colors.dim(` (could not list pending install scripts in ${pkgName}: ${(listing.stderr || listing.output).trim().split('\n')[0]})`));
|
|
4380
|
+
return;
|
|
4381
|
+
}
|
|
4382
|
+
const pending = parsePendingScripts(listing.output);
|
|
4383
|
+
if (!pending.length)
|
|
4384
|
+
return;
|
|
4385
|
+
const trusted = new Set([...ownScopePackages(dir), ...localDepDisplayNames(dir)]);
|
|
4386
|
+
const auto = pending.filter(p => trusted.has(p.name));
|
|
4387
|
+
const ask = pending.filter(p => !trusted.has(p.name));
|
|
4388
|
+
const label = (p) => p.versions.length ? `${p.name}@${p.versions.join('|')}` : p.name;
|
|
4389
|
+
if (opts.dryRun) {
|
|
4390
|
+
console.log(colors.dim(` [dry-run] allowScripts in ${pkgName}: would approve ${auto.map(label).join(', ') || '(none)'}; would ask about ${ask.map(label).join(', ') || '(none)'}`));
|
|
4391
|
+
return;
|
|
4392
|
+
}
|
|
4393
|
+
const approve = auto.map(p => p.name);
|
|
4394
|
+
const deny = [];
|
|
4395
|
+
const skipped = [];
|
|
4396
|
+
if (ask.length) {
|
|
4397
|
+
if (!process.stdin.isTTY) {
|
|
4398
|
+
console.log(colors.yellow(` ⚠ ${ask.length} third-party package(s) in ${pkgName} have install scripts not yet covered by allowScripts (no terminal to ask): ${ask.map(label).join(', ')}`));
|
|
4399
|
+
console.log(colors.dim(` npm approve-scripts --no-allow-scripts-pin <pkg> or npm deny-scripts <pkg>`));
|
|
4400
|
+
skipped.push(...ask.map(label));
|
|
4401
|
+
}
|
|
4402
|
+
else {
|
|
4403
|
+
console.log(colors.cyan(`npm gates install scripts for ${ask.length} third-party package(s) in ${pkgName}. Approvals are recorded in package.json "allowScripts" (name-only) and honored on the global install too.`));
|
|
4404
|
+
let allRemaining = false;
|
|
4405
|
+
for (const p of ask) {
|
|
4406
|
+
const prebuilt = PREBUILT_SCRIPT_RUNNERS.test(p.scripts) ? ' — prebuilt-binary fetcher, the package ships binaries' : '';
|
|
4407
|
+
if (allRemaining) {
|
|
4408
|
+
approve.push(p.name);
|
|
4409
|
+
continue;
|
|
4410
|
+
}
|
|
4411
|
+
const answer = await promptChoice(` Allow install scripts for ${label(p)} (${p.scripts})${prebuilt}? [y]es / [n]o, deny / [a]ll remaining / [s]kip for now:`, ['y', 'n', 'a', 's']);
|
|
4412
|
+
switch (answer) {
|
|
4413
|
+
case 'y':
|
|
4414
|
+
approve.push(p.name);
|
|
4415
|
+
break;
|
|
4416
|
+
case 'a':
|
|
4417
|
+
approve.push(p.name);
|
|
4418
|
+
allRemaining = true;
|
|
4419
|
+
break;
|
|
4420
|
+
case 'n':
|
|
4421
|
+
deny.push(p.name);
|
|
4422
|
+
break;
|
|
4423
|
+
default:
|
|
4424
|
+
skipped.push(label(p));
|
|
4425
|
+
break; // 's', or EOF ('' from promptChoice)
|
|
4426
|
+
}
|
|
4427
|
+
}
|
|
4428
|
+
}
|
|
4429
|
+
}
|
|
4430
|
+
// `npm approve-scripts <name>` matches installed nodes by the same display
|
|
4431
|
+
// name the pending listing used, and writes the right key for each kind
|
|
4432
|
+
// (registry name, or the file: path for a linked dep).
|
|
4433
|
+
if (approve.length) {
|
|
4434
|
+
const r = await runCommandAsync('npm', ['approve-scripts', '--no-allow-scripts-pin', ...approve], { cwd: dir, silent: true });
|
|
4435
|
+
if (r.success)
|
|
4436
|
+
console.log(colors.green(` ✓ allowScripts: approved ${approve.join(', ')}${auto.length ? ` (${auto.length} own/local approved automatically)` : ''}`));
|
|
4437
|
+
else
|
|
4438
|
+
console.log(colors.red(` ✗ npm approve-scripts failed: ${(r.stderr || r.output).trim().split('\n').find(l => /npm error/.test(l)) ?? 'see output'}`));
|
|
4439
|
+
}
|
|
4440
|
+
if (deny.length) {
|
|
4441
|
+
const r = await runCommandAsync('npm', ['deny-scripts', ...deny], { cwd: dir, silent: true });
|
|
4442
|
+
if (r.success)
|
|
4443
|
+
console.log(colors.yellow(` ✓ allowScripts: denied ${deny.join(', ')} (remove the entry from package.json allowScripts to reconsider)`));
|
|
4444
|
+
else
|
|
4445
|
+
console.log(colors.red(` ✗ npm deny-scripts failed: ${(r.stderr || r.output).trim().split('\n').find(l => /npm error/.test(l)) ?? 'see output'}`));
|
|
4446
|
+
}
|
|
4447
|
+
if (skipped.length)
|
|
4448
|
+
console.log(colors.dim(` · install scripts still unreviewed (asked again next run): ${skipped.join(', ')}`));
|
|
4231
4449
|
}
|
|
4232
4450
|
/** Install-script runners that fetch or select a prebuilt binary and only
|
|
4233
4451
|
* compile when no prebuild matches the platform. A package using one of these
|
|
@@ -4301,9 +4519,10 @@ async function installGlobalWithRetry(pkgSpec, cwd, isNewPackage = false, maxRet
|
|
|
4301
4519
|
let attempt = 0;
|
|
4302
4520
|
let cacheFixRounds = 0;
|
|
4303
4521
|
// Our own packages are trusted — we just built and published them from
|
|
4304
|
-
// this very directory — so their install scripts run
|
|
4305
|
-
//
|
|
4306
|
-
|
|
4522
|
+
// this very directory — so their install scripts run, along with whatever
|
|
4523
|
+
// the project's package.json allowScripts approved (ensureAllowScripts)
|
|
4524
|
+
// and the user's .npmrc list. Everything else stays behind npm's gate.
|
|
4525
|
+
const allowArgs = allowScriptsArgs(cwd);
|
|
4307
4526
|
while (true) {
|
|
4308
4527
|
// 2026-09-01 10:15 EDT — Claude Code (Opus 5), at Bob's direction.
|
|
4309
4528
|
// `--prefer-online`: this install always targets a version published
|
|
@@ -4623,13 +4842,15 @@ async function verifyWslGlobalBins(cwd) {
|
|
|
4623
4842
|
}
|
|
4624
4843
|
}
|
|
4625
4844
|
export async function installInWsl(wslArgs, opts = {}) {
|
|
4626
|
-
// Same trust rule as the Windows installs:
|
|
4627
|
-
//
|
|
4845
|
+
// Same trust rule as the Windows installs: our own packages plus the
|
|
4846
|
+
// project's package.json approvals, minus its denies. The Windows .npmrc
|
|
4847
|
+
// list is left out — WSL's npm has its own .npmrc. Probed against WSL's
|
|
4628
4848
|
// npm, which is a separate install from the Windows one.
|
|
4629
4849
|
if (opts.cwd && wslArgs.includes('install') && await wslNpmSupportsAllowScripts()) {
|
|
4630
|
-
const
|
|
4631
|
-
|
|
4632
|
-
|
|
4850
|
+
const project = projectAllowScripts(opts.cwd);
|
|
4851
|
+
const names = [...new Set([...ownScopePackages(opts.cwd), ...project.allowed])].filter(n => !project.denied.includes(n));
|
|
4852
|
+
if (names.length)
|
|
4853
|
+
wslArgs = [...wslArgs, '--allow-scripts', names.join(',')];
|
|
4633
4854
|
}
|
|
4634
4855
|
const runOnce = async () => {
|
|
4635
4856
|
console.log(colors.cyan(`> wsl ${wslArgs.join(' ')}`));
|
|
@@ -6922,6 +7143,10 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
|
|
|
6922
7143
|
}
|
|
6923
7144
|
if (!modulesOk)
|
|
6924
7145
|
console.log(colors.yellow('Continuing with -force despite npm install failure...'));
|
|
7146
|
+
// 2026-09-07 — Claude Code (Fable 5.1): same allowScripts upkeep the CLI
|
|
7147
|
+
// entrypoint does; the global install below reads the result.
|
|
7148
|
+
if (modulesOk)
|
|
7149
|
+
await ensureAllowScripts(cwd, { verbose });
|
|
6925
7150
|
}
|
|
6926
7151
|
// Run build step if package.json has a build script (skip if CLI already built)
|
|
6927
7152
|
if (pkg.scripts?.build && !options._fromCli) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bobfrankston/npmglobalize",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.221",
|
|
4
4
|
"description": "Transform file: dependencies to npm versions for publishing",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"@bobfrankston/freezepak": "^0.1.9",
|
|
35
|
-
"@bobfrankston/importgen": "^0.1.
|
|
35
|
+
"@bobfrankston/importgen": "^0.1.42",
|
|
36
36
|
"@bobfrankston/themecolors": "^0.1.9",
|
|
37
37
|
"@bobfrankston/userconfig": "^1.0.11",
|
|
38
38
|
"@npmcli/package-json": "^7.0.4",
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
".transformedSnapshot": {
|
|
61
61
|
"dependencies": {
|
|
62
62
|
"@bobfrankston/freezepak": "^0.1.9",
|
|
63
|
-
"@bobfrankston/importgen": "^0.1.
|
|
63
|
+
"@bobfrankston/importgen": "^0.1.42",
|
|
64
64
|
"@bobfrankston/themecolors": "^0.1.9",
|
|
65
65
|
"@bobfrankston/userconfig": "^1.0.11",
|
|
66
66
|
"@npmcli/package-json": "^7.0.4",
|