@bobfrankston/npmglobalize 1.0.145 → 1.0.147

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 CHANGED
@@ -86,6 +86,14 @@ npmglobalize
86
86
  npmglobalize -npd # --no-publish-deps
87
87
  ```
88
88
 
89
+ ### 🔍 Prescan (Default)
90
+
91
+ Before any transform/publish, `npmglobalize` walks the full `file:` dep graph and reports all problems up front (unresolvable paths, missing `package.json`, unscoped packages with private intent, etc.). This lets you fix the whole punch list at once rather than being interrupted mid-cascade.
92
+
93
+ Errors abort (unless `--force`); warnings prompt to continue.
94
+
95
+ Skip the prescan with `-no-prescan` / `-nps`.
96
+
89
97
  **Force republish** all file: dependencies even if versions exist:
90
98
  ```bash
91
99
  npmglobalize --force-publish
@@ -264,7 +272,10 @@ Publishing requires being on a branch so commits and tags can be properly tracke
264
272
  ```
265
273
  -update-deps, -ud Update package.json to latest versions (safe: minor/patch)
266
274
  -update-major Allow major version updates (breaking changes)
275
+ -publish-deps Auto-publish file: dependencies (default)
276
+ -pd Like -publish-deps, plus auto-yes to dep-cascade prompts (private only)
267
277
  -no-publish-deps, -npd Skip auto-publishing file: dependencies
278
+ -no-prescan, -nps Skip upfront dep-graph prescan
268
279
  -force-publish Republish dependencies even if version exists
269
280
  -fix Run npm audit fix after transformation
270
281
  -no-fix Don't run npm audit
package/cli.js CHANGED
@@ -31,7 +31,10 @@ Release Options:
31
31
  Dependency Options:
32
32
  -update-deps, -ud Update package.json to latest (minor/patch only, safe)
33
33
  -update-major Allow major version updates (breaking changes)
34
+ -publish-deps Auto-publish file: dependencies (default)
35
+ -pd Like -publish-deps, plus auto-yes to dep-cascade prompts (private only)
34
36
  -no-publish-deps, -npd Don't auto-publish file: dependencies (use with caution)
37
+ -no-prescan, -nps Skip upfront dep-graph prescan
35
38
  -force-publish Republish dependencies even if version exists
36
39
  -fix Run npm audit fix after transformation
37
40
 
@@ -251,10 +254,18 @@ function parseArgs(args) {
251
254
  case '-publish-deps':
252
255
  options.publishDeps = true; // Explicitly enable (though it's default)
253
256
  break;
257
+ case '-pd':
258
+ options.publishDeps = true;
259
+ options.publishDepsYes = true; // Auto-yes to dep-cascade prompts (private only)
260
+ break;
254
261
  case '-no-publish-deps':
255
262
  case '-npd':
256
263
  options.publishDeps = false; // Disable auto-publishing
257
264
  break;
265
+ case '-no-prescan':
266
+ case '-nps':
267
+ options.noPrescan = true;
268
+ break;
258
269
  case '-force-publish':
259
270
  options.forcePublish = true;
260
271
  break;
package/lib/types.d.ts CHANGED
@@ -52,6 +52,10 @@ export interface GlobalizeOptions {
52
52
  updateMajor?: boolean;
53
53
  /** Publish file: dependencies before converting them */
54
54
  publishDeps?: boolean;
55
+ /** Auto-yes to dep-cascade prompts (add scope for private); does NOT auto-yes public prompts */
56
+ publishDepsYes?: boolean;
57
+ /** Skip the upfront dep-graph prescan */
58
+ noPrescan?: boolean;
55
59
  /** Force republish dependencies even if version exists on npm */
56
60
  forcePublish?: boolean;
57
61
  /** Run npm audit and fix vulnerabilities */
package/lib.d.ts CHANGED
@@ -67,6 +67,8 @@ export interface GlobalizeOptions {
67
67
  updateMajor?: boolean;
68
68
  /** Publish file: dependencies before converting them */
69
69
  publishDeps?: boolean;
70
+ /** Auto-yes to dep-cascade prompts (add scope to make private, etc.); does NOT auto-yes public prompts */
71
+ publishDepsYes?: boolean;
70
72
  /** Force republish dependencies even if version exists on npm */
71
73
  forcePublish?: boolean;
72
74
  /** Run npm audit and fix vulnerabilities */
@@ -96,6 +98,10 @@ export interface GlobalizeOptions {
96
98
  /** Internal: auto-initialize git repos without prompting (user chose "all") */
97
99
  autoInit?: boolean;
98
100
  /** Internal: signals this call is from workspace orchestrator */
101
+ /** Skip the upfront dep-graph prescan */
102
+ noPrescan?: boolean;
103
+ /** Internal: recursive dep-cascade call (suppresses prescan + banner) */
104
+ _fromDep?: boolean;
99
105
  _fromWorkspace?: boolean;
100
106
  /** Internal: signals this call is from CLI (version already printed) */
101
107
  _fromCli?: boolean;
@@ -190,6 +196,19 @@ export declare function transformDeps(pkg: any, baseDir: string, verbose?: boole
190
196
  path: string;
191
197
  }>;
192
198
  };
199
+ /** A problem discovered by the prescan. */
200
+ export interface PrescanIssue {
201
+ severity: 'error' | 'warning';
202
+ package: string;
203
+ path: string;
204
+ message: string;
205
+ suggestion?: string;
206
+ }
207
+ /** Walk the full file: dep graph and collect issues up front — missing scopes,
208
+ * unresolvable paths, missing package.json, unpublished transitives. Lets the
209
+ * user resolve all problems before starting the publish cascade instead of
210
+ * being interrupted mid-run. */
211
+ export declare function prescanDepGraph(baseDir: string, visited?: Set<string>, verbose?: boolean): PrescanIssue[];
193
212
  /** Restore file: dependencies from .dependencies using a three-way merge that
194
213
  * preserves any external modifications made since the last transform. */
195
214
  export declare function restoreDeps(pkg: any, verbose?: boolean): boolean;
package/lib.js CHANGED
@@ -986,6 +986,83 @@ function printDepTree(baseDir, indent = 0, visited = new Set()) {
986
986
  }
987
987
  }
988
988
  }
989
+ /** Walk the full file: dep graph and collect issues up front — missing scopes,
990
+ * unresolvable paths, missing package.json, unpublished transitives. Lets the
991
+ * user resolve all problems before starting the publish cascade instead of
992
+ * being interrupted mid-run. */
993
+ export function prescanDepGraph(baseDir, visited = new Set(), verbose = false) {
994
+ const issues = [];
995
+ let realDir;
996
+ try {
997
+ realDir = fs.realpathSync(baseDir);
998
+ }
999
+ catch {
1000
+ return issues;
1001
+ }
1002
+ if (visited.has(realDir))
1003
+ return issues;
1004
+ visited.add(realDir);
1005
+ let pkg;
1006
+ try {
1007
+ pkg = readPackageJson(baseDir);
1008
+ }
1009
+ catch {
1010
+ return issues;
1011
+ }
1012
+ for (const key of DEP_KEYS) {
1013
+ if (!pkg[key])
1014
+ continue;
1015
+ const deps = pkg['.' + key] || pkg[key];
1016
+ for (const [name, value] of Object.entries(deps)) {
1017
+ if (!isFileRef(value))
1018
+ continue;
1019
+ let targetPath;
1020
+ try {
1021
+ targetPath = resolveFilePath(value, baseDir);
1022
+ }
1023
+ catch {
1024
+ issues.push({ severity: 'error', package: name, path: value, message: 'unresolvable file: path' });
1025
+ continue;
1026
+ }
1027
+ if (!fs.existsSync(targetPath)) {
1028
+ issues.push({
1029
+ severity: 'error', package: name, path: targetPath,
1030
+ message: `file: target does not exist`,
1031
+ suggestion: `check that "${value}" is the correct relative path`
1032
+ });
1033
+ continue;
1034
+ }
1035
+ let targetPkg;
1036
+ try {
1037
+ targetPkg = readPackageJson(targetPath);
1038
+ }
1039
+ catch {
1040
+ issues.push({ severity: 'error', package: name, path: targetPath, message: 'no package.json at file: target' });
1041
+ continue;
1042
+ }
1043
+ const depName = targetPkg.name || name;
1044
+ // Unscoped + private-intent: will trigger a mid-cascade scope prompt
1045
+ if (!depName.startsWith('@')) {
1046
+ const depConfig = readConfig(targetPath);
1047
+ const depVis = depConfig.npmVisibility
1048
+ ?? (targetPkg.publishConfig?.access === 'public' ? 'public' : undefined);
1049
+ const noprefix = depConfig.noprefix === true;
1050
+ if (depVis !== 'public' && !noprefix) {
1051
+ issues.push({
1052
+ severity: 'warning',
1053
+ package: depName,
1054
+ path: targetPath,
1055
+ message: 'unscoped package — private publishing will prompt for a scope',
1056
+ suggestion: 'add a scope (e.g., @bobfrankston/) OR set npmVisibility="public" OR set noprefix=true in .globalize.json5'
1057
+ });
1058
+ }
1059
+ }
1060
+ // Recurse into the dep
1061
+ issues.push(...prescanDepGraph(targetPath, visited, verbose));
1062
+ }
1063
+ }
1064
+ return issues;
1065
+ }
989
1066
  /** Restore file: dependencies from .dependencies using a three-way merge that
990
1067
  * preserves any external modifications made since the last transform. */
991
1068
  export function restoreDeps(pkg, verbose = false) {
@@ -2462,7 +2539,8 @@ async function doLocalInstall(cwd, options) {
2462
2539
  }
2463
2540
  export async function globalize(cwd, options = {}, configOptions = {}) {
2464
2541
  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
2465
- forcePublish = false, fix = true, fixTags = false, rebase = false, show = false, local = false, freeze = false } = options;
2542
+ publishDepsYes = false, // -pd: auto-yes to dep-cascade prompts (private only)
2543
+ noPrescan = false, forcePublish = false, fix = true, fixTags = false, rebase = false, show = false, local = false, freeze = false } = options;
2466
2544
  // Show tool version only for recursive dep calls (CLI already prints it at startup)
2467
2545
  const toolVersion = getToolVersion();
2468
2546
  if (!options._fromWorkspace && !options._fromCli) {
@@ -2568,6 +2646,37 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
2568
2646
  }
2569
2647
  return true;
2570
2648
  }
2649
+ // Prescan the file: dep graph up front — surface all problems before
2650
+ // any transform/publish, so the user can fix them once and run clean.
2651
+ // Only run at the top level (not during recursive cascade or workspace).
2652
+ if (!noPrescan && !cleanup && !options._fromDep && !options._fromWorkspace) {
2653
+ const issues = prescanDepGraph(cwd, new Set(), verbose);
2654
+ if (issues.length > 0) {
2655
+ const errors = issues.filter(i => i.severity === 'error');
2656
+ const warnings = issues.filter(i => i.severity === 'warning');
2657
+ console.log(colors.yellow(`Prescan found ${issues.length} issue(s) in the file: dep graph:`));
2658
+ for (const i of issues) {
2659
+ const tag = i.severity === 'error' ? colors.red('ERROR') : colors.yellow('WARN');
2660
+ console.log(` ${tag} ${i.package}: ${i.message}`);
2661
+ console.log(colors.dim(` at ${i.path}`));
2662
+ if (i.suggestion)
2663
+ console.log(colors.dim(` → ${i.suggestion}`));
2664
+ }
2665
+ console.log('');
2666
+ if (errors.length > 0 && !force) {
2667
+ console.error(colors.red(`Prescan aborted: ${errors.length} error(s). Fix them or rerun with --force.`));
2668
+ return false;
2669
+ }
2670
+ if (warnings.length > 0 && !force && !publishDepsYes) {
2671
+ const ok = await confirm('Continue despite prescan warnings?', false);
2672
+ if (!ok)
2673
+ return false;
2674
+ }
2675
+ }
2676
+ else if (verbose) {
2677
+ console.log(colors.dim('Prescan: no issues found in dep graph.'));
2678
+ }
2679
+ }
2571
2680
  // Check ignore files first (unless cleanup mode)
2572
2681
  if (!cleanup && !asis) {
2573
2682
  const checkResult = checkIgnoreFiles(cwd, { conform, asis, verbose });
@@ -3263,7 +3372,9 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
3263
3372
  fix,
3264
3373
  conform, // Propagate conform to dependencies
3265
3374
  publishDeps, // Propagate so transitive deps also get published
3375
+ publishDepsYes, // Propagate auto-yes through cascade
3266
3376
  forcePublish, // Propagate so transitive deps get force-published too
3377
+ _fromDep: true, // Suppress nested prescan
3267
3378
  rebase, // Propagate so behind-remote deps get rebased automatically
3268
3379
  autoInit: options.autoInit // Propagate "init all" choice
3269
3380
  });
@@ -3289,7 +3400,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
3289
3400
  console.log(colors.yellow(' 3. Use --force to continue anyway (NOT RECOMMENDED)'));
3290
3401
  console.log(colors.yellow(' 4. Use -npd (--no-publish-deps) to skip dependency publishing'));
3291
3402
  console.log('');
3292
- if (!force) {
3403
+ if (!force && !publishDepsYes) {
3293
3404
  const shouldContinue = await confirm('Continue with unpublished dependencies?', false);
3294
3405
  if (!shouldContinue) {
3295
3406
  return false;
@@ -3561,17 +3672,11 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
3561
3672
  console.log(colors.green(`✓ Installed globally: ${pkgName}@${pkgVersion}`));
3562
3673
  }
3563
3674
  else {
3564
- // Registry install failed — fall back to local install
3675
+ // Registry install failed — check if it's an auth issue
3565
3676
  const auth = checkNpmAuth();
3566
3677
  if (!auth.authenticated) {
3567
- console.log(colors.yellow(` Registry install failed (${auth.error || 'auth issue'}) — falling back to local install...`));
3568
- }
3569
- else {
3570
- console.log(colors.yellow(` Registry install failed — falling back to local install...`));
3571
- }
3572
- const localFallback = runCommand('npm', ['install', '-g', '.'], { cwd, silent: false, showCommand: true });
3573
- if (localFallback.success) {
3574
- console.log(colors.green(`✓ Installed globally from local: ${pkgName}@${pkgVersion}`));
3678
+ console.error(colors.red(` Registry install failed — npm auth problem: ${auth.error || 'unknown'}`));
3679
+ console.error(colors.yellow(` Run 'npm login' to fix, then: npm install -g ${pkgName}@${pkgVersion}`));
3575
3680
  }
3576
3681
  else {
3577
3682
  console.error(colors.red(`✗ Global install failed`));
@@ -4280,19 +4385,12 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
4280
4385
  console.log(colors.green(`✓ Installed globally: ${pkgName}@${pkgVersion}`));
4281
4386
  }
4282
4387
  else {
4283
- // Registry install failed — may be auth/token issue for private packages.
4284
- // Fall back to local install which doesn't need registry auth.
4388
+ // Registry install failed — check if it's an auth issue
4285
4389
  const auth = checkNpmAuth();
4286
4390
  if (!auth.authenticated) {
4287
- console.log(colors.yellow(` Registry install failed (${auth.error || 'auth issue'}) — falling back to local install...`));
4288
- }
4289
- else {
4290
- console.log(colors.yellow(` Registry install failed — falling back to local install...`));
4291
- }
4292
- const localFallback = runCommand('npm', ['install', '-g', '.'], { cwd, silent: false, showCommand: true });
4293
- if (localFallback.success) {
4294
- globalInstallOk = true;
4295
- console.log(colors.green(`✓ Installed globally from local: ${pkgName}@${pkgVersion}`));
4391
+ console.error(colors.red(`✗ Global install failed — npm auth problem: ${auth.error || 'unknown'}`));
4392
+ console.error(colors.yellow(` Run 'npm login' to fix, then: npm install -g ${pkgName}@${pkgVersion}`));
4393
+ recordBuildIssue(pkgName, 'warning', `Global install failed (${auth.error || 'auth issue'}) — run 'npm login'`);
4296
4394
  }
4297
4395
  else {
4298
4396
  console.error(colors.red(`✗ Global install failed`));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/npmglobalize",
3
- "version": "1.0.145",
3
+ "version": "1.0.147",
4
4
  "description": "Transform file: dependencies to npm versions for publishing",
5
5
  "main": "index.js",
6
6
  "type": "module",