@bobfrankston/npmglobalize 1.0.151 → 1.0.153

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
@@ -350,6 +350,11 @@ Workspace mode is auto-detected when run from a root with `"private": true` and
350
350
  -asis Skip ignore file checks (or set "asis": true in .globalize.json5)
351
351
  -fix-tags Automatically fix version/tag mismatches
352
352
  -rebase Automatically rebase if local is behind remote
353
+ -clean-nested-modules, -clean-nested
354
+ Before npm pack, wipe node_modules/ inside each file: dep
355
+ target. Fixes arborist "Cannot read properties of null"
356
+ crashes caused by sibling file: deps with nested
357
+ node_modules. Suggested automatically when the error hits.
353
358
  -show Show package.json dependency changes
354
359
  -package, -pkg Update package.json scripts to use npmglobalize (see below)
355
360
  -h, -help Show help
package/cli.js CHANGED
@@ -75,6 +75,10 @@ Other Options:
75
75
  (noEmit projects: removes TS ignores from .npmignore)
76
76
  -asis Skip ignore file checks (or set "asis": true in .globalize.json5)
77
77
  -rebase Automatically rebase if local is behind remote
78
+ -clean-nested-modules, -clean-nested
79
+ Before npm pack, wipe node_modules/ in each file: dep target.
80
+ Fixes arborist "Cannot read properties of null" crashes when
81
+ sibling file: deps have their own nested node_modules.
78
82
  -show Show package.json dependency changes
79
83
  -package, -pkg Update package.json scripts to use npmglobalize
80
84
  -h, -help Show this help
@@ -318,6 +322,11 @@ function parseArgs(args) {
318
322
  options.importgen = false;
319
323
  options.explicitKeys.add('importgen');
320
324
  break;
325
+ case '-clean-nested':
326
+ case '-clean-nested-modules':
327
+ options.cleanNestedModules = true;
328
+ options.explicitKeys.add('cleanNestedModules');
329
+ break;
321
330
  default:
322
331
  if (arg.startsWith('-')) {
323
332
  unrecognized.push(arg);
package/lib/config.js CHANGED
@@ -64,7 +64,7 @@ export function writeConfig(dir, config, explicitKeys) {
64
64
  const existing = readConfig(dir);
65
65
  // Filter out temporary flags and default values (unless explicitly set)
66
66
  const filtered = {};
67
- const omitKeys = new Set(['cleanup', 'init', 'dryRun', 'message', 'conform', 'asis', 'help', 'error', 'updateDeps', 'updateMajor', 'publishDeps', 'forcePublish', 'once']);
67
+ const omitKeys = new Set(['cleanup', 'init', 'dryRun', 'message', 'conform', 'asis', 'help', 'error', 'updateDeps', 'updateMajor', 'publishDeps', 'forcePublish', 'once', 'cleanNestedModules']);
68
68
  for (const [key, value] of Object.entries(config)) {
69
69
  if (omitKeys.has(key))
70
70
  continue;
package/lib/types.d.ts CHANGED
@@ -82,6 +82,9 @@ export interface GlobalizeOptions {
82
82
  local?: boolean;
83
83
  /** Freeze node_modules: replace symlinks/junctions with real copies for network share use */
84
84
  freeze?: boolean;
85
+ /** Before `npm pack`, delete `node_modules/` inside each `file:` dep target.
86
+ * Works around arborist crashes when siblings have nested node_modules. */
87
+ cleanNestedModules?: boolean;
85
88
  /** Internal: signals this call is from workspace orchestrator */
86
89
  _fromWorkspace?: boolean;
87
90
  /** Internal: signals this call is from CLI (version already printed) */
package/lib.d.ts CHANGED
@@ -95,6 +95,9 @@ export interface GlobalizeOptions {
95
95
  local?: boolean;
96
96
  /** Freeze node_modules: replace symlinks/junctions with real copies for network share use */
97
97
  freeze?: boolean;
98
+ /** Before `npm pack`, delete `node_modules/` inside each `file:` dep target.
99
+ * Works around arborist crashes when siblings have nested node_modules. */
100
+ cleanNestedModules?: boolean;
98
101
  /** Internal: auto-initialize git repos without prompting (user chose "all") */
99
102
  autoInit?: boolean;
100
103
  /** Internal: signals this call is from workspace orchestrator */
package/lib.js CHANGED
@@ -236,7 +236,7 @@ export function writeConfig(dir, config, explicitKeys) {
236
236
  const existing = readConfig(dir);
237
237
  // Filter out temporary flags and default values (unless explicitly set)
238
238
  const filtered = {};
239
- const omitKeys = new Set(['cleanup', 'init', 'dryRun', 'message', 'conform', 'asis', 'help', 'error', 'updateDeps', 'updateMajor', 'publishDeps', 'forcePublish', 'once']);
239
+ const omitKeys = new Set(['cleanup', 'init', 'dryRun', 'message', 'conform', 'asis', 'help', 'error', 'updateDeps', 'updateMajor', 'publishDeps', 'forcePublish', 'once', 'cleanNestedModules']);
240
240
  for (const [key, value] of Object.entries(config)) {
241
241
  if (omitKeys.has(key))
242
242
  continue;
@@ -1262,6 +1262,59 @@ function waitForNpmVersion(pkgName, version, maxWaitMs = 90000) {
1262
1262
  process.stdout.write(' timed out\n');
1263
1263
  return false;
1264
1264
  }
1265
+ function cleanNestedDepModules(pkg, cwd, verbose) {
1266
+ const stashed = [];
1267
+ const seen = new Set();
1268
+ const suffix = `.npmglobalize-stash-${process.pid}`;
1269
+ for (const key of ['.dependencies', '.devDependencies', 'dependencies', 'devDependencies']) {
1270
+ const deps = pkg?.[key];
1271
+ if (!deps || typeof deps !== 'object')
1272
+ continue;
1273
+ for (const [name, spec] of Object.entries(deps)) {
1274
+ if (seen.has(name))
1275
+ continue;
1276
+ if (typeof spec !== 'string' || !spec.startsWith('file:'))
1277
+ continue;
1278
+ const target = path.resolve(cwd, spec.slice('file:'.length));
1279
+ const nm = path.join(target, 'node_modules');
1280
+ if (!fs.existsSync(nm))
1281
+ continue;
1282
+ seen.add(name);
1283
+ const backup = nm + suffix;
1284
+ try {
1285
+ if (fs.existsSync(backup))
1286
+ fs.rmSync(backup, { recursive: true, force: true });
1287
+ fs.renameSync(nm, backup);
1288
+ stashed.push({ name, nm, backup });
1289
+ if (verbose)
1290
+ console.log(colors.dim(` stashed ${nm} -> ${path.basename(backup)}`));
1291
+ }
1292
+ catch (e) {
1293
+ console.error(colors.yellow(` warning: could not stash ${nm}: ${e.message}`));
1294
+ }
1295
+ }
1296
+ }
1297
+ return stashed;
1298
+ }
1299
+ /** Restore `node_modules/` previously moved aside by `cleanNestedDepModules`.
1300
+ * Safe to call multiple times; missing backups are skipped. */
1301
+ function restoreNestedDepModules(stashed, verbose) {
1302
+ for (const s of stashed) {
1303
+ try {
1304
+ if (!fs.existsSync(s.backup))
1305
+ continue;
1306
+ if (fs.existsSync(s.nm))
1307
+ fs.rmSync(s.nm, { recursive: true, force: true });
1308
+ fs.renameSync(s.backup, s.nm);
1309
+ if (verbose)
1310
+ console.log(colors.dim(` restored ${s.nm}`));
1311
+ }
1312
+ catch (e) {
1313
+ console.error(colors.yellow(` warning: could not restore ${s.nm}: ${e.message}`));
1314
+ console.error(colors.yellow(` backup remains at ${s.backup} — restore manually`));
1315
+ }
1316
+ }
1317
+ }
1265
1318
  /** Run npm install -g with retries for registry propagation delay */
1266
1319
  function installGlobalWithRetry(pkgSpec, cwd, maxRetries = 3) {
1267
1320
  let result = runCommand('npm', ['install', '-g', pkgSpec], { cwd, silent: false, showCommand: true });
@@ -4237,8 +4290,25 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
4237
4290
  if (verbose) {
4238
4291
  console.log(colors.green(`✓ Authenticated as ${authStatus.username}`));
4239
4292
  }
4293
+ // Optionally stash nested node_modules in file: dep targets before pack
4294
+ // (arborist crashes when sibling file: deps have populated node_modules).
4295
+ // Restored immediately after pack so symlinked file: deps stay runnable.
4296
+ let stashedDepModules = [];
4297
+ if (options.cleanNestedModules) {
4298
+ stashedDepModules = cleanNestedDepModules(pkg, cwd, verbose);
4299
+ if (stashedDepModules.length > 0) {
4300
+ console.log(colors.yellow(` Cleaned node_modules in ${stashedDepModules.length} file: dep target(s): ${stashedDepModules.map(s => s.name).join(', ')}`));
4301
+ }
4302
+ else if (verbose) {
4303
+ console.log(colors.dim(' --clean-nested-modules: nothing to clean'));
4304
+ }
4305
+ }
4240
4306
  // Create tarball first
4241
4307
  const packResult = runCommand('npm', ['pack'], { cwd, silent: true });
4308
+ // Restore stashed node_modules now that pack is done — must happen
4309
+ // before any subsequent install -g symlinks to these dep targets.
4310
+ if (stashedDepModules.length > 0)
4311
+ restoreNestedDepModules(stashedDepModules, verbose);
4242
4312
  if (!packResult.success) {
4243
4313
  const d = diagnoseNpmPackFailure(cwd, packResult.output, packResult.stderr, pkg);
4244
4314
  console.error(colors.red(`ERROR: ${d.summary}`));
@@ -4248,6 +4318,9 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
4248
4318
  console.error(colors.yellow(` Caused by referenced module: ${d.referencedModule}`));
4249
4319
  if (d.hint)
4250
4320
  console.error(colors.yellow(` Hint: ${d.hint}`));
4321
+ if (d.summary.includes('arborist') && !options.cleanNestedModules) {
4322
+ console.error(colors.yellow(` Retry with --clean-nested-modules to wipe node_modules/ inside each file: dep target`));
4323
+ }
4251
4324
  recordBuildIssue(pkg.name || path.basename(cwd), 'error', d.referencedModule ? `${d.summary} (via ${d.referencedModule})` : d.summary);
4252
4325
  return false;
4253
4326
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/npmglobalize",
3
- "version": "1.0.151",
3
+ "version": "1.0.153",
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.7",
35
- "@bobfrankston/importgen": "^0.1.33",
35
+ "@bobfrankston/importgen": "^0.1.34",
36
36
  "@bobfrankston/themecolors": "^0.1.5",
37
37
  "@bobfrankston/userconfig": "^1.0.7",
38
38
  "@npmcli/package-json": "^7.0.4",
@@ -60,7 +60,7 @@
60
60
  ".transformedSnapshot": {
61
61
  "dependencies": {
62
62
  "@bobfrankston/freezepak": "^0.1.7",
63
- "@bobfrankston/importgen": "^0.1.33",
63
+ "@bobfrankston/importgen": "^0.1.34",
64
64
  "@bobfrankston/themecolors": "^0.1.5",
65
65
  "@bobfrankston/userconfig": "^1.0.7",
66
66
  "@npmcli/package-json": "^7.0.4",