@bobfrankston/npmglobalize 1.0.228 → 1.0.229

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.
Files changed (3) hide show
  1. package/README.md +3 -3
  2. package/lib.js +34 -93
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -92,7 +92,7 @@ Before any transform/publish, `npmglobalize` walks the full `file:` dep graph an
92
92
 
93
93
  Errors abort (unless `--force`); warnings prompt to continue.
94
94
 
95
- An unscoped dependency with private intent is reported differently: it is not a question. npmglobalize never renames a dependency to add a scope (the name is what every consumer imports by, and changing it is the dep owner's own project), so the dep cannot reach npm and neither can the package that depends on it. The first run says so in full, names the dep and its path, records it under `unscopedDepsNoted` in the consumer's `.globalize.json5`, and stops. Later runs print one line per dep and stop. Nothing is asked, because no answer could change the outcome. Fix it in the dep: scope the name in its own `package.json`, or set `npmVisibility: "public"` or `noprefix: true` in its `.globalize.json5`; the note clears itself on the next run. If the cascade reaches such a dep anyway (`-no-prescan`), it stops there with the same message. The rename offer still exists when you run npmglobalize *in* the unscoped package itself. (2026-09-14)
95
+ An unscoped dependency with private intent is reported differently: it is not a question. npmglobalize never renames a dependency to add a scope (the name is what every consumer imports by, and changing it is the dep owner's own project), so the dep cannot reach npm and neither can the package that depends on it. The first run says so in full, names the dep and its path, records it under `unscopedDepsNoted` in the consumer's `.globalize.json5`, and stops. Later runs print one line per dep and stop. Nothing is asked, because no answer could change the outcome. Fix it in the dep: scope the name in its own `package.json`, or set `npmVisibility: "public"` or `noprefix: true` in its `.globalize.json5`; the note clears itself on the next run. If the cascade reaches such a dep anyway (`-no-prescan`), it stops there with the same message. The same holds when you run npmglobalize *in* an unscoped package: it used to ask "Add scope to package name?", and now it warns and assumes no, since a rename changes what every consumer imports. Publishing an unscoped package PUBLIC on purpose takes an explicit `-npm public` or `noprefix: true`. (2026-09-14)
96
96
 
97
97
  Skip the prescan with `-no-prescan` / `-nps`.
98
98
 
@@ -478,7 +478,7 @@ not what you meant to publish, so npmglobalize lists the conflicted files and st
478
478
  -update-major Allow major version updates (breaking changes)
479
479
  -publish-deps Auto-publish file: dependencies (default)
480
480
  -pd Like -publish-deps, plus auto-yes to dep-cascade prompts (private only;
481
- never adds a scope to a dep)
481
+ npmglobalize never adds a scope to any package)
482
482
  -no-publish-deps, -npd Skip auto-publishing file: dependencies
483
483
  -no-prescan, -nps Skip upfront dep-graph prescan
484
484
  -force-publish Republish dependencies even if version exists
@@ -793,7 +793,7 @@ Before transforming or publishing anything, `npmglobalize` builds `file:` depend
793
793
  For each project visited (the target and every transitive `file:` dep):
794
794
 
795
795
  - If `tsconfig.json` is missing or has `"noEmit": true` → **skip** (not a TypeScript build), unless the `build` script runs `importgen` — a plain-JS browser app still needs its import map regenerated.
796
- - If `tsconfig.json` exists but `package.json` has no `"build"` script → **prompt** to add `"build": "tsc"` (plus a `tsc -p <dir>` per sub-project — see below). Decline and that project is skipped.
796
+ - If `tsconfig.json` exists but `package.json` has no `"build"` script → **add** `"build": "tsc"` without asking (2026-09-14; it used to prompt) (plus a `tsc -p <dir>` per sub-project — see below). Decline and that project is skipped.
797
797
  - Otherwise → run `npm run build`. A failure halts the cascade unless `-force` is passed.
798
798
 
799
799
  Cycle-safe via a shared visited set; each project is built at most once per run.
package/lib.js CHANGED
@@ -3604,12 +3604,17 @@ export async function ensureBuildScript(cwd) {
3604
3604
  const subs = await readySubProjects(cwd, readConfig(cwd));
3605
3605
  const tscParts = ['tsc', ...subs.map(subProjectBuildCommand)];
3606
3606
  const newScript = [...(existingBuild ? [existingBuild] : []), ...tscParts].join(' && ');
3607
+ // No question here: a TypeScript project that emits needs a build script, and
3608
+ // "tsc" is the only sensible one, so the answer was always yes. The prompt cost
3609
+ // a keystroke per package for no decision. The importgen and sub-project prompts
3610
+ // above stay, since those rewrite a build script that already works.
3611
+ // 2026-09-14 18:05 EDT — Claude Code (Fable 5.1), at Bob's direction ("is there
3612
+ // a command option to avoid having to answer questions like whether to update
3613
+ // tsconfig?" — and "-yes is too general", so the question is gone instead).
3607
3614
  console.log(colors.yellow(`TypeScript project has no "build" script in ${pkg.name || cwd}`));
3608
3615
  for (const s of subs)
3609
3616
  console.log(colors.yellow(` sub-project ${s.dir}/ — ${s.reason}`));
3610
- const addIt = await confirm(`Add "build": "${newScript}" to ${pkg.name || path.basename(cwd)}'s package.json?`, true);
3611
- if (!addIt)
3612
- return null;
3617
+ console.log(colors.cyan(` Adding "build": "${newScript}" to ${pkg.name || path.basename(cwd)}'s package.json`));
3613
3618
  if (!pkg.scripts)
3614
3619
  pkg.scripts = {};
3615
3620
  pkg.scripts.build = newScript;
@@ -7528,60 +7533,33 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
7528
7533
  publicConfirmed = true;
7529
7534
  }
7530
7535
  }
7531
- // A dependency reached through the cascade is never renamed. Adding a scope changes the
7532
- // name every consumer imports by, which is the dep owner's project, not a publish-time
7533
- // side effect. Refuse and say what to fix; the top-level run on the package itself may
7534
- // still offer the rename below, because there the user chose to work on that package.
7535
- // 2026-09-14 16:40 EDT Claude Code (Fable 5.1), at Bob's direction: "It shouldn't try
7536
- // to publish to a scope not in the package. I need to fix the package and that's a
7537
- // separate and messy project."
7538
- // noprefix in the dep's .globalize.json5 is the same escape hatch the prescan honors.
7539
- if (!isScoped && options._fromDep && effectiveNpmVisibility !== 'public' && currentAccess !== 'public'
7540
- && readConfig(cwd).noprefix !== true) {
7541
- console.error(colors.red(`Dependency '${pkg.name}' is unscoped and cannot be published privately.`));
7542
- console.error(colors.red(` npmglobalize does not rename dependencies. Scope it in ${path.join(cwd, 'package.json')},`));
7543
- console.error(colors.red(` or set npmVisibility="public" or noprefix=true in its .globalize.json5.`));
7536
+ // An unscoped name cannot be published privately, and npmglobalize does not rename a
7537
+ // package to fix that: the name is what every consumer imports by, so adding a scope
7538
+ // is the package owner's own project, not a publish-time side effect. This used to
7539
+ // ask "Add scope to package name?"; now it warns and assumes no, for a dependency in
7540
+ // the cascade and for the package itself alike. Publishing unscoped as PUBLIC takes an
7541
+ // explicit -npm public, or noprefix: true in .globalize.json5 (the escape hatch the
7542
+ // prescan honors too).
7543
+ // 2026-09-14 18:10 EDT Claude Code (Fable 5.1), at Bob's direction: "It shouldn't try
7544
+ // to publish to a scope not in the package" and, on the prompt in the package itself,
7545
+ // "For libraries that is problematic since other programs are affected so just warn
7546
+ // and assume no."
7547
+ const unscopedAllowed = readConfig(cwd).noprefix === true;
7548
+ if (!isScoped && effectiveNpmVisibility !== 'public' && currentAccess !== 'public' && !unscopedAllowed) {
7549
+ const scopeHint = readUserNpmConfig().scope || '@scope';
7550
+ console.log(colors.yellow(`${pkg.name} is unscoped, so it cannot be published privately. Not publishing until the name in ${path.join(cwd, 'package.json')} has a scope (e.g., ${scopeHint}/${pkg.name}).`));
7551
+ console.log(colors.dim(` npmglobalize does not rename packages. To publish it unscoped and PUBLIC on purpose: -npm public, or noprefix: true in .globalize.json5.`));
7544
7552
  return false;
7545
7553
  }
7546
7554
  if (effectiveNpmVisibility === 'private') {
7547
7555
  // User explicitly wants private publication
7548
7556
  if (!isScoped) {
7549
- // Offer to add scope check .userconfig first, then npm whoami
7550
- const userConfig = readUserNpmConfig();
7551
- const auth = checkNpmAuth();
7552
- const defaultScope = userConfig.scope
7553
- || (auth.username ? `@${auth.username}` : undefined);
7554
- const scopedExample = defaultScope ? `${defaultScope}/${pkg.name}` : `@scope/${pkg.name}`;
7555
- console.log(colors.yellow(`Private packages must be scoped (e.g., ${scopedExample})`));
7556
- if (dryRun) {
7557
- console.log(' [dry-run] Would prompt to add scope');
7558
- return false;
7559
- }
7560
- const addScope = await confirm(`Add scope to package name?`, true);
7561
- if (!addScope) {
7562
- return false;
7563
- }
7564
- const scope = await promptText('Scope:', defaultScope);
7565
- if (!scope) {
7566
- console.error(colors.red('No scope provided. Aborting.'));
7567
- return false;
7568
- }
7569
- // Normalize: ensure it starts with @
7570
- const normalizedScope = scope.startsWith('@') ? scope : `@${scope}`;
7571
- const newName = `${normalizedScope}/${pkg.name}`;
7572
- pkg.name = newName;
7573
- isScoped = true;
7574
- currentAccess = checkNpmAccess(newName);
7575
- writePackageJson(cwd, pkg);
7576
- console.log(colors.green(`✓ Renamed package to ${newName}`));
7577
- // Save scope to .userconfig if not already there
7578
- if (!userConfig.scope) {
7579
- const saveScope = await confirm(`Save scope "${normalizedScope}" to global config (${getUserConfigDir()}\\npm.json5)?`, true);
7580
- if (saveScope) {
7581
- writeUserNpmConfig({ scope: normalizedScope });
7582
- console.log(colors.green(`✓ Saved default scope to ${getUserConfigDir()}\\npm.json5`));
7583
- }
7584
- }
7557
+ // Only reachable with noprefix: true (the unscoped guard above returned
7558
+ // otherwise). noprefix says "publish unscoped", private says "restricted",
7559
+ // and npm cannot do both. The rename prompt that used to live here is gone
7560
+ // (2026-09-14, see the guard above).
7561
+ console.log(colors.yellow(`${pkg.name} is unscoped (noprefix) and npm cannot restrict an unscoped package. Not publishing; use -npm public to publish it unscoped and PUBLIC.`));
7562
+ return false;
7585
7563
  }
7586
7564
  if (currentAccess === 'public') {
7587
7565
  console.log(colors.yellow(`Package '${pkg.name}' is currently PUBLIC on npm. Converting to private...`));
@@ -7653,47 +7631,10 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
7653
7631
  console.log(colors.dim(` Use --npm public to make it public`));
7654
7632
  }
7655
7633
  else {
7656
- // Unscoped new package prompt to add scope (same as explicit-private path)
7657
- const ucfg = readUserNpmConfig();
7658
- const auth2 = checkNpmAuth();
7659
- const defaultScope = ucfg.scope
7660
- || (auth2.username ? `@${auth2.username}` : undefined);
7661
- const scopedExample = defaultScope ? `${defaultScope}/${pkg.name}` : `@scope/${pkg.name}`;
7662
- console.log(colors.yellow(`WARNING: Package '${pkg.name}' is unscoped and will be PUBLIC.`));
7663
- console.log(colors.yellow(` Unscoped packages cannot be private on npm.`));
7664
- if (dryRun) {
7665
- console.log(` [dry-run] Would prompt to add scope (e.g., ${scopedExample})`);
7666
- }
7667
- else {
7668
- const addScope = await confirm(`Add scope to make it private (e.g., ${scopedExample})?`, true);
7669
- if (addScope) {
7670
- const scope = await promptText('Scope:', defaultScope);
7671
- if (scope) {
7672
- const normalizedScope = scope.startsWith('@') ? scope : `@${scope}`;
7673
- const newName = `${normalizedScope}/${pkg.name}`;
7674
- pkg.name = newName;
7675
- isScoped = true;
7676
- currentAccess = checkNpmAccess(newName);
7677
- writePackageJson(cwd, pkg);
7678
- console.log(colors.green(`✓ Renamed package to ${newName}`));
7679
- if (!ucfg.scope) {
7680
- const saveScope = await confirm(`Save scope "${normalizedScope}" to global config (${getUserConfigDir()}\\npm.json5)?`, true);
7681
- if (saveScope) {
7682
- writeUserNpmConfig({ scope: normalizedScope });
7683
- console.log(colors.green(`✓ Saved default scope to ${getUserConfigDir()}\\npm.json5`));
7684
- }
7685
- }
7686
- }
7687
- else {
7688
- console.log(colors.yellow(` No scope provided. Continuing as public.`));
7689
- console.log(colors.yellow(` Use --npm public to suppress this prompt.`));
7690
- }
7691
- }
7692
- else {
7693
- console.log(colors.yellow(` Continuing as public.`));
7694
- console.log(colors.yellow(` Use --npm public to suppress this prompt.`));
7695
- }
7696
- }
7634
+ // Unscoped new package. Only reachable with noprefix: true (the unscoped
7635
+ // guard above returned otherwise), so publishing it PUBLIC is the stated
7636
+ // intent. The rename prompt that used to live here is gone (2026-09-14).
7637
+ console.log(colors.yellow(`Package '${pkg.name}' is unscoped (noprefix) and will publish as PUBLIC — npm cannot restrict an unscoped package.`));
7697
7638
  }
7698
7639
  }
7699
7640
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/npmglobalize",
3
- "version": "1.0.228",
3
+ "version": "1.0.229",
4
4
  "description": "Transform file: dependencies to npm versions for publishing",
5
5
  "main": "index.js",
6
6
  "type": "module",