@bobfrankston/npmglobalize 1.0.222 → 1.0.224
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 +23 -3
- package/cli.js +8 -7
- package/lib.d.ts +15 -2
- package/lib.js +101 -29
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -200,6 +200,24 @@ node_modules check:
|
|
|
200
200
|
**name-only** (`--no-allow-scripts-pin`): a version pin would re-open the
|
|
201
201
|
question on every bump. Without a terminal (piped stdin) nothing is asked;
|
|
202
202
|
the pending packages are listed with the command to approve them.
|
|
203
|
+
- **Each answer is remembered for every project.** A decision about `sharp`'s
|
|
204
|
+
install script is about sharp, not about the project that asked first, so
|
|
205
|
+
`y`/`n`/`a` answers are stored once per user in your userconfig `npm.json5` (the file `@bobfrankston/userconfig` names as `configPath`; the prompt prints its location):
|
|
206
|
+
|
|
207
|
+
```json5
|
|
208
|
+
allowScripts: {
|
|
209
|
+
sharp: true,
|
|
210
|
+
koffi: true,
|
|
211
|
+
'some-pkg': false,
|
|
212
|
+
},
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
The next project that meets a remembered package gets its `package.json`
|
|
216
|
+
entry written silently (`· install-script decisions applied from …npm.json5:
|
|
217
|
+
sharp allowed`). A project's own `allowScripts` entry wins over the
|
|
218
|
+
remembered one, so a per-project exception is just an edit to that
|
|
219
|
+
`package.json`. Edit or delete an entry in `npm.json5` to change your mind
|
|
220
|
+
everywhere.
|
|
203
221
|
- The field is written by npm itself, so a linked dep gets the key npm wants
|
|
204
222
|
for it — the `file:` spec, e.g. `"file:../../../../projects/com/com-wrapper": true`
|
|
205
223
|
(relative to the link's parent directory, not the project).
|
|
@@ -207,8 +225,8 @@ node_modules check:
|
|
|
207
225
|
The **global** install then allowlists your own packages — it just built and
|
|
208
226
|
published them from your source, so they're trusted — plus whatever the
|
|
209
227
|
project's `allowScripts` approved (a `file:` key is mapped to that dep's
|
|
210
|
-
published name) and your user `.npmrc`
|
|
211
|
-
|
|
228
|
+
published name), your remembered `npm.json5` approvals, and your user `.npmrc`
|
|
229
|
+
`allow-scripts` list, minus anything denied. Third-party packages you never approved stay gated:
|
|
212
230
|
|
|
213
231
|
```
|
|
214
232
|
> npm install -g @bobfrankston/winpos@2.0.51 --allow-scripts @bobfrankston/winpos,@bobfrankston/msger,@bobfrankston/msgcommon,sharp
|
|
@@ -884,7 +902,9 @@ This is useful for:
|
|
|
884
902
|
deprecation note; it was renamed (2026-09-07) because "local" read as "local mode" rather
|
|
885
903
|
than "install from the local directory".
|
|
886
904
|
|
|
887
|
-
`-local-install` is persisted to `.globalize.json5` (`"
|
|
905
|
+
`-local-install` is persisted to `.globalize.json5` (`"localInstall": true`; the older
|
|
906
|
+
`"local"` key is still read and is rewritten to the new name the next time the file is
|
|
907
|
+
saved), so a bare
|
|
888
908
|
`npmglobalize` keeps doing local installs until you run `-global` (or edit the
|
|
889
909
|
file). Nothing is committed, bumped, or published, but a `.commitmsg` is still
|
|
890
910
|
consumed: it is appended to `npmchanges.md` under a
|
package/cli.js
CHANGED
|
@@ -235,14 +235,15 @@ function parseArgs(args) {
|
|
|
235
235
|
// from the local directory". Renamed to -local-install (-localinstall also
|
|
236
236
|
// accepted). -local still works but says so, so muscle memory isn't broken
|
|
237
237
|
// and the old spelling is phased out rather than silently kept alive.
|
|
238
|
-
// The .globalize.json5 key
|
|
238
|
+
// The .globalize.json5 key was renamed "local" → "localInstall" the same day;
|
|
239
|
+
// readConfig accepts the old name and the next persist rewrites it.
|
|
239
240
|
case '-local':
|
|
240
|
-
console.log(colors.yellow('! -local is deprecated — use -local-install (
|
|
241
|
+
console.log(colors.yellow('! -local is deprecated — use -local-install (or -localinstall). Same meaning: npm install -g . from the local directory, no publish.'));
|
|
241
242
|
// falls through
|
|
242
243
|
case '-local-install':
|
|
243
244
|
case '-localinstall':
|
|
244
|
-
options.
|
|
245
|
-
options.explicitKeys.add('
|
|
245
|
+
options.localInstall = true;
|
|
246
|
+
options.explicitKeys.add('localInstall');
|
|
246
247
|
break;
|
|
247
248
|
// 2026-09-01 16:30 EDT — Claude Code (Opus 5), at Bob's direction. -local-install
|
|
248
249
|
// persists to .globalize.json5 but had no counterpart, so leaving local-only
|
|
@@ -250,8 +251,8 @@ function parseArgs(args) {
|
|
|
250
251
|
// 2026-09-01 15:00 EDT — Bob: "-global makes more sense, -nolocal is confusing";
|
|
251
252
|
// -nolocal dropped, -global is the only spelling.
|
|
252
253
|
case '-global':
|
|
253
|
-
options.
|
|
254
|
-
options.explicitKeys.add('
|
|
254
|
+
options.localInstall = false;
|
|
255
|
+
options.explicitKeys.add('localInstall');
|
|
255
256
|
break;
|
|
256
257
|
case '-install':
|
|
257
258
|
case '-i':
|
|
@@ -663,7 +664,7 @@ export async function main() {
|
|
|
663
664
|
console.log(`Updated ${path.join(cwd, 'package.json')} scripts:`);
|
|
664
665
|
changes.forEach(c => console.log(` ${c}`));
|
|
665
666
|
// If no other flags need processing, exit; otherwise fall through
|
|
666
|
-
if (!cliOptions.install && !cliOptions.link && !cliOptions.
|
|
667
|
+
if (!cliOptions.install && !cliOptions.link && !cliOptions.localInstall) {
|
|
667
668
|
process.exit(0);
|
|
668
669
|
}
|
|
669
670
|
}
|
package/lib.d.ts
CHANGED
|
@@ -132,8 +132,9 @@ export interface GlobalizeOptions {
|
|
|
132
132
|
* are absent. Currently declarative (recorded in config and displayed);
|
|
133
133
|
* the actual conversion path is TODO. */
|
|
134
134
|
usePaths?: boolean;
|
|
135
|
-
/** Local install only — skip transform/publish, just npm install -g .
|
|
136
|
-
|
|
135
|
+
/** Local install only (-local-install) — skip transform/publish, just npm install -g .
|
|
136
|
+
* Config key renamed from "local" 2026-09-07; readConfig still accepts the old name. */
|
|
137
|
+
localInstall?: boolean;
|
|
137
138
|
/** Build every file: dep even when outputs look up to date (skips the freshness check) */
|
|
138
139
|
forceBuild?: boolean;
|
|
139
140
|
/** Freeze node_modules: replace symlinks/junctions with real copies for network share use */
|
|
@@ -570,6 +571,18 @@ export declare function projectAllowScripts(dir: string): {
|
|
|
570
571
|
allowed: string[];
|
|
571
572
|
denied: string[];
|
|
572
573
|
};
|
|
574
|
+
/** The user's remembered install-script decisions, by package name.
|
|
575
|
+
* `true` = approve, `false` = deny. Read from npm.json5 via userconfig. */
|
|
576
|
+
export declare function globalAllowScripts(): Record<string, boolean>;
|
|
577
|
+
/** Merge new decisions into the remembered set and persist it. */
|
|
578
|
+
export declare function rememberAllowScripts(decisions: Record<string, boolean>): void;
|
|
579
|
+
/** Project policy layered over the remembered user policy: a name the
|
|
580
|
+
* project decided keeps the project's answer; every other remembered
|
|
581
|
+
* name contributes its own. */
|
|
582
|
+
export declare function effectiveAllowScripts(dir: string): {
|
|
583
|
+
allowed: string[];
|
|
584
|
+
denied: string[];
|
|
585
|
+
};
|
|
573
586
|
/** Bring the project's package.json `allowScripts` up to date with what is
|
|
574
587
|
* installed, so `npm install` stops warning and — once npm starts enforcing
|
|
575
588
|
* the policy — keeps running the scripts this project depends on.
|
package/lib.js
CHANGED
|
@@ -14,7 +14,7 @@ import os from 'os';
|
|
|
14
14
|
import path from 'path';
|
|
15
15
|
import { execSync, spawn, spawnSync } from 'child_process';
|
|
16
16
|
import { builtinModules } from 'module';
|
|
17
|
-
import { readConfig as readUserConfig, writeConfig as writeUserConfig, configDir } from '@bobfrankston/userconfig';
|
|
17
|
+
import { readConfig as readUserConfig, writeConfig as writeUserConfig, configDir, configPath as userConfigPath } from '@bobfrankston/userconfig';
|
|
18
18
|
import { freezeDependencies } from '@bobfrankston/freezepak';
|
|
19
19
|
import { importgen as runImportgen } from '@bobfrankston/importgen';
|
|
20
20
|
/** Wrapper for spawnSync that avoids DEP0190 (args + shell: true).
|
|
@@ -312,7 +312,18 @@ export function readConfig(dir) {
|
|
|
312
312
|
}
|
|
313
313
|
try {
|
|
314
314
|
const content = fs.readFileSync(configPath, 'utf-8');
|
|
315
|
-
|
|
315
|
+
const config = JSON5.parse(content);
|
|
316
|
+
// 2026-09-07 — Claude Code (Fable 5.1), at Bob's direction: the "local" key
|
|
317
|
+
// was renamed "localInstall" along with the -local → -local-install flag.
|
|
318
|
+
// Old files keep working: the value is carried over here, and since
|
|
319
|
+
// writeConfig reads the existing config through this same function, the
|
|
320
|
+
// next persist writes the new name and drops the old one.
|
|
321
|
+
if ('local' in config && !('localInstall' in config)) {
|
|
322
|
+
config.localInstall = config.local;
|
|
323
|
+
console.log(colors.dim(` (.globalize.json5: "local" is now "localInstall" — rewritten on the next persist)`));
|
|
324
|
+
}
|
|
325
|
+
delete config.local;
|
|
326
|
+
return config;
|
|
316
327
|
}
|
|
317
328
|
catch (error) {
|
|
318
329
|
console.warn(`Warning: Could not parse .globalize.json5 config: ${error.message}`);
|
|
@@ -332,7 +343,7 @@ const BOOL_COMMENTS = {
|
|
|
332
343
|
quiet: { on: 'Suppress npm warnings', off: 'Show npm warnings' },
|
|
333
344
|
verbose: { on: 'Show detailed output', off: 'Normal output detail' },
|
|
334
345
|
fix: { on: 'Auto-run npm audit fix (default)', off: 'Do NOT run npm audit fix (audit report still runs)' },
|
|
335
|
-
|
|
346
|
+
localInstall: { on: 'Local install only via -local-install (skip transform/publish)', off: 'Normal transform/publish flow' },
|
|
336
347
|
noPublish: { on: 'Transform but don\'t publish', off: 'Publish normally' },
|
|
337
348
|
freeze: { on: 'Freeze node_modules (replace symlinks with real copies)', off: 'Leave node_modules symlinks as-is' },
|
|
338
349
|
usePaths: { on: 'Resolve file: deps from sibling checkouts', off: 'Resolve file: deps from npm, not siblings (standalone package)' },
|
|
@@ -444,7 +455,7 @@ export function writeConfig(dir, config, explicitKeys) {
|
|
|
444
455
|
['"gitVisibility": "private"', 'Git repo: private or public'],
|
|
445
456
|
['"npmVisibility": "private"', 'npm package: private or public'],
|
|
446
457
|
['"fix": true', 'true = auto-run npm audit fix (never --force); false = report only'],
|
|
447
|
-
['"
|
|
458
|
+
['"localInstall": false', 'true = install from the local directory only, set by -local-install (skip transform/publish); clear with -global'],
|
|
448
459
|
['"noPublish": false', 'true = transform but don\'t publish'],
|
|
449
460
|
['"freeze": false', 'true = freeze node_modules (replace symlinks with real copies)'],
|
|
450
461
|
['"usePaths": true', 'true = resolve file: deps from siblings; false = use latest npm version (standalone)'],
|
|
@@ -4361,18 +4372,55 @@ function npmrcAllowScripts() {
|
|
|
4361
4372
|
return [];
|
|
4362
4373
|
return r.output.split(/[,\s]+/).map(s => s.trim()).filter(Boolean);
|
|
4363
4374
|
}
|
|
4375
|
+
// 2026-09-07 — Claude Code (Fable 5.1), at Bob's direction ("why not save the
|
|
4376
|
+
// accept scripts globally rather than asking me each time"). A yes/no about a
|
|
4377
|
+
// third-party package's install scripts is a decision about that PACKAGE, not
|
|
4378
|
+
// about the project that happened to ask first — sharp is sharp in whts and in
|
|
4379
|
+
// label-core. So every answer is remembered once, per user, in npm.json5
|
|
4380
|
+
// (`allowScripts: { sharp: true, "evil-pkg": false }`), and a project that
|
|
4381
|
+
// meets a remembered package gets its package.json field written without a
|
|
4382
|
+
// question. package.json stays the record npm itself reads; npm.json5 is the
|
|
4383
|
+
// memory that pre-answers it.
|
|
4384
|
+
/** The user's remembered install-script decisions, by package name.
|
|
4385
|
+
* `true` = approve, `false` = deny. Read from npm.json5 via userconfig. */
|
|
4386
|
+
export function globalAllowScripts() {
|
|
4387
|
+
const policy = readUserNpmConfig().allowScripts;
|
|
4388
|
+
return policy && typeof policy === 'object' ? { ...policy } : {};
|
|
4389
|
+
}
|
|
4390
|
+
/** Merge new decisions into the remembered set and persist it. */
|
|
4391
|
+
export function rememberAllowScripts(decisions) {
|
|
4392
|
+
if (!Object.keys(decisions).length)
|
|
4393
|
+
return;
|
|
4394
|
+
writeUserNpmConfig({ allowScripts: { ...globalAllowScripts(), ...decisions } });
|
|
4395
|
+
}
|
|
4364
4396
|
/** npm args allowing install scripts on a GLOBAL install: our own packages,
|
|
4365
|
-
* whatever the project's package.json `allowScripts` approves,
|
|
4366
|
-
* user's .npmrc list — minus
|
|
4367
|
-
*
|
|
4397
|
+
* whatever the project's package.json `allowScripts` approves, the user's
|
|
4398
|
+
* remembered approvals (npm.json5), and the user's .npmrc list — minus
|
|
4399
|
+
* anything denied. A project's explicit decision wins over the remembered
|
|
4400
|
+
* one. Empty when npm predates the policy (it runs the scripts anyway) or
|
|
4401
|
+
* nothing is allowed. */
|
|
4368
4402
|
function allowScriptsArgs(dir) {
|
|
4369
4403
|
if (!npmSupportsAllowScripts())
|
|
4370
4404
|
return [];
|
|
4371
|
-
const
|
|
4372
|
-
const names = [...new Set([...ownScopePackages(dir), ...
|
|
4373
|
-
.filter(n => !
|
|
4405
|
+
const { allowed, denied } = effectiveAllowScripts(dir);
|
|
4406
|
+
const names = [...new Set([...ownScopePackages(dir), ...allowed, ...npmrcAllowScripts()])]
|
|
4407
|
+
.filter(n => !denied.includes(n));
|
|
4374
4408
|
return names.length ? ['--allow-scripts', names.join(',')] : [];
|
|
4375
4409
|
}
|
|
4410
|
+
/** Project policy layered over the remembered user policy: a name the
|
|
4411
|
+
* project decided keeps the project's answer; every other remembered
|
|
4412
|
+
* name contributes its own. */
|
|
4413
|
+
export function effectiveAllowScripts(dir) {
|
|
4414
|
+
const project = projectAllowScripts(dir);
|
|
4415
|
+
const allowed = new Set(project.allowed);
|
|
4416
|
+
const denied = new Set(project.denied);
|
|
4417
|
+
for (const [name, ok] of Object.entries(globalAllowScripts())) {
|
|
4418
|
+
if (allowed.has(name) || denied.has(name))
|
|
4419
|
+
continue;
|
|
4420
|
+
(ok ? allowed : denied).add(name);
|
|
4421
|
+
}
|
|
4422
|
+
return { allowed: [...allowed], denied: [...denied] };
|
|
4423
|
+
}
|
|
4376
4424
|
/** Parse the text listing of `npm approve-scripts --allow-scripts-pending`:
|
|
4377
4425
|
* one indented `name@version (event: cmd; event: cmd)` line per node,
|
|
4378
4426
|
* the same package repeated when several versions are installed. */
|
|
@@ -4424,16 +4472,23 @@ export async function ensureAllowScripts(dir, opts = {}) {
|
|
|
4424
4472
|
if (!pending.length)
|
|
4425
4473
|
return;
|
|
4426
4474
|
const trusted = new Set([...ownScopePackages(dir), ...localDepDisplayNames(dir)]);
|
|
4427
|
-
const auto = pending.filter(p => trusted.has(p.name));
|
|
4428
|
-
const ask = pending.filter(p => !trusted.has(p.name));
|
|
4429
4475
|
const label = (p) => p.versions.length ? `${p.name}@${p.versions.join('|')}` : p.name;
|
|
4476
|
+
const auto = pending.filter(p => trusted.has(p.name));
|
|
4477
|
+
// 2026-09-07 — Claude Code (Fable 5.1), at Bob's direction: a package already
|
|
4478
|
+
// decided in npm.json5 is applied here without asking; only never-seen
|
|
4479
|
+
// third-party packages reach the prompt, and their answers are remembered.
|
|
4480
|
+
const remembered = globalAllowScripts();
|
|
4481
|
+
const rememberedYes = pending.filter(p => !trusted.has(p.name) && remembered[p.name] === true);
|
|
4482
|
+
const rememberedNo = pending.filter(p => !trusted.has(p.name) && remembered[p.name] === false);
|
|
4483
|
+
const ask = pending.filter(p => !trusted.has(p.name) && !(p.name in remembered));
|
|
4430
4484
|
if (opts.dryRun) {
|
|
4431
|
-
console.log(colors.dim(` [dry-run] allowScripts in ${pkgName}: would approve ${auto.map(label).join(', ') || '(none)'}; would ask about ${ask.map(label).join(', ') || '(none)'}`));
|
|
4485
|
+
console.log(colors.dim(` [dry-run] allowScripts in ${pkgName}: would approve ${[...auto, ...rememberedYes].map(label).join(', ') || '(none)'}; would deny ${rememberedNo.map(label).join(', ') || '(none)'}; would ask about ${ask.map(label).join(', ') || '(none)'}`));
|
|
4432
4486
|
return;
|
|
4433
4487
|
}
|
|
4434
|
-
const approve = auto.map(p => p.name);
|
|
4435
|
-
const deny =
|
|
4488
|
+
const approve = [...auto, ...rememberedYes].map(p => p.name);
|
|
4489
|
+
const deny = rememberedNo.map(p => p.name);
|
|
4436
4490
|
const skipped = [];
|
|
4491
|
+
const decisions = {};
|
|
4437
4492
|
if (ask.length) {
|
|
4438
4493
|
if (!process.stdin.isTTY) {
|
|
4439
4494
|
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(', ')}`));
|
|
@@ -4441,40 +4496,56 @@ export async function ensureAllowScripts(dir, opts = {}) {
|
|
|
4441
4496
|
skipped.push(...ask.map(label));
|
|
4442
4497
|
}
|
|
4443
4498
|
else {
|
|
4444
|
-
console.log(colors.cyan(`npm gates install scripts for ${ask.length} third-party package(s) in ${pkgName}.
|
|
4499
|
+
console.log(colors.cyan(`npm gates install scripts for ${ask.length} third-party package(s) in ${pkgName}. Each answer is recorded in package.json "allowScripts" (name-only), honored on the global install, and remembered for every project in ${userConfigPath}.`));
|
|
4445
4500
|
let allRemaining = false;
|
|
4446
4501
|
for (const p of ask) {
|
|
4447
4502
|
const prebuilt = PREBUILT_SCRIPT_RUNNERS.test(p.scripts) ? ' — prebuilt-binary fetcher, the package ships binaries' : '';
|
|
4448
4503
|
if (allRemaining) {
|
|
4449
4504
|
approve.push(p.name);
|
|
4505
|
+
decisions[p.name] = true;
|
|
4450
4506
|
continue;
|
|
4451
4507
|
}
|
|
4452
4508
|
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']);
|
|
4453
4509
|
switch (answer) {
|
|
4454
4510
|
case 'y':
|
|
4455
4511
|
approve.push(p.name);
|
|
4512
|
+
decisions[p.name] = true;
|
|
4456
4513
|
break;
|
|
4457
4514
|
case 'a':
|
|
4458
4515
|
approve.push(p.name);
|
|
4516
|
+
decisions[p.name] = true;
|
|
4459
4517
|
allRemaining = true;
|
|
4460
4518
|
break;
|
|
4461
4519
|
case 'n':
|
|
4462
4520
|
deny.push(p.name);
|
|
4521
|
+
decisions[p.name] = false;
|
|
4463
4522
|
break;
|
|
4464
4523
|
default:
|
|
4465
4524
|
skipped.push(label(p));
|
|
4466
4525
|
break; // 's', or EOF ('' from promptChoice)
|
|
4467
4526
|
}
|
|
4468
4527
|
}
|
|
4528
|
+
try {
|
|
4529
|
+
rememberAllowScripts(decisions);
|
|
4530
|
+
}
|
|
4531
|
+
catch (error) {
|
|
4532
|
+
// Not swallowed: the project's package.json still gets the answer below;
|
|
4533
|
+
// only the cross-project memory is lost, and the user is told so.
|
|
4534
|
+
console.log(colors.yellow(` ⚠ could not remember install-script decisions in ${userConfigPath}: ${error.message}`));
|
|
4535
|
+
}
|
|
4469
4536
|
}
|
|
4470
4537
|
}
|
|
4538
|
+
if (rememberedYes.length || rememberedNo.length) {
|
|
4539
|
+
console.log(colors.dim(` · install-script decisions applied from ${userConfigPath}: ${[...rememberedYes.map(p => `${p.name} allowed`), ...rememberedNo.map(p => `${p.name} denied`)].join(', ')}`));
|
|
4540
|
+
}
|
|
4471
4541
|
// `npm approve-scripts <name>` matches installed nodes by the same display
|
|
4472
4542
|
// name the pending listing used, and writes the right key for each kind
|
|
4473
4543
|
// (registry name, or the file: path for a linked dep).
|
|
4474
4544
|
if (approve.length) {
|
|
4475
4545
|
const r = await runCommandAsync('npm', ['approve-scripts', '--no-allow-scripts-pin', ...approve], { cwd: dir, silent: true });
|
|
4546
|
+
const autoNote = [auto.length ? `${auto.length} own/local` : '', rememberedYes.length ? `${rememberedYes.length} remembered` : ''].filter(Boolean).join(', ');
|
|
4476
4547
|
if (r.success)
|
|
4477
|
-
console.log(colors.green(` ✓ allowScripts: approved ${approve.join(', ')}${
|
|
4548
|
+
console.log(colors.green(` ✓ allowScripts: approved ${approve.join(', ')}${autoNote ? ` (${autoNote} approved automatically)` : ''}`));
|
|
4478
4549
|
else
|
|
4479
4550
|
console.log(colors.red(` ✗ npm approve-scripts failed: ${(r.stderr || r.output).trim().split('\n').find(l => /npm error/.test(l)) ?? 'see output'}`));
|
|
4480
4551
|
}
|
|
@@ -4888,8 +4959,9 @@ export async function installInWsl(wslArgs, opts = {}) {
|
|
|
4888
4959
|
// list is left out — WSL's npm has its own .npmrc. Probed against WSL's
|
|
4889
4960
|
// npm, which is a separate install from the Windows one.
|
|
4890
4961
|
if (opts.cwd && wslArgs.includes('install') && await wslNpmSupportsAllowScripts()) {
|
|
4891
|
-
|
|
4892
|
-
const
|
|
4962
|
+
// 2026-09-07 — Claude Code (Fable 5.1): remembered npm.json5 decisions apply in WSL too.
|
|
4963
|
+
const policy = effectiveAllowScripts(opts.cwd);
|
|
4964
|
+
const names = [...new Set([...ownScopePackages(opts.cwd), ...policy.allowed])].filter(n => !policy.denied.includes(n));
|
|
4893
4965
|
if (names.length)
|
|
4894
4966
|
wslArgs = [...wslArgs, '--allow-scripts', names.join(',')];
|
|
4895
4967
|
}
|
|
@@ -6669,7 +6741,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
|
|
|
6669
6741
|
const { bump = 'patch', noPublish = false, cleanup = false, install = false, link = false, wsl = false, force = false, files = true, dryRun = false, quiet = true, verbose = false, init = false, gitVisibility = 'private', npmVisibility = 'private', message, conform = false, asis = false, updateDeps = false, updateMajor = false, publishDeps = true, // Default to publishing deps for safety
|
|
6670
6742
|
publishDepsYes = false, // -pd: auto-yes to dep-cascade prompts (private only)
|
|
6671
6743
|
publicDeps = false, // -public-deps: cascade public visibility to all deps
|
|
6672
|
-
noPrescan = false, forcePublish = false, fix = true, fixTags = false, rebase = false, show = false,
|
|
6744
|
+
noPrescan = false, forcePublish = false, fix = true, fixTags = false, rebase = false, show = false, localInstall = false, freeze = false, usePaths = true, allowTs, adopt = false, importCheck = false } = options;
|
|
6673
6745
|
// 2026-09-06 — Claude Code (Fable 5.1), at Bob's direction: a directory marked
|
|
6674
6746
|
// `.stale` is retired. Refuse to build, publish or install it — and because the
|
|
6675
6747
|
// dep cascade reaches deps through this same function, refuse to cascade into
|
|
@@ -6733,7 +6805,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
|
|
|
6733
6805
|
settings.push('--force-publish');
|
|
6734
6806
|
if (configOptions.show)
|
|
6735
6807
|
settings.push('--show');
|
|
6736
|
-
if (configOptions.
|
|
6808
|
+
if (configOptions.localInstall)
|
|
6737
6809
|
settings.push('-local-install');
|
|
6738
6810
|
if (configOptions.freeze)
|
|
6739
6811
|
settings.push('-freeze');
|
|
@@ -6758,7 +6830,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
|
|
|
6758
6830
|
// 2026-09-01 16:30 EDT — Claude Code (Opus 5): this was a verbatim copy of
|
|
6759
6831
|
// doLocalInstall (the 10:55 output-capture note lives there now). One path,
|
|
6760
6832
|
// so the .commitmsg recording added today lands in both entry points.
|
|
6761
|
-
if (
|
|
6833
|
+
if (localInstall) {
|
|
6762
6834
|
return doLocalInstall(cwd, options);
|
|
6763
6835
|
}
|
|
6764
6836
|
// Prescan the file: dep graph up front — surface all problems before
|
|
@@ -6988,7 +7060,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
|
|
|
6988
7060
|
+ ' 5) Abort\nChoice [1]:', ['1', '2', '3', '4', '5', '']);
|
|
6989
7061
|
if (choice === '4') {
|
|
6990
7062
|
console.log(colors.dim('Switching to local-only mode...'));
|
|
6991
|
-
writeConfig(cwd, { ...configOptions,
|
|
7063
|
+
writeConfig(cwd, { ...configOptions, localInstall: true }, new Set(['localInstall']));
|
|
6992
7064
|
return doLocalInstall(cwd, options);
|
|
6993
7065
|
}
|
|
6994
7066
|
if (choice === '5') {
|
|
@@ -7017,7 +7089,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
|
|
|
7017
7089
|
+ ' 4) Abort\nChoice [1]:', ['1', '2', '3', '4', '']);
|
|
7018
7090
|
if (choice === '3') {
|
|
7019
7091
|
console.log(colors.dim('Switching to local-only mode...'));
|
|
7020
|
-
writeConfig(cwd, { ...configOptions,
|
|
7092
|
+
writeConfig(cwd, { ...configOptions, localInstall: true }, new Set(['localInstall']));
|
|
7021
7093
|
return doLocalInstall(cwd, options);
|
|
7022
7094
|
}
|
|
7023
7095
|
if (choice === '4') {
|
|
@@ -7060,7 +7132,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
|
|
|
7060
7132
|
+ ' 4) Abort\nChoice [1]:', ['1', '2', '3', '4', '']);
|
|
7061
7133
|
if (choice === '3') {
|
|
7062
7134
|
console.log(colors.dim('Switching to local-only mode...'));
|
|
7063
|
-
writeConfig(cwd, { ...configOptions,
|
|
7135
|
+
writeConfig(cwd, { ...configOptions, localInstall: true }, new Set(['localInstall']));
|
|
7064
7136
|
return doLocalInstall(cwd, options);
|
|
7065
7137
|
}
|
|
7066
7138
|
if (choice === '4') {
|
|
@@ -7875,8 +7947,8 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
|
|
|
7875
7947
|
surprises.push(`usePaths=false${fromConfig('usePaths')} — file: deps resolved from npm, not siblings`);
|
|
7876
7948
|
if (configOptions.freeze || freeze)
|
|
7877
7949
|
surprises.push(`freeze=true${fromConfig('freeze')}`);
|
|
7878
|
-
if (configOptions.
|
|
7879
|
-
surprises.push(`
|
|
7950
|
+
if (configOptions.localInstall || localInstall)
|
|
7951
|
+
surprises.push(`localInstall=true${fromConfig('localInstall')}`);
|
|
7880
7952
|
if (configOptions.files === false || files === false)
|
|
7881
7953
|
surprises.push(`files=false${fromConfig('files')} — npm versions left in package.json`);
|
|
7882
7954
|
if (configOptions.allowTs === true || allowTs === true)
|
|
@@ -9064,8 +9136,8 @@ export async function globalizeWorkspace(rootDir, options = {}, configOptions =
|
|
|
9064
9136
|
// user up front avoids the surprise of "I asked for install but most
|
|
9065
9137
|
// packages didn't install."
|
|
9066
9138
|
const installSources = [];
|
|
9067
|
-
if (options.
|
|
9068
|
-
const src = configOptions.
|
|
9139
|
+
if (options.localInstall || configOptions.localInstall) {
|
|
9140
|
+
const src = configOptions.localInstall && !options.localInstall ? '.globalize.json5' : 'CLI';
|
|
9069
9141
|
installSources.push(`-local-install (${src})`);
|
|
9070
9142
|
}
|
|
9071
9143
|
if (options.install || configOptions.install) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bobfrankston/npmglobalize",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.224",
|
|
4
4
|
"description": "Transform file: dependencies to npm versions for publishing",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"@bobfrankston/freezepak": "^0.1.9",
|
|
35
35
|
"@bobfrankston/importgen": "^0.1.43",
|
|
36
|
-
"@bobfrankston/themecolors": "^0.1.
|
|
36
|
+
"@bobfrankston/themecolors": "^0.1.10",
|
|
37
37
|
"@bobfrankston/userconfig": "^1.0.11",
|
|
38
38
|
"@npmcli/package-json": "^7.0.4",
|
|
39
39
|
"json5": "^2.2.3",
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
"dependencies": {
|
|
62
62
|
"@bobfrankston/freezepak": "^0.1.9",
|
|
63
63
|
"@bobfrankston/importgen": "^0.1.43",
|
|
64
|
-
"@bobfrankston/themecolors": "^0.1.
|
|
64
|
+
"@bobfrankston/themecolors": "^0.1.10",
|
|
65
65
|
"@bobfrankston/userconfig": "^1.0.11",
|
|
66
66
|
"@npmcli/package-json": "^7.0.4",
|
|
67
67
|
"json5": "^2.2.3",
|