@bobfrankston/npmglobalize 1.0.237 → 1.0.238
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 +1 -1
- package/lib.d.ts +18 -10
- package/lib.js +95 -27
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -94,7 +94,7 @@ Errors abort (unless `--force`); warnings prompt to continue.
|
|
|
94
94
|
|
|
95
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
|
-
**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
|
|
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
99
|
Skip the prescan with `-no-prescan` / `-nps`.
|
|
100
100
|
|
package/lib.d.ts
CHANGED
|
@@ -425,7 +425,7 @@ 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: —
|
|
428
|
+
* by version range instead of file: — rewritten to file: by fixRegistryRefs (2026-09-23), nothing asked. */
|
|
429
429
|
kind?: 'unscoped' | 'registryRef';
|
|
430
430
|
package: string;
|
|
431
431
|
/** For 'registryRef': the directory of the CONSUMER whose package.json holds the range. */
|
|
@@ -448,15 +448,23 @@ export declare function findUndeclaredImports(cwd: string, pkg: any): Undeclared
|
|
|
448
448
|
* user resolve all problems before starting the publish cascade instead of
|
|
449
449
|
* being interrupted mid-run. */
|
|
450
450
|
export declare function prescanDepGraph(baseDir: string, visited?: Set<string>, verbose?: boolean, fileTargets?: Map<string, string>): PrescanIssue[];
|
|
451
|
-
/**
|
|
452
|
-
*
|
|
453
|
-
*
|
|
454
|
-
*
|
|
455
|
-
*
|
|
456
|
-
*
|
|
457
|
-
*
|
|
458
|
-
*
|
|
459
|
-
|
|
451
|
+
/** Own-scope packages that some package in the file: graph takes from the registry (a version
|
|
452
|
+
* range) instead of from local source get REWRITTEN to a file: path, and npm install runs in
|
|
453
|
+
* that consumer so node_modules holds the link instead of the registry copy. The consumer is
|
|
454
|
+
* usually nested under other file: deps, where neither the `+` list nor the dependency tree
|
|
455
|
+
* shows it, which is why this runs in the prescan, before the cascade reads the consumer.
|
|
456
|
+
* A range whose local source cannot be found (not linked elsewhere in the graph, no sibling
|
|
457
|
+
* directory of that name) is reported as before and left alone. `"files": false` consumers
|
|
458
|
+
* were never collected (see prescanDepGraph). Every outcome is also recorded for the
|
|
459
|
+
* Issues Summary, since the prescan block has scrolled away by the end of a cascade.
|
|
460
|
+
* 2026-09-23 01:40 EDT — Claude Code (Fable 5.1), at Bob's direction: "you should also make
|
|
461
|
+
* sure that package.json for local packages such as httppoly uses file: rather than the npm
|
|
462
|
+
* version". Until now this only reported (v1.0.233), and certsupport kept
|
|
463
|
+
* `miscassists: ^1.0.61` through three runs. Returns false if an npm install failed. */
|
|
464
|
+
export declare function fixRegistryRefs(registryRefs: PrescanIssue[], fileTargets: Map<string, string>, opts?: {
|
|
465
|
+
dryRun?: boolean;
|
|
466
|
+
verbose?: boolean;
|
|
467
|
+
}): Promise<boolean>;
|
|
460
468
|
/** Report unscoped `file:` deps found by the prescan, without asking anything.
|
|
461
469
|
*
|
|
462
470
|
* An unscoped dep with private intent cannot reach npm, and npmglobalize does not
|
package/lib.js
CHANGED
|
@@ -2001,7 +2001,7 @@ export function findUndeclaredImports(cwd, pkg) {
|
|
|
2001
2001
|
* being interrupted mid-run. */
|
|
2002
2002
|
// 2026-09-21 — Claude Code (Fable 5.1), at Bob's direction ("you need to warn about @installs
|
|
2003
2003
|
// because they may be nested so I won't see them"). `fileTargets` added: every file: target the
|
|
2004
|
-
// walk resolves is recorded (package name → directory) so
|
|
2004
|
+
// walk resolves is recorded (package name → directory) so fixRegistryRefs can say where the
|
|
2005
2005
|
// local source of a registry-installed package is. Own-scope deps given as a version range are
|
|
2006
2006
|
// collected as kind 'registryRef'. Seen in certsupport: `@bobfrankston/miscassists: ^1.0.61`
|
|
2007
2007
|
// committed in its package.json, two levels under pserve, where no tree listing showed it.
|
|
@@ -2104,34 +2104,96 @@ export function prescanDepGraph(baseDir, visited = new Set(), verbose = false, f
|
|
|
2104
2104
|
}
|
|
2105
2105
|
return issues;
|
|
2106
2106
|
}
|
|
2107
|
-
/**
|
|
2108
|
-
* (
|
|
2109
|
-
*
|
|
2110
|
-
*
|
|
2111
|
-
*
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2107
|
+
/** Where the local source of an own-scope package is, or null. First the map prescanDepGraph
|
|
2108
|
+
* filled (the package is linked as file: elsewhere in the graph); then the sibling-directory
|
|
2109
|
+
* convention every file: ref in these trees follows (`file:../<name>`): a directory named
|
|
2110
|
+
* after the unscoped package name beside the consumer, accepted only when the package.json
|
|
2111
|
+
* there carries exactly that package name. Nothing else is searched. */
|
|
2112
|
+
function findLocalSource(consumerDir, packageName, fileTargets) {
|
|
2113
|
+
const known = fileTargets.get(packageName);
|
|
2114
|
+
if (known)
|
|
2115
|
+
return known;
|
|
2116
|
+
const unscopedName = packageName.includes('/') ? packageName.slice(packageName.indexOf('/') + 1) : packageName;
|
|
2117
|
+
const sibling = path.join(path.dirname(consumerDir), unscopedName);
|
|
2118
|
+
try {
|
|
2119
|
+
if (readPackageJson(sibling).name === packageName)
|
|
2120
|
+
return sibling;
|
|
2121
|
+
}
|
|
2122
|
+
catch (error) {
|
|
2123
|
+
// Justified swallow: no package.json beside the consumer under that name is the
|
|
2124
|
+
// ordinary "not a sibling" outcome. The caller reports "local source not found" on
|
|
2125
|
+
// null, so the information becomes that message rather than being lost.
|
|
2126
|
+
}
|
|
2127
|
+
return null;
|
|
2128
|
+
}
|
|
2129
|
+
/** Own-scope packages that some package in the file: graph takes from the registry (a version
|
|
2130
|
+
* range) instead of from local source get REWRITTEN to a file: path, and npm install runs in
|
|
2131
|
+
* that consumer so node_modules holds the link instead of the registry copy. The consumer is
|
|
2132
|
+
* usually nested under other file: deps, where neither the `+` list nor the dependency tree
|
|
2133
|
+
* shows it, which is why this runs in the prescan, before the cascade reads the consumer.
|
|
2134
|
+
* A range whose local source cannot be found (not linked elsewhere in the graph, no sibling
|
|
2135
|
+
* directory of that name) is reported as before and left alone. `"files": false` consumers
|
|
2136
|
+
* were never collected (see prescanDepGraph). Every outcome is also recorded for the
|
|
2137
|
+
* Issues Summary, since the prescan block has scrolled away by the end of a cascade.
|
|
2138
|
+
* 2026-09-23 01:40 EDT — Claude Code (Fable 5.1), at Bob's direction: "you should also make
|
|
2139
|
+
* sure that package.json for local packages such as httppoly uses file: rather than the npm
|
|
2140
|
+
* version". Until now this only reported (v1.0.233), and certsupport kept
|
|
2141
|
+
* `miscassists: ^1.0.61` through three runs. Returns false if an npm install failed. */
|
|
2142
|
+
export async function fixRegistryRefs(registryRefs, fileTargets, opts = {}) {
|
|
2116
2143
|
if (!registryRefs.length)
|
|
2117
|
-
return;
|
|
2144
|
+
return true;
|
|
2145
|
+
const { dryRun = false, verbose = false } = opts;
|
|
2118
2146
|
console.log(colors.yellow(`${registryRefs.length} own-scope dependenc${registryRefs.length === 1 ? 'y is' : 'ies are'} installed from npm, not linked with file: (a registry copy in node_modules, not the local source):`));
|
|
2147
|
+
// One npm install per consumer, after all of its ranges are rewritten.
|
|
2148
|
+
const rewrittenConsumers = new Map(); // consumer dir → consumer name
|
|
2149
|
+
let ok = true;
|
|
2119
2150
|
for (const ref of registryRefs) {
|
|
2151
|
+
const pkgJsonPath = path.join(ref.path, 'package.json');
|
|
2120
2152
|
console.log(` ${colors.yellow('@')} ${ref.consumer} → ${ref.package} ${ref.range}`);
|
|
2121
|
-
console.log(colors.dim(` in ${
|
|
2122
|
-
const localSource =
|
|
2123
|
-
if (localSource) {
|
|
2124
|
-
console.log(colors.dim(` local source
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2153
|
+
console.log(colors.dim(` in ${pkgJsonPath}`));
|
|
2154
|
+
const localSource = findLocalSource(ref.path, ref.package, fileTargets);
|
|
2155
|
+
if (!localSource) {
|
|
2156
|
+
console.log(colors.dim(` local source not found (not linked as file: elsewhere in this graph, no sibling directory of that name) — left as is`));
|
|
2157
|
+
recordBuildIssue(ref.consumer, 'warning', `${ref.package} ${ref.range} is installed from npm, not file: — ${pkgJsonPath} (local source not found, left as is)`);
|
|
2158
|
+
continue;
|
|
2159
|
+
}
|
|
2160
|
+
let relative = path.relative(ref.path, localSource).split(path.sep).join('/');
|
|
2161
|
+
if (!relative.startsWith('.'))
|
|
2162
|
+
relative = './' + relative;
|
|
2163
|
+
const fileSpec = `file:${relative}`;
|
|
2164
|
+
if (dryRun) {
|
|
2165
|
+
console.log(colors.dim(` [dry-run] would set to ${fileSpec} (${localSource}) and run npm install in ${ref.consumer}`));
|
|
2166
|
+
continue;
|
|
2167
|
+
}
|
|
2168
|
+
const pkg = readPackageJson(ref.path);
|
|
2169
|
+
let rewrote = false;
|
|
2170
|
+
for (const key of DEP_KEYS) {
|
|
2171
|
+
// A `.dependencies` backup (left by an interrupted transform) is the file: truth;
|
|
2172
|
+
// prescanDepGraph read the range from it, so the rewrite goes there too.
|
|
2173
|
+
for (const depsKey of ['.' + key, key]) {
|
|
2174
|
+
const deps = pkg[depsKey];
|
|
2175
|
+
if (deps && deps[ref.package] === ref.range) {
|
|
2176
|
+
deps[ref.package] = fileSpec;
|
|
2177
|
+
rewrote = true;
|
|
2178
|
+
}
|
|
2179
|
+
}
|
|
2180
|
+
}
|
|
2181
|
+
if (!rewrote) {
|
|
2182
|
+
console.log(colors.dim(` range not found in ${pkgJsonPath} any more — left as is`));
|
|
2183
|
+
continue;
|
|
2184
|
+
}
|
|
2185
|
+
writePackageJson(ref.path, pkg);
|
|
2186
|
+
console.log(colors.green(` ✓ set to ${fileSpec} (${localSource})`));
|
|
2187
|
+
recordBuildIssue(ref.consumer, 'warning', `${ref.package} ${ref.range} rewritten to ${fileSpec} in ${pkgJsonPath}`);
|
|
2188
|
+
rewrittenConsumers.set(ref.path, ref.consumer);
|
|
2189
|
+
}
|
|
2190
|
+
for (const [consumerDir, consumerName] of rewrittenConsumers) {
|
|
2191
|
+
console.log(colors.yellow(`↻ npm install in ${consumerName} (${consumerDir}) to replace the registry copy with the file: link`));
|
|
2192
|
+
if (!await npmInstallWithCleanRetry(consumerDir, verbose, `fixRegistryRefs: ${consumerDir}`))
|
|
2193
|
+
ok = false;
|
|
2194
|
+
}
|
|
2134
2195
|
console.log('');
|
|
2196
|
+
return ok;
|
|
2135
2197
|
}
|
|
2136
2198
|
/** Report unscoped `file:` deps found by the prescan, without asking anything.
|
|
2137
2199
|
*
|
|
@@ -7366,9 +7428,15 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
|
|
|
7366
7428
|
const fileTargets = new Map();
|
|
7367
7429
|
const scanned = prescanDepGraph(cwd, new Set(), verbose, fileTargets);
|
|
7368
7430
|
// 2026-09-21 — Claude Code (Fable 5.1), at Bob's direction: registry refs to own-scope
|
|
7369
|
-
// packages are
|
|
7370
|
-
//
|
|
7371
|
-
|
|
7431
|
+
// packages are handled apart from the prompt below, like unscoped deps — no question.
|
|
7432
|
+
// 2026-09-23 — Claude Code (Fable 5.1), at Bob's direction: they are now FIXED (rewritten
|
|
7433
|
+
// to file: and npm-installed), not just reported. A failed install stops the run like
|
|
7434
|
+
// ensureFileDepModules does, unless -force.
|
|
7435
|
+
const refsOk = await fixRegistryRefs(scanned.filter(i => i.kind === 'registryRef'), fileTargets, { dryRun, verbose });
|
|
7436
|
+
if (!refsOk && !force) {
|
|
7437
|
+
console.error(colors.red('npm install failed while linking own-scope deps as file: (see above). Stopping; use -force to continue anyway.'));
|
|
7438
|
+
return false;
|
|
7439
|
+
}
|
|
7372
7440
|
const allIssues = scanned.filter(i => i.kind !== 'registryRef');
|
|
7373
7441
|
// Unscoped deps are handled apart from the error/warning prompt below: nothing the
|
|
7374
7442
|
// user answers could change the outcome, so nothing is asked. Report, remember,
|