@bobfrankston/npmglobalize 1.0.239 → 1.0.240
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 +2 -0
- package/lib.d.ts +18 -2
- package/lib.js +97 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -96,6 +96,8 @@ An unscoped dependency with private intent is reported differently: it is not a
|
|
|
96
96
|
|
|
97
97
|
**Own-scope packages installed from npm instead of linked.** When a package in the `file:` graph depends on one of its own-scope packages by version range (`"@bobfrankston/miscassists": "^1.0.61"`) rather than `file:`, npm puts a registry copy in that package's `node_modules` instead of a link to the local source. The consumer is usually nested under other `file:` deps, where neither the `+` list nor the dependency tree shows it, so the prescan finds every such edge. Since 2026-09-23 it **fixes** them: the range is rewritten to a `file:` path in the consumer's `package.json` and `npm install` runs in that consumer, so `node_modules` holds the link. The local source is the directory the same package is linked from elsewhere in the graph, or else the sibling directory named after the package (the `file:../<name>` convention), accepted only when its `package.json` carries that exact name. A range whose local source cannot be found either way is reported and left alone. Nothing is asked. A package with `"files": false` in its `.globalize.json5` keeps npm refs on purpose and is not touched. Each outcome (rewritten, or not found) is repeated as one line in the end-of-run **Issues Summary**. A failed `npm install` stops the run unless `-force`. `-dry-run` prints what would be rewritten. (2026-09-21, fixer 2026-09-23)
|
|
98
98
|
|
|
99
|
+
**A `file:` dependency declared under the wrong key.** `"ubntdefs": "file:../ubntdefs"` links fine locally even when that package's own `package.json` says `@bobfrankston/ubntdefs`, because npm links a `file:` dep under whatever key you wrote. But the published `package.json` carries the version range under that key, so the registry copy of the consumer says `"ubntdefs": "^1.0.5"`, a name that is not on npm, and every registry install of it or of anything depending on it dies with E404 (dbx10 → setcolor, 2026-09-24). The prescan compares every `file:` key with the target's own name and **fixes** a mismatch: the key is renamed to the real name in the consumer's `package.json` (the `file:` path is kept), `npm uninstall <old key>` drops the link under the old name, and `npm install` links it under the real one. Nothing is asked; `-dry-run` prints what would change; each rename and any failed uninstall is repeated in the **Issues Summary**; a failed install stops the run unless `-force`. Imports in the consumer's code are not touched, so if anything imported by the old key, fix that by hand. A run with `-no-prescan` that reaches such a key stops in the transform with the same explanation rather than publishing a broken `package.json`. (2026-09-24)
|
|
100
|
+
|
|
99
101
|
Skip the prescan with `-no-prescan` / `-nps`.
|
|
100
102
|
|
|
101
103
|
#### Workspace skip prescan
|
package/lib.d.ts
CHANGED
|
@@ -425,9 +425,13 @@ export interface PrescanIssue {
|
|
|
425
425
|
/** 'unscoped': an unscoped file: dep with private intent. Not an error and not a
|
|
426
426
|
* question — globalize reports it once, remembers it, and stops.
|
|
427
427
|
* 'registryRef': a package in the file: graph depends on one of its own-scope packages
|
|
428
|
-
* by version range instead of file: — rewritten to file: by fixRegistryRefs (2026-09-23), nothing asked.
|
|
429
|
-
|
|
428
|
+
* by version range instead of file: — rewritten to file: by fixRegistryRefs (2026-09-23), nothing asked.
|
|
429
|
+
* 'nameMismatch': a file: dep declared under a key that is not the target's package.json name —
|
|
430
|
+
* key renamed, old name uninstalled, reinstalled by fixDepKeyMismatches (2026-09-24), nothing asked. */
|
|
431
|
+
kind?: 'unscoped' | 'registryRef' | 'nameMismatch';
|
|
430
432
|
package: string;
|
|
433
|
+
/** 'nameMismatch' only: the key as written in the consumer's package.json (`ubntdefs`). */
|
|
434
|
+
key?: string;
|
|
431
435
|
/** For 'registryRef': the directory of the CONSUMER whose package.json holds the range. */
|
|
432
436
|
path: string;
|
|
433
437
|
/** 'registryRef' only: name of the package whose package.json declares the dep. */
|
|
@@ -465,6 +469,18 @@ export declare function fixRegistryRefs(registryRefs: PrescanIssue[], fileTarget
|
|
|
465
469
|
dryRun?: boolean;
|
|
466
470
|
verbose?: boolean;
|
|
467
471
|
}): Promise<boolean>;
|
|
472
|
+
/** Rename file: dependency keys that differ from the target package's own name, then
|
|
473
|
+
* `npm uninstall` the old name and `npm install` so node_modules holds the link under the
|
|
474
|
+
* real name. The published package.json carries the version range under the KEY, so a
|
|
475
|
+
* wrong key publishes a dependency nobody can install (dbx10 → `"ubntdefs": "^1.0.5"`,
|
|
476
|
+
* setcolor's install died with E404 on 2026-09-24). Nothing is asked; -dry-run prints what
|
|
477
|
+
* would change; each outcome lands in the Issues Summary.
|
|
478
|
+
* 2026-09-24 16:20 EDT — Claude Code (Fable 5.1), at Bob's direction: "A check would be to
|
|
479
|
+
* look at the file; to uninstall from the old name and reinstall with the new. This is safe." */
|
|
480
|
+
export declare function fixDepKeyMismatches(mismatches: PrescanIssue[], opts?: {
|
|
481
|
+
dryRun?: boolean;
|
|
482
|
+
verbose?: boolean;
|
|
483
|
+
}): Promise<boolean>;
|
|
468
484
|
/** Report unscoped `file:` deps found by the prescan, without asking anything.
|
|
469
485
|
*
|
|
470
486
|
* An unscoped dep with private intent cannot reach npm, and npmglobalize does not
|
package/lib.js
CHANGED
|
@@ -1757,6 +1757,13 @@ export function transformDeps(pkg, baseDir, verbose = false, forcePublish = fals
|
|
|
1757
1757
|
console.log(colors.blue(` + ${name} → ${value}`));
|
|
1758
1758
|
try {
|
|
1759
1759
|
const targetPkg = readPackageJson(targetPath);
|
|
1760
|
+
// 2026-09-24 — Claude Code (Fable 5.1), at Bob's direction: the range below goes
|
|
1761
|
+
// under the KEY, so a key that is not the target's name would publish a dep nobody
|
|
1762
|
+
// can install (dbx10's "ubntdefs": "^1.0.5"). The prescan renames such keys; this
|
|
1763
|
+
// is the stop for a run that skipped it (-no-prescan).
|
|
1764
|
+
if (targetPkg.name && targetPkg.name !== name) {
|
|
1765
|
+
throw new Error(`dependency key "${name}" names a package whose package.json says ${targetPkg.name}; published as "${name}" it could not be installed. Rerun without -no-prescan to have the key renamed, or rename it by hand`);
|
|
1766
|
+
}
|
|
1760
1767
|
const targetVersion = targetPkg.version;
|
|
1761
1768
|
const npmVersion = '^' + targetVersion;
|
|
1762
1769
|
// Check if this version exists on npm (or if force publish)
|
|
@@ -2075,6 +2082,21 @@ export function prescanDepGraph(baseDir, visited = new Set(), verbose = false, f
|
|
|
2075
2082
|
}
|
|
2076
2083
|
const depName = targetPkg.name || name;
|
|
2077
2084
|
fileTargets.set(depName, targetPath);
|
|
2085
|
+
// 2026-09-24 16:20 EDT — Claude Code (Fable 5.1), at Bob's direction. A file: dep
|
|
2086
|
+
// declared under a key that is not the target's package.json name ("ubntdefs":
|
|
2087
|
+
// file:… whose package.json says "@bobfrankston/ubntdefs") links fine locally, but
|
|
2088
|
+
// transformDeps writes the version range under the KEY, so the published dbx10 said
|
|
2089
|
+
// `"ubntdefs": "^1.0.5"` — a name that is not on npm — and every registry install of
|
|
2090
|
+
// a consumer (setcolor) died with E404. Flagged here; fixDepKeyMismatches renames the
|
|
2091
|
+
// key, uninstalls the old name and reinstalls. Bob: "uninstall from the old name and
|
|
2092
|
+
// reinstall with the new. This is safe."
|
|
2093
|
+
if (targetPkg.name && targetPkg.name !== name) {
|
|
2094
|
+
issues.push({
|
|
2095
|
+
severity: 'warning', kind: 'nameMismatch',
|
|
2096
|
+
package: targetPkg.name, key: name, path: baseDir, consumer: consumerName, range: value,
|
|
2097
|
+
message: `${consumerName} declares ${targetPkg.name} under the key "${name}" (${value})`
|
|
2098
|
+
});
|
|
2099
|
+
}
|
|
2078
2100
|
// Unscoped + private-intent. This used to be a WARNING whose "continue" led
|
|
2079
2101
|
// into the cascade prompting to rename the dep by adding a scope. The cascade
|
|
2080
2102
|
// never renames a dependency now (see the _fromDep check in globalize), so it
|
|
@@ -2197,6 +2219,72 @@ export async function fixRegistryRefs(registryRefs, fileTargets, opts = {}) {
|
|
|
2197
2219
|
console.log('');
|
|
2198
2220
|
return ok;
|
|
2199
2221
|
}
|
|
2222
|
+
/** Rename file: dependency keys that differ from the target package's own name, then
|
|
2223
|
+
* `npm uninstall` the old name and `npm install` so node_modules holds the link under the
|
|
2224
|
+
* real name. The published package.json carries the version range under the KEY, so a
|
|
2225
|
+
* wrong key publishes a dependency nobody can install (dbx10 → `"ubntdefs": "^1.0.5"`,
|
|
2226
|
+
* setcolor's install died with E404 on 2026-09-24). Nothing is asked; -dry-run prints what
|
|
2227
|
+
* would change; each outcome lands in the Issues Summary.
|
|
2228
|
+
* 2026-09-24 16:20 EDT — Claude Code (Fable 5.1), at Bob's direction: "A check would be to
|
|
2229
|
+
* look at the file; to uninstall from the old name and reinstall with the new. This is safe." */
|
|
2230
|
+
export async function fixDepKeyMismatches(mismatches, opts = {}) {
|
|
2231
|
+
if (!mismatches.length)
|
|
2232
|
+
return true;
|
|
2233
|
+
const { dryRun = false, verbose = false } = opts;
|
|
2234
|
+
console.log(colors.yellow(`${mismatches.length} file: dependenc${mismatches.length === 1 ? 'y is' : 'ies are'} declared under a key that is not the package's own name (published that way, npm would look for a name that does not exist):`));
|
|
2235
|
+
// One uninstall + install per consumer, after all of its keys are renamed.
|
|
2236
|
+
const touched = new Map();
|
|
2237
|
+
for (const m of mismatches) {
|
|
2238
|
+
const pkgJsonPath = path.join(m.path, 'package.json');
|
|
2239
|
+
console.log(` ${colors.yellow('≠')} ${m.consumer}: "${m.key}" → ${m.package} (${m.range})`);
|
|
2240
|
+
console.log(colors.dim(` in ${pkgJsonPath}`));
|
|
2241
|
+
if (dryRun) {
|
|
2242
|
+
console.log(colors.dim(` [dry-run] would rename the key to ${m.package}, then npm uninstall ${m.key} and npm install in ${m.consumer}`));
|
|
2243
|
+
continue;
|
|
2244
|
+
}
|
|
2245
|
+
const pkg = readPackageJson(m.path);
|
|
2246
|
+
let rewrote = false;
|
|
2247
|
+
for (const key of DEP_KEYS) {
|
|
2248
|
+
// A `.dependencies` backup (left by an interrupted transform) is the file: truth;
|
|
2249
|
+
// prescanDepGraph read the key from it, so the rename goes there too.
|
|
2250
|
+
for (const depsKey of ['.' + key, key]) {
|
|
2251
|
+
const deps = pkg[depsKey];
|
|
2252
|
+
if (!deps || deps[m.key] !== m.range)
|
|
2253
|
+
continue;
|
|
2254
|
+
delete deps[m.key];
|
|
2255
|
+
deps[m.package] = m.range;
|
|
2256
|
+
rewrote = true;
|
|
2257
|
+
}
|
|
2258
|
+
}
|
|
2259
|
+
if (!rewrote) {
|
|
2260
|
+
console.log(colors.dim(` key not found in ${pkgJsonPath} any more — left as is`));
|
|
2261
|
+
continue;
|
|
2262
|
+
}
|
|
2263
|
+
writePackageJson(m.path, pkg);
|
|
2264
|
+
console.log(colors.green(` ✓ renamed to ${m.package}`));
|
|
2265
|
+
recordBuildIssue(m.consumer, 'warning', `dependency key "${m.key}" renamed to ${m.package} (the file: target's own name) in ${pkgJsonPath}`);
|
|
2266
|
+
const entry = touched.get(m.path) ?? { consumer: m.consumer, oldKeys: [], newNames: [] };
|
|
2267
|
+
entry.oldKeys.push(m.key);
|
|
2268
|
+
entry.newNames.push(m.package);
|
|
2269
|
+
touched.set(m.path, entry);
|
|
2270
|
+
}
|
|
2271
|
+
let ok = true;
|
|
2272
|
+
for (const [dir, { consumer, oldKeys, newNames }] of touched) {
|
|
2273
|
+
console.log(colors.yellow(`↻ npm uninstall ${oldKeys.join(' ')} in ${consumer} (${dir}) — drops the link under the old name`));
|
|
2274
|
+
const uninstall = await runCommandAsync('npm', ['uninstall', ...oldKeys], { cwd: dir, silent: !verbose });
|
|
2275
|
+
if (!uninstall.success) {
|
|
2276
|
+
// Not fatal: the install below links the real name; a leftover link under the old
|
|
2277
|
+
// name is inert (nothing imports it) and is named so it can be removed by hand.
|
|
2278
|
+
console.log(colors.yellow(` npm uninstall failed in ${dir}; node_modules/${oldKeys.join(', node_modules/')} may remain — continuing with npm install`));
|
|
2279
|
+
recordBuildIssue(consumer, 'warning', `npm uninstall ${oldKeys.join(' ')} failed in ${dir}; the old link may remain in node_modules`);
|
|
2280
|
+
}
|
|
2281
|
+
console.log(colors.yellow(`↻ npm install in ${consumer} (${dir}) to link ${newNames.join(', ')} under the real name`));
|
|
2282
|
+
if (!await npmInstallWithCleanRetry(dir, verbose, `fixDepKeyMismatches: ${dir}`))
|
|
2283
|
+
ok = false;
|
|
2284
|
+
}
|
|
2285
|
+
console.log('');
|
|
2286
|
+
return ok;
|
|
2287
|
+
}
|
|
2200
2288
|
/** Report unscoped `file:` deps found by the prescan, without asking anything.
|
|
2201
2289
|
*
|
|
2202
2290
|
* An unscoped dep with private intent cannot reach npm, and npmglobalize does not
|
|
@@ -7617,7 +7705,15 @@ async function globalizeUnderLock(cwd, options = {}, configOptions = {}) {
|
|
|
7617
7705
|
console.error(colors.red('npm install failed while linking own-scope deps as file: (see above). Stopping; use -force to continue anyway.'));
|
|
7618
7706
|
return false;
|
|
7619
7707
|
}
|
|
7620
|
-
|
|
7708
|
+
// 2026-09-24 — Claude Code (Fable 5.1), at Bob's direction: file: deps declared under a
|
|
7709
|
+
// key that is not the target's name are renamed + reinstalled the same way (see
|
|
7710
|
+
// fixDepKeyMismatches); a failed install stops the run unless -force.
|
|
7711
|
+
const mismatchOk = await fixDepKeyMismatches(scanned.filter(i => i.kind === 'nameMismatch'), { dryRun, verbose });
|
|
7712
|
+
if (!mismatchOk && !force) {
|
|
7713
|
+
console.error(colors.red('npm install failed after renaming a dependency key to the package\'s own name (see above). Stopping; use -force to continue anyway.'));
|
|
7714
|
+
return false;
|
|
7715
|
+
}
|
|
7716
|
+
const allIssues = scanned.filter(i => i.kind !== 'registryRef' && i.kind !== 'nameMismatch');
|
|
7621
7717
|
// Unscoped deps are handled apart from the error/warning prompt below: nothing the
|
|
7622
7718
|
// user answers could change the outcome, so nothing is asked. Report, remember,
|
|
7623
7719
|
// stop. See reportUnscopedDeps.
|