@bobfrankston/npmglobalize 1.0.228 → 1.0.230
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 +10 -3
- package/lib.d.ts +5 -0
- package/lib.js +68 -99
- 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
|
|
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
|
|
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
|
|
@@ -586,6 +586,13 @@ Workspace mode is auto-detected when run from a root with `"private": true` and
|
|
|
586
586
|
migration automatically when a build hits a TS7 deprecation
|
|
587
587
|
error, so day-to-day you never need this flag.
|
|
588
588
|
npmglobalize <path> -tsfix
|
|
589
|
+
If the automatic migration breaks a build (usually relative
|
|
590
|
+
imports missing their .js extension, TS2835), the tsconfig is
|
|
591
|
+
put back so the build keeps working and EVERY error from the
|
|
592
|
+
nodenext attempt is listed, file(line,col) and the suggested
|
|
593
|
+
fix, for fixing by hand. Nothing in the source is rewritten.
|
|
594
|
+
The migration reruns on the next build once the imports are
|
|
595
|
+
fixed. (2026-09-14)
|
|
589
596
|
-show Show package.json dependency changes
|
|
590
597
|
-package, -pkg Update package.json scripts to use npmglobalize (see below)
|
|
591
598
|
-h, -help Show help
|
|
@@ -793,7 +800,7 @@ Before transforming or publishing anything, `npmglobalize` builds `file:` depend
|
|
|
793
800
|
For each project visited (the target and every transitive `file:` dep):
|
|
794
801
|
|
|
795
802
|
- 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 → **
|
|
803
|
+
- 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
804
|
- Otherwise → run `npm run build`. A failure halts the cascade unless `-force` is passed.
|
|
798
805
|
|
|
799
806
|
Cycle-safe via a shared visited set; each project is built at most once per run.
|
package/lib.d.ts
CHANGED
|
@@ -25,6 +25,11 @@ export declare function clearBuildIssues(): void;
|
|
|
25
25
|
/** Extract the first TypeScript error line from build output for the summary.
|
|
26
26
|
* Returns a short string like "file.ts(42,5): error TS2339: Property 'foo' ..." */
|
|
27
27
|
export declare function extractFirstTscError(output: string): string | null;
|
|
28
|
+
/** Every TypeScript error line in build output — `file.ts(line,col): error TSnnnn: message`
|
|
29
|
+
* — in order, deduplicated, untruncated. For reports where the reader will act on each
|
|
30
|
+
* line (the imports a nodenext migration surfaces), as opposed to the one-line summary
|
|
31
|
+
* extractFirstTscError gives. (2026-09-14) */
|
|
32
|
+
export declare function extractTscErrors(output: string): string[];
|
|
28
33
|
/** A TS7016 — "Could not find a declaration file for module 'X'" — that tsc blames
|
|
29
34
|
* on the file being compiled is frequently not that file's fault: the copy of X in
|
|
30
35
|
* node_modules carries no `.d.ts` whatsoever. That happens when X was published at
|
package/lib.js
CHANGED
|
@@ -78,6 +78,24 @@ export function extractFirstTscError(output) {
|
|
|
78
78
|
}
|
|
79
79
|
return null;
|
|
80
80
|
}
|
|
81
|
+
/** Every TypeScript error line in build output — `file.ts(line,col): error TSnnnn: message`
|
|
82
|
+
* — in order, deduplicated, untruncated. For reports where the reader will act on each
|
|
83
|
+
* line (the imports a nodenext migration surfaces), as opposed to the one-line summary
|
|
84
|
+
* extractFirstTscError gives. (2026-09-14) */
|
|
85
|
+
export function extractTscErrors(output) {
|
|
86
|
+
if (!output)
|
|
87
|
+
return [];
|
|
88
|
+
const seen = new Set();
|
|
89
|
+
const lines = [];
|
|
90
|
+
for (const m of output.matchAll(/^(.+?\(\d+,\d+\): error TS\d+: .+)$/gm)) {
|
|
91
|
+
const line = m[1].trimEnd();
|
|
92
|
+
if (seen.has(line))
|
|
93
|
+
continue;
|
|
94
|
+
seen.add(line);
|
|
95
|
+
lines.push(line);
|
|
96
|
+
}
|
|
97
|
+
return lines;
|
|
98
|
+
}
|
|
81
99
|
/** Does `dir` contain any `.d.ts` at all? Bounded, and never descends into a
|
|
82
100
|
* nested `node_modules` — we are asking about this package's own output. */
|
|
83
101
|
function hasDeclarationFiles(dir, depth = 3) {
|
|
@@ -3604,12 +3622,17 @@ export async function ensureBuildScript(cwd) {
|
|
|
3604
3622
|
const subs = await readySubProjects(cwd, readConfig(cwd));
|
|
3605
3623
|
const tscParts = ['tsc', ...subs.map(subProjectBuildCommand)];
|
|
3606
3624
|
const newScript = [...(existingBuild ? [existingBuild] : []), ...tscParts].join(' && ');
|
|
3625
|
+
// No question here: a TypeScript project that emits needs a build script, and
|
|
3626
|
+
// "tsc" is the only sensible one, so the answer was always yes. The prompt cost
|
|
3627
|
+
// a keystroke per package for no decision. The importgen and sub-project prompts
|
|
3628
|
+
// above stay, since those rewrite a build script that already works.
|
|
3629
|
+
// 2026-09-14 18:05 EDT — Claude Code (Fable 5.1), at Bob's direction ("is there
|
|
3630
|
+
// a command option to avoid having to answer questions like whether to update
|
|
3631
|
+
// tsconfig?" — and "-yes is too general", so the question is gone instead).
|
|
3607
3632
|
console.log(colors.yellow(`TypeScript project has no "build" script in ${pkg.name || cwd}`));
|
|
3608
3633
|
for (const s of subs)
|
|
3609
3634
|
console.log(colors.yellow(` sub-project ${s.dir}/ — ${s.reason}`));
|
|
3610
|
-
|
|
3611
|
-
if (!addIt)
|
|
3612
|
-
return null;
|
|
3635
|
+
console.log(colors.cyan(` Adding "build": "${newScript}" to ${pkg.name || path.basename(cwd)}'s package.json`));
|
|
3613
3636
|
if (!pkg.scripts)
|
|
3614
3637
|
pkg.scripts = {};
|
|
3615
3638
|
pkg.scripts.build = newScript;
|
|
@@ -3748,13 +3771,23 @@ export async function buildProject(cwd, opts = {}) {
|
|
|
3748
3771
|
const retry = await runCommandAsync('npm', ['run', 'build'], { cwd, silent: true });
|
|
3749
3772
|
if (retry.success) {
|
|
3750
3773
|
dropBuildIssuesFrom(issueMark); // the migration was undone; its warnings no longer apply
|
|
3751
|
-
const firstErr = extractFirstTscError(migratedOutput);
|
|
3752
3774
|
const label = pkg.name || path.basename(cwd);
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3775
|
+
// List EVERY error the migrated build produced, not just the first: these
|
|
3776
|
+
// are the imports (usually relative specifiers missing their .js extension)
|
|
3777
|
+
// that have to be fixed by hand before nodenext can stay. The tsconfig is
|
|
3778
|
+
// left on the deprecated settings so the build keeps working meanwhile;
|
|
3779
|
+
// the next build after the imports are fixed migrates again on its own.
|
|
3780
|
+
// 2026-09-14 18:30 EDT — Claude Code (Fable 5.1), at Bob's direction:
|
|
3781
|
+
// "Rather than doing the rewrite of the imports, flag them so I can
|
|
3782
|
+
// manually fix them since I expect most cases have already been fixed."
|
|
3783
|
+
const errs = extractTscErrors(migratedOutput);
|
|
3784
|
+
console.log(colors.yellow(` Reverted the tsconfig TS7 migration in ${label} — under "nodenext" the build fails on ${errs.length || 'these'} error(s):`));
|
|
3785
|
+
for (const e of errs)
|
|
3786
|
+
console.log(colors.yellow(` ${e}`));
|
|
3787
|
+
if (!errs.length)
|
|
3788
|
+
console.log(colors.yellow(` ${extractFirstTscError(migratedOutput) || '(no tsc error line found — rerun with -verbose)'}`));
|
|
3789
|
+
console.log(colors.yellow(` Deprecated settings left in place so the build still works. Fix the imports above; the migration reruns on the next build.`));
|
|
3790
|
+
recordBuildIssue(label, 'warning', `tsconfig still uses settings TypeScript 7 removes: under "nodenext" the build fails on ${errs.length} error(s) (first: ${errs[0] || extractFirstTscError(migratedOutput) || 'see build output'}). Fix the listed imports; the migration reruns on the next build.`);
|
|
3758
3791
|
buildResult = retry;
|
|
3759
3792
|
}
|
|
3760
3793
|
else {
|
|
@@ -7528,60 +7561,33 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
|
|
|
7528
7561
|
publicConfirmed = true;
|
|
7529
7562
|
}
|
|
7530
7563
|
}
|
|
7531
|
-
//
|
|
7532
|
-
//
|
|
7533
|
-
//
|
|
7534
|
-
//
|
|
7535
|
-
//
|
|
7536
|
-
//
|
|
7537
|
-
//
|
|
7538
|
-
//
|
|
7539
|
-
|
|
7540
|
-
|
|
7541
|
-
|
|
7542
|
-
|
|
7543
|
-
|
|
7564
|
+
// An unscoped name cannot be published privately, and npmglobalize does not rename a
|
|
7565
|
+
// package to fix that: the name is what every consumer imports by, so adding a scope
|
|
7566
|
+
// is the package owner's own project, not a publish-time side effect. This used to
|
|
7567
|
+
// ask "Add scope to package name?"; now it warns and assumes no, for a dependency in
|
|
7568
|
+
// the cascade and for the package itself alike. Publishing unscoped as PUBLIC takes an
|
|
7569
|
+
// explicit -npm public, or noprefix: true in .globalize.json5 (the escape hatch the
|
|
7570
|
+
// prescan honors too).
|
|
7571
|
+
// 2026-09-14 18:10 EDT — Claude Code (Fable 5.1), at Bob's direction: "It shouldn't try
|
|
7572
|
+
// to publish to a scope not in the package" and, on the prompt in the package itself,
|
|
7573
|
+
// "For libraries that is problematic since other programs are affected so just warn
|
|
7574
|
+
// and assume no."
|
|
7575
|
+
const unscopedAllowed = readConfig(cwd).noprefix === true;
|
|
7576
|
+
if (!isScoped && effectiveNpmVisibility !== 'public' && currentAccess !== 'public' && !unscopedAllowed) {
|
|
7577
|
+
const scopeHint = readUserNpmConfig().scope || '@scope';
|
|
7578
|
+
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}).`));
|
|
7579
|
+
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
7580
|
return false;
|
|
7545
7581
|
}
|
|
7546
7582
|
if (effectiveNpmVisibility === 'private') {
|
|
7547
7583
|
// User explicitly wants private publication
|
|
7548
7584
|
if (!isScoped) {
|
|
7549
|
-
//
|
|
7550
|
-
|
|
7551
|
-
|
|
7552
|
-
|
|
7553
|
-
|
|
7554
|
-
|
|
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
|
-
}
|
|
7585
|
+
// Only reachable with noprefix: true (the unscoped guard above returned
|
|
7586
|
+
// otherwise). noprefix says "publish unscoped", private says "restricted",
|
|
7587
|
+
// and npm cannot do both. The rename prompt that used to live here is gone
|
|
7588
|
+
// (2026-09-14, see the guard above).
|
|
7589
|
+
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.`));
|
|
7590
|
+
return false;
|
|
7585
7591
|
}
|
|
7586
7592
|
if (currentAccess === 'public') {
|
|
7587
7593
|
console.log(colors.yellow(`Package '${pkg.name}' is currently PUBLIC on npm. Converting to private...`));
|
|
@@ -7653,47 +7659,10 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
|
|
|
7653
7659
|
console.log(colors.dim(` Use --npm public to make it public`));
|
|
7654
7660
|
}
|
|
7655
7661
|
else {
|
|
7656
|
-
// Unscoped new package
|
|
7657
|
-
|
|
7658
|
-
|
|
7659
|
-
|
|
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
|
-
}
|
|
7662
|
+
// Unscoped new package. Only reachable with noprefix: true (the unscoped
|
|
7663
|
+
// guard above returned otherwise), so publishing it PUBLIC is the stated
|
|
7664
|
+
// intent. The rename prompt that used to live here is gone (2026-09-14).
|
|
7665
|
+
console.log(colors.yellow(`Package '${pkg.name}' is unscoped (noprefix) and will publish as PUBLIC — npm cannot restrict an unscoped package.`));
|
|
7697
7666
|
}
|
|
7698
7667
|
}
|
|
7699
7668
|
}
|