@bobfrankston/npmglobalize 1.0.227 → 1.0.228
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 +13 -1
- package/lib.d.ts +36 -1
- package/lib.js +188 -62
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -92,6 +92,8 @@ 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 rename offer still exists when you run npmglobalize *in* the unscoped package itself. (2026-09-14)
|
|
96
|
+
|
|
95
97
|
Skip the prescan with `-no-prescan` / `-nps`.
|
|
96
98
|
|
|
97
99
|
#### Workspace skip prescan
|
|
@@ -369,6 +371,11 @@ How would you like to set up git?
|
|
|
369
371
|
5) Abort
|
|
370
372
|
```
|
|
371
373
|
|
|
374
|
+
The "Use local install only" line appears only for a package with a `bin` field.
|
|
375
|
+
A library has nothing for a global install to expose, so it is not offered the
|
|
376
|
+
option and the remaining lines are numbered without a gap. The same applies to
|
|
377
|
+
the "No git repository found" and "No git remote configured" menus.
|
|
378
|
+
|
|
372
379
|
**Adopt** runs:
|
|
373
380
|
|
|
374
381
|
```bash
|
|
@@ -470,7 +477,8 @@ not what you meant to publish, so npmglobalize lists the conflicted files and st
|
|
|
470
477
|
-update-deps, -ud Update package.json to latest versions (safe: minor/patch)
|
|
471
478
|
-update-major Allow major version updates (breaking changes)
|
|
472
479
|
-publish-deps Auto-publish file: dependencies (default)
|
|
473
|
-
-pd Like -publish-deps, plus auto-yes to dep-cascade prompts (private only
|
|
480
|
+
-pd Like -publish-deps, plus auto-yes to dep-cascade prompts (private only;
|
|
481
|
+
never adds a scope to a dep)
|
|
474
482
|
-no-publish-deps, -npd Skip auto-publishing file: dependencies
|
|
475
483
|
-no-prescan, -nps Skip upfront dep-graph prescan
|
|
476
484
|
-force-publish Republish dependencies even if version exists
|
|
@@ -914,6 +922,10 @@ consumed: it is appended to `npmchanges.md` under a
|
|
|
914
922
|
lost. Several local entries may share one version number — the date tells them
|
|
915
923
|
apart. No git commit is made for it; the next real publish carries it along.
|
|
916
924
|
|
|
925
|
+
On a library (no `bin`) there is nothing to install, and the output says so: `Local mode:
|
|
926
|
+
<pkg> — library, nothing to install`. The `file:` deps are still built and the `.commitmsg`
|
|
927
|
+
is still consumed; only the install step does not exist. (2026-09-14)
|
|
928
|
+
|
|
917
929
|
### Private Packages
|
|
918
930
|
|
|
919
931
|
Packages with `"private": true` in `package.json` skip the npm publish step. Dependencies are still transformed and restored — the publish is the only thing skipped.
|
package/lib.d.ts
CHANGED
|
@@ -91,7 +91,8 @@ export interface GlobalizeOptions {
|
|
|
91
91
|
updateMajor?: boolean;
|
|
92
92
|
/** Publish file: dependencies before converting them */
|
|
93
93
|
publishDeps?: boolean;
|
|
94
|
-
/** Auto-yes to dep-cascade prompts (
|
|
94
|
+
/** Auto-yes to dep-cascade prompts (git setup, prescan warnings); does NOT auto-yes public
|
|
95
|
+
* prompts. Cannot add a scope to a dep: the cascade never renames a dependency (2026-09-14). */
|
|
95
96
|
publishDepsYes?: boolean;
|
|
96
97
|
/** Cascade npmVisibility:"public" to all transitive workspace/file: deps without prompting.
|
|
97
98
|
* In workspace mode: fixpoint-promotes every workspace member reachable from a public consumer.
|
|
@@ -160,6 +161,12 @@ export interface GlobalizeOptions {
|
|
|
160
161
|
* following each entry's own .globalize.json5. FYI for now: recorded and
|
|
161
162
|
* displayed, nothing is updated automatically. */
|
|
162
163
|
upstream?: UpstreamEntry[];
|
|
164
|
+
/** Bookkeeping, not a CLI option: unscoped `file:` deps this package was already told
|
|
165
|
+
* about (dep name → date first noted). The first run explains why the dep, and this
|
|
166
|
+
* package with it, will not reach npm until the dep's own package.json has a scope;
|
|
167
|
+
* later runs print one line. Nothing is asked either time, since no answer could
|
|
168
|
+
* change the outcome. Cleared automatically once the dep is scoped. (2026-09-14) */
|
|
169
|
+
unscopedDepsNoted?: Record<string, string>;
|
|
163
170
|
/** Internal: signals this call is from workspace orchestrator */
|
|
164
171
|
/** Skip the upfront dep-graph prescan */
|
|
165
172
|
noPrescan?: boolean;
|
|
@@ -344,6 +351,9 @@ export declare function transformDeps(pkg: any, baseDir: string, verbose?: boole
|
|
|
344
351
|
/** A problem discovered by the prescan. */
|
|
345
352
|
export interface PrescanIssue {
|
|
346
353
|
severity: 'error' | 'warning';
|
|
354
|
+
/** 'unscoped': an unscoped file: dep with private intent. Not an error and not a
|
|
355
|
+
* question — globalize reports it once, remembers it, and stops. */
|
|
356
|
+
kind?: 'unscoped';
|
|
347
357
|
package: string;
|
|
348
358
|
path: string;
|
|
349
359
|
message: string;
|
|
@@ -360,6 +370,17 @@ export declare function findUndeclaredImports(cwd: string, pkg: any): Undeclared
|
|
|
360
370
|
* user resolve all problems before starting the publish cascade instead of
|
|
361
371
|
* being interrupted mid-run. */
|
|
362
372
|
export declare function prescanDepGraph(baseDir: string, visited?: Set<string>, verbose?: boolean): PrescanIssue[];
|
|
373
|
+
/** Report unscoped `file:` deps found by the prescan, without asking anything.
|
|
374
|
+
*
|
|
375
|
+
* An unscoped dep with private intent cannot reach npm, and npmglobalize does not
|
|
376
|
+
* rename a dependency to fix that (the name is what every consumer imports by; scoping
|
|
377
|
+
* it is the dep owner's own project). So the consumer in `cwd` cannot be published
|
|
378
|
+
* either. Nothing the user could answer changes that, so there is no prompt: the first
|
|
379
|
+
* run explains it in full, records the dep in `unscopedDepsNoted` in the consumer's
|
|
380
|
+
* .globalize.json5, and later runs print one line per dep. Deps that have since been
|
|
381
|
+
* scoped drop out of the record automatically.
|
|
382
|
+
* 2026-09-14 17:05 EDT — Claude Code (Fable 5.1), at Bob's direction. */
|
|
383
|
+
export declare function reportUnscopedDeps(cwd: string, unscoped: PrescanIssue[]): void;
|
|
363
384
|
/** Restore file: dependencies from .dependencies using a three-way merge that
|
|
364
385
|
* preserves any external modifications made since the last transform. */
|
|
365
386
|
export declare function restoreDeps(pkg: any, verbose?: boolean): boolean;
|
|
@@ -677,6 +698,20 @@ export declare function promptText(message: string, defaultValue?: string): Prom
|
|
|
677
698
|
* answer resolved null and every caller fell through to its default action, so a typo
|
|
678
699
|
* silently did something. Now it says what it didn't understand and asks again.
|
|
679
700
|
*/
|
|
701
|
+
/** One line of a numbered console menu. `action` is what the caller switches on;
|
|
702
|
+
* the number the user types is assigned at display time, so lines can be
|
|
703
|
+
* omitted without leaving gaps or shifting meanings. */
|
|
704
|
+
export interface MenuOption {
|
|
705
|
+
label: string;
|
|
706
|
+
action: string;
|
|
707
|
+
}
|
|
708
|
+
/** Show `header` and the options numbered 1..N in display order, then read a
|
|
709
|
+
* choice. Empty input takes the first option. Returns the chosen `action`.
|
|
710
|
+
* 2026-09-14 14:30 EDT — Claude Code (Fable 5.1), at Bob's direction. The git
|
|
711
|
+
* setup menus were hand-numbered strings with a fixed "Use local install only"
|
|
712
|
+
* line, so a library (no `bin`) was offered an install that doLocalInstall
|
|
713
|
+
* would then skip. Options are now data, so a line can be left out. */
|
|
714
|
+
export declare function promptMenu(header: string, options: MenuOption[]): Promise<string>;
|
|
680
715
|
export declare function promptChoice(message: string, choices: string[]): Promise<string>;
|
|
681
716
|
/** Initialize a local git repo by adopting history from an existing remote.
|
|
682
717
|
* Preserves the working tree — local files appear as uncommitted changes
|
package/lib.js
CHANGED
|
@@ -403,6 +403,9 @@ export function writeConfig(dir, config, explicitKeys) {
|
|
|
403
403
|
if (config.upstream === undefined && existing.upstream !== undefined) {
|
|
404
404
|
config = { ...config, upstream: existing.upstream };
|
|
405
405
|
}
|
|
406
|
+
// unscopedDepsNoted is deliberately NOT carried across: reportUnscopedDeps clears it
|
|
407
|
+
// by omitting the key, and a publish-path writeConfig that drops it only means the
|
|
408
|
+
// full explanation prints once more. 2026-09-14
|
|
406
409
|
const omitKeys = new Set(['cleanup', 'init', 'dryRun', 'message', 'conform', 'asis', 'help', 'error', 'updateDeps', 'updateMajor', 'publishDeps', 'publicDeps', 'forcePublish', 'once', 'cleanNestedModules']);
|
|
407
410
|
for (const [key, value] of Object.entries(config)) {
|
|
408
411
|
if (omitKeys.has(key))
|
|
@@ -480,7 +483,8 @@ export function writeConfig(dir, config, explicitKeys) {
|
|
|
480
483
|
['"usePaths": true', 'true = resolve file: deps from siblings; false = use latest npm version (standalone)'],
|
|
481
484
|
['"allowTs": false', 'true = include .ts source in npm tarball (auto-true for noEmit projects)'],
|
|
482
485
|
['"importgen": auto', 'Detected from .vscode/tasks.json / import map in HTML; false = never ask'],
|
|
483
|
-
['"subProjects": auto', 'Sub-dirs with their own build script or tsconfig (e.g. ["client", "Sw"]); detected from .vscode/tasks.json; false = never ask']
|
|
486
|
+
['"subProjects": auto', 'Sub-dirs with their own build script or tsconfig (e.g. ["client", "Sw"]); detected from .vscode/tasks.json; false = never ask'],
|
|
487
|
+
['"unscopedDepsNoted": {}', 'Bookkeeping: unscoped file: deps already explained (name → date); cleared when the dep gets a scope']
|
|
484
488
|
];
|
|
485
489
|
const refWidth = Math.max(...reference.map(([decl]) => decl.length));
|
|
486
490
|
for (const [decl, note] of reference) {
|
|
@@ -1824,8 +1828,16 @@ export function prescanDepGraph(baseDir, visited = new Set(), verbose = false) {
|
|
|
1824
1828
|
continue;
|
|
1825
1829
|
}
|
|
1826
1830
|
const depName = targetPkg.name || name;
|
|
1827
|
-
// Unscoped + private-intent
|
|
1828
|
-
|
|
1831
|
+
// Unscoped + private-intent. This used to be a WARNING whose "continue" led
|
|
1832
|
+
// into the cascade prompting to rename the dep by adding a scope. The cascade
|
|
1833
|
+
// never renames a dependency now (see the _fromDep check in globalize), so it
|
|
1834
|
+
// is flagged `kind: 'unscoped'` and globalize reports it without asking anything.
|
|
1835
|
+
// Reported once per dep, not once per consumer edge: the recursion below
|
|
1836
|
+
// dedupes via `visited`, but this check ran before it, so a dep shared by four
|
|
1837
|
+
// packages printed four identical lines.
|
|
1838
|
+
// 2026-09-14 16:40 EDT — Claude Code (Fable 5.1), at Bob's direction: "It
|
|
1839
|
+
// shouldn't try to publish to a scope not in the package."
|
|
1840
|
+
if (!depName.startsWith('@') && !visited.has(fs.realpathSync(targetPath))) {
|
|
1829
1841
|
const depConfig = readConfig(targetPath);
|
|
1830
1842
|
const depVis = depConfig.npmVisibility
|
|
1831
1843
|
?? (targetPkg.publishConfig?.access === 'public' ? 'public' : undefined);
|
|
@@ -1833,10 +1845,11 @@ export function prescanDepGraph(baseDir, visited = new Set(), verbose = false) {
|
|
|
1833
1845
|
if (depVis !== 'public' && !noprefix) {
|
|
1834
1846
|
issues.push({
|
|
1835
1847
|
severity: 'warning',
|
|
1848
|
+
kind: 'unscoped',
|
|
1836
1849
|
package: depName,
|
|
1837
1850
|
path: targetPath,
|
|
1838
|
-
message: 'unscoped
|
|
1839
|
-
suggestion: '
|
|
1851
|
+
message: 'unscoped — not published to npm until its own package.json name has a scope',
|
|
1852
|
+
suggestion: 'scope it in ITS OWN package.json (e.g., @bobfrankston/), or set npmVisibility="public" or noprefix=true in its .globalize.json5'
|
|
1840
1853
|
});
|
|
1841
1854
|
}
|
|
1842
1855
|
}
|
|
@@ -1846,6 +1859,60 @@ export function prescanDepGraph(baseDir, visited = new Set(), verbose = false) {
|
|
|
1846
1859
|
}
|
|
1847
1860
|
return issues;
|
|
1848
1861
|
}
|
|
1862
|
+
/** Report unscoped `file:` deps found by the prescan, without asking anything.
|
|
1863
|
+
*
|
|
1864
|
+
* An unscoped dep with private intent cannot reach npm, and npmglobalize does not
|
|
1865
|
+
* rename a dependency to fix that (the name is what every consumer imports by; scoping
|
|
1866
|
+
* it is the dep owner's own project). So the consumer in `cwd` cannot be published
|
|
1867
|
+
* either. Nothing the user could answer changes that, so there is no prompt: the first
|
|
1868
|
+
* run explains it in full, records the dep in `unscopedDepsNoted` in the consumer's
|
|
1869
|
+
* .globalize.json5, and later runs print one line per dep. Deps that have since been
|
|
1870
|
+
* scoped drop out of the record automatically.
|
|
1871
|
+
* 2026-09-14 17:05 EDT — Claude Code (Fable 5.1), at Bob's direction. */
|
|
1872
|
+
export function reportUnscopedDeps(cwd, unscoped) {
|
|
1873
|
+
const config = readConfig(cwd);
|
|
1874
|
+
const noted = { ...(config.unscopedDepsNoted ?? {}) };
|
|
1875
|
+
if (unscoped.length === 0 && Object.keys(noted).length === 0)
|
|
1876
|
+
return; // nothing to say, nothing to forget
|
|
1877
|
+
let consumerName;
|
|
1878
|
+
try {
|
|
1879
|
+
consumerName = readPackageJson(cwd).name || path.basename(cwd);
|
|
1880
|
+
}
|
|
1881
|
+
catch {
|
|
1882
|
+
consumerName = path.basename(cwd);
|
|
1883
|
+
}
|
|
1884
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
1885
|
+
let changed = false;
|
|
1886
|
+
for (const issue of unscoped) {
|
|
1887
|
+
const when = noted[issue.package];
|
|
1888
|
+
if (when) {
|
|
1889
|
+
console.log(colors.yellow(`${issue.package} still unscoped (noted ${when}) — ${consumerName} is not published to npm until ${issue.package} has a scope.`));
|
|
1890
|
+
continue;
|
|
1891
|
+
}
|
|
1892
|
+
console.log(colors.yellow(`${issue.package} is unscoped, so it will not be published to npm until the name in its package.json has a scope.`));
|
|
1893
|
+
console.log(colors.yellow(`${consumerName} depends on ${issue.package} and will not be published to npm either.`));
|
|
1894
|
+
console.log(colors.dim(` ${issue.package} is at ${issue.path}`));
|
|
1895
|
+
console.log(colors.dim(` → ${issue.suggestion}`));
|
|
1896
|
+
console.log(colors.dim(` Noted in ${path.join(cwd, '.globalize.json5')}; later runs print one line for ${issue.package}.`));
|
|
1897
|
+
noted[issue.package] = today;
|
|
1898
|
+
changed = true;
|
|
1899
|
+
}
|
|
1900
|
+
// Forget deps that are no longer reported: they were scoped (or made public) since.
|
|
1901
|
+
const stillUnscoped = new Set(unscoped.map(i => i.package));
|
|
1902
|
+
for (const name of Object.keys(noted)) {
|
|
1903
|
+
if (stillUnscoped.has(name))
|
|
1904
|
+
continue;
|
|
1905
|
+
delete noted[name];
|
|
1906
|
+
changed = true;
|
|
1907
|
+
}
|
|
1908
|
+
if (changed) {
|
|
1909
|
+
const next = { ...config };
|
|
1910
|
+
delete next.unscopedDepsNoted;
|
|
1911
|
+
if (Object.keys(noted).length)
|
|
1912
|
+
next.unscopedDepsNoted = noted;
|
|
1913
|
+
writeConfig(cwd, next);
|
|
1914
|
+
}
|
|
1915
|
+
}
|
|
1849
1916
|
/** Restore file: dependencies from .dependencies using a three-way merge that
|
|
1850
1917
|
* preserves any external modifications made since the last transform. */
|
|
1851
1918
|
export function restoreDeps(pkg, verbose = false) {
|
|
@@ -5546,13 +5613,21 @@ export async function promptText(message, defaultValue) {
|
|
|
5546
5613
|
});
|
|
5547
5614
|
});
|
|
5548
5615
|
}
|
|
5549
|
-
/**
|
|
5550
|
-
*
|
|
5551
|
-
*
|
|
5552
|
-
*
|
|
5553
|
-
*
|
|
5554
|
-
*
|
|
5555
|
-
|
|
5616
|
+
/** Show `header` and the options numbered 1..N in display order, then read a
|
|
5617
|
+
* choice. Empty input takes the first option. Returns the chosen `action`.
|
|
5618
|
+
* 2026-09-14 14:30 EDT — Claude Code (Fable 5.1), at Bob's direction. The git
|
|
5619
|
+
* setup menus were hand-numbered strings with a fixed "Use local install only"
|
|
5620
|
+
* line, so a library (no `bin`) was offered an install that doLocalInstall
|
|
5621
|
+
* would then skip. Options are now data, so a line can be left out. */
|
|
5622
|
+
export function promptMenu(header, options) {
|
|
5623
|
+
const lines = options.map((o, i) => ` ${i + 1}) ${o.label}`);
|
|
5624
|
+
const message = `${header}\n${lines.join('\n')}\nChoice [1]:`;
|
|
5625
|
+
const valid = options.map((_, i) => String(i + 1));
|
|
5626
|
+
return promptChoice(message, [...valid, '']).then(choice => {
|
|
5627
|
+
const index = choice === '' ? 0 : Number(choice) - 1;
|
|
5628
|
+
return options[index].action;
|
|
5629
|
+
});
|
|
5630
|
+
}
|
|
5556
5631
|
export function promptChoice(message, choices) {
|
|
5557
5632
|
const rl = readline.createInterface({
|
|
5558
5633
|
input: process.stdin,
|
|
@@ -6711,15 +6786,21 @@ async function doLocalInstall(cwd, options) {
|
|
|
6711
6786
|
const pkg = readPackageJson(cwd);
|
|
6712
6787
|
const pkgName = pkg.name || path.basename(cwd);
|
|
6713
6788
|
const pkgVersion = pkg.version || '0.0.0';
|
|
6789
|
+
// A library has no bin, so there is nothing to install: say what actually happens
|
|
6790
|
+
// (deps built, notes recorded, no publish) instead of announcing an install and
|
|
6791
|
+
// then taking it back. 2026-09-14 17:50 EDT — Claude Code (Fable 5.1), at Bob's
|
|
6792
|
+
// direction: "why mention local install when there is no install".
|
|
6793
|
+
if (!pkg.bin) {
|
|
6794
|
+
console.log(colors.blue(`Local mode: ${pkgName}@${pkgVersion} — library, nothing to install`));
|
|
6795
|
+
console.log(colors.dim('Not transformed or published; file: deps left as-is'));
|
|
6796
|
+
console.log(colors.dim(' (switch back to the publish flow with -global)'));
|
|
6797
|
+
recordLocalChange(cwd, pkgVersion, dryRun);
|
|
6798
|
+
return true;
|
|
6799
|
+
}
|
|
6714
6800
|
console.log(colors.blue(`Local install: ${pkgName}@${pkgVersion}`));
|
|
6715
6801
|
console.log(colors.dim('Skipping transform/publish — installing with file: deps as-is'));
|
|
6716
6802
|
console.log(colors.dim(' (switch back to the publish flow with -global)'));
|
|
6717
|
-
// Record before the bin check: a library's notes are history too.
|
|
6718
6803
|
recordLocalChange(cwd, pkgVersion, dryRun);
|
|
6719
|
-
if (!pkg.bin) {
|
|
6720
|
-
console.log(colors.dim(` (library — skipping global install)`));
|
|
6721
|
-
return true;
|
|
6722
|
-
}
|
|
6723
6804
|
if (dryRun) {
|
|
6724
6805
|
console.log(' [dry-run] Would run: npm install -g .');
|
|
6725
6806
|
if (wsl)
|
|
@@ -6856,10 +6937,17 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
|
|
|
6856
6937
|
// any transform/publish, so the user can fix them once and run clean.
|
|
6857
6938
|
// Only run at the top level (not during recursive cascade or workspace).
|
|
6858
6939
|
if (!noPrescan && !cleanup && !options._fromDep && !options._fromWorkspace) {
|
|
6859
|
-
const
|
|
6940
|
+
const allIssues = prescanDepGraph(cwd, new Set(), verbose);
|
|
6941
|
+
// Unscoped deps are handled apart from the error/warning prompt below: nothing the
|
|
6942
|
+
// user answers could change the outcome, so nothing is asked. Report, remember,
|
|
6943
|
+
// stop. See reportUnscopedDeps.
|
|
6944
|
+
// 2026-09-14 17:05 EDT — Claude Code (Fable 5.1), at Bob's direction: "The message
|
|
6945
|
+
// should simply point it out won't be published to npm till the scope is fixed and
|
|
6946
|
+
// then remember I was warned. It shouldn't [need] a response because nothing would
|
|
6947
|
+
// be done."
|
|
6948
|
+
const unscoped = allIssues.filter(i => i.kind === 'unscoped');
|
|
6949
|
+
const issues = allIssues.filter(i => i.kind !== 'unscoped');
|
|
6860
6950
|
if (issues.length > 0) {
|
|
6861
|
-
const errors = issues.filter(i => i.severity === 'error');
|
|
6862
|
-
const warnings = issues.filter(i => i.severity === 'warning');
|
|
6863
6951
|
console.log(colors.yellow(`Prescan found ${issues.length} issue(s) in the file: dep graph:`));
|
|
6864
6952
|
for (const i of issues) {
|
|
6865
6953
|
const tag = i.severity === 'error' ? colors.red('ERROR') : colors.yellow('WARN');
|
|
@@ -6869,6 +6957,18 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
|
|
|
6869
6957
|
console.log(colors.dim(` → ${i.suggestion}`));
|
|
6870
6958
|
}
|
|
6871
6959
|
console.log('');
|
|
6960
|
+
}
|
|
6961
|
+
// Always called: with nothing unscoped it only forgets deps that were noted
|
|
6962
|
+
// earlier and have since been scoped.
|
|
6963
|
+
reportUnscopedDeps(cwd, unscoped);
|
|
6964
|
+
if (unscoped.length > 0) {
|
|
6965
|
+
// The run stops here regardless of the other issues, so the continue prompt
|
|
6966
|
+
// below is skipped: it would ask a question whose answer changes nothing.
|
|
6967
|
+
return false;
|
|
6968
|
+
}
|
|
6969
|
+
if (issues.length > 0) {
|
|
6970
|
+
const errors = issues.filter(i => i.severity === 'error');
|
|
6971
|
+
const warnings = issues.filter(i => i.severity === 'warning');
|
|
6872
6972
|
if (errors.length > 0 && !force) {
|
|
6873
6973
|
console.error(colors.red(`Prescan aborted: ${errors.length} error(s). Fix them or rerun with --force.`));
|
|
6874
6974
|
return false;
|
|
@@ -7016,6 +7116,19 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
|
|
|
7016
7116
|
const gitStatus = getGitStatus(cwd);
|
|
7017
7117
|
// Handle init mode
|
|
7018
7118
|
let justInitialized = false;
|
|
7119
|
+
// 2026-09-14 14:30 EDT — Claude Code (Fable 5.1), at Bob's direction. The git
|
|
7120
|
+
// setup menus below offer "Use local install only" — a global npm install of
|
|
7121
|
+
// this package. That is only meaningful for a CLI (a package with `bin`);
|
|
7122
|
+
// doLocalInstall skips a library anyway, so a library is not offered the line.
|
|
7123
|
+
const hasBin = !!readPackageJson(cwd).bin;
|
|
7124
|
+
const localInstallOption = hasBin
|
|
7125
|
+
? [{ label: 'Use local install only (skip git/publish)', action: 'local' }]
|
|
7126
|
+
: [];
|
|
7127
|
+
const switchToLocalInstall = () => {
|
|
7128
|
+
console.log(colors.dim('Switching to local-only mode...'));
|
|
7129
|
+
writeConfig(cwd, { ...configOptions, localInstall: true }, new Set(['localInstall']));
|
|
7130
|
+
return doLocalInstall(cwd, options);
|
|
7131
|
+
};
|
|
7019
7132
|
if (!gitStatus.isRepo) {
|
|
7020
7133
|
console.log('No git repository found.');
|
|
7021
7134
|
// Probe for an existing remote we could adopt history from.
|
|
@@ -7069,55 +7182,54 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
|
|
|
7069
7182
|
// a reader who wanted the second line typed 2 and got something else. Bob:
|
|
7070
7183
|
// "I type 2 when I mean a because that's what humans do." Options are now
|
|
7071
7184
|
// numbered in display order; the ALL variant is 2, directly under the single.
|
|
7072
|
-
|
|
7185
|
+
// 2026-09-14 — Claude Code (Fable 5.1): menus are now promptMenu option
|
|
7186
|
+
// lists; the local-install line is present only for a package with `bin`.
|
|
7187
|
+
// The "No git repository found." header is printed once, above.
|
|
7188
|
+
let action;
|
|
7073
7189
|
if (adoptable) {
|
|
7074
|
-
|
|
7075
|
-
|
|
7076
|
-
|
|
7077
|
-
|
|
7078
|
-
|
|
7079
|
-
|
|
7080
|
-
|
|
7081
|
-
|
|
7082
|
-
|
|
7083
|
-
|
|
7084
|
-
}
|
|
7085
|
-
if (choice === '5') {
|
|
7190
|
+
action = await promptMenu('How would you like to set up git?', [
|
|
7191
|
+
{ label: 'Adopt history from existing remote (recommended)', action: 'adopt' },
|
|
7192
|
+
{ label: 'Adopt for ALL remaining deps (don\'t ask again)', action: 'adopt-all' },
|
|
7193
|
+
{ label: 'Initialize fresh git repository', action: 'init' },
|
|
7194
|
+
...localInstallOption,
|
|
7195
|
+
{ label: 'Abort', action: 'abort' },
|
|
7196
|
+
]);
|
|
7197
|
+
if (action === 'local')
|
|
7198
|
+
return switchToLocalInstall();
|
|
7199
|
+
if (action === 'abort') {
|
|
7086
7200
|
console.log('Aborted. Run with --init to initialize.');
|
|
7087
7201
|
return false;
|
|
7088
7202
|
}
|
|
7089
|
-
if (
|
|
7203
|
+
if (action === 'adopt-all')
|
|
7090
7204
|
options.autoInit = true;
|
|
7091
|
-
if (
|
|
7205
|
+
if (action === 'init') {
|
|
7092
7206
|
const success = await initGit(cwd, gitVisibility, dryRun, allowTs);
|
|
7093
7207
|
if (!success)
|
|
7094
7208
|
return false;
|
|
7095
7209
|
}
|
|
7096
7210
|
else {
|
|
7097
|
-
// '
|
|
7211
|
+
// 'adopt' or 'adopt-all'
|
|
7098
7212
|
const success = await adoptExistingRemote(cwd, adoptable.url, adoptable.defaultBranch, dryRun);
|
|
7099
7213
|
if (!success)
|
|
7100
7214
|
return false;
|
|
7101
7215
|
}
|
|
7102
7216
|
}
|
|
7103
7217
|
else {
|
|
7104
|
-
|
|
7105
|
-
|
|
7106
|
-
|
|
7107
|
-
|
|
7108
|
-
|
|
7109
|
-
|
|
7110
|
-
|
|
7111
|
-
|
|
7112
|
-
|
|
7113
|
-
}
|
|
7114
|
-
if (choice === '4') {
|
|
7218
|
+
action = await promptMenu('What would you like to do?', [
|
|
7219
|
+
{ label: 'Initialize git repository', action: 'init' },
|
|
7220
|
+
{ label: 'Initialize for ALL remaining deps (don\'t ask again)', action: 'init-all' },
|
|
7221
|
+
...localInstallOption,
|
|
7222
|
+
{ label: 'Abort', action: 'abort' },
|
|
7223
|
+
]);
|
|
7224
|
+
if (action === 'local')
|
|
7225
|
+
return switchToLocalInstall();
|
|
7226
|
+
if (action === 'abort') {
|
|
7115
7227
|
console.log('Aborted. Run with --init to initialize.');
|
|
7116
7228
|
return false;
|
|
7117
7229
|
}
|
|
7118
|
-
if (
|
|
7230
|
+
if (action === 'init-all')
|
|
7119
7231
|
options.autoInit = true;
|
|
7120
|
-
//
|
|
7232
|
+
// 'init' or 'init-all'
|
|
7121
7233
|
const success = await initGit(cwd, gitVisibility, dryRun, allowTs);
|
|
7122
7234
|
if (!success)
|
|
7123
7235
|
return false;
|
|
@@ -7143,22 +7255,21 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
|
|
|
7143
7255
|
else if (!gitStatus.hasRemote) {
|
|
7144
7256
|
// Git repo exists but no remote - need to create GitHub repo
|
|
7145
7257
|
if (!init && !options.autoInit && !publishDepsYes) {
|
|
7146
|
-
// 2026-09-
|
|
7147
|
-
|
|
7148
|
-
|
|
7149
|
-
|
|
7150
|
-
|
|
7151
|
-
|
|
7152
|
-
|
|
7153
|
-
|
|
7154
|
-
|
|
7155
|
-
return
|
|
7156
|
-
|
|
7157
|
-
if (choice === '4') {
|
|
7258
|
+
// 2026-09-14 — Claude Code (Fable 5.1): promptMenu option list; local-install
|
|
7259
|
+
// line only for a package with `bin` (see hasBin above).
|
|
7260
|
+
const action = await promptMenu('No git remote configured. What would you like to do?', [
|
|
7261
|
+
{ label: 'Create GitHub repository', action: 'create' },
|
|
7262
|
+
{ label: 'Create for ALL remaining deps (don\'t ask again)', action: 'create-all' },
|
|
7263
|
+
...localInstallOption,
|
|
7264
|
+
{ label: 'Abort', action: 'abort' },
|
|
7265
|
+
]);
|
|
7266
|
+
if (action === 'local')
|
|
7267
|
+
return switchToLocalInstall();
|
|
7268
|
+
if (action === 'abort') {
|
|
7158
7269
|
console.log('Aborted. Run with --init to set up GitHub repository.');
|
|
7159
7270
|
return false;
|
|
7160
7271
|
}
|
|
7161
|
-
if (
|
|
7272
|
+
if (action === 'create-all') {
|
|
7162
7273
|
options.autoInit = true;
|
|
7163
7274
|
}
|
|
7164
7275
|
}
|
|
@@ -7417,6 +7528,21 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
|
|
|
7417
7528
|
publicConfirmed = true;
|
|
7418
7529
|
}
|
|
7419
7530
|
}
|
|
7531
|
+
// A dependency reached through the cascade is never renamed. Adding a scope changes the
|
|
7532
|
+
// name every consumer imports by, which is the dep owner's project, not a publish-time
|
|
7533
|
+
// side effect. Refuse and say what to fix; the top-level run on the package itself may
|
|
7534
|
+
// still offer the rename below, because there the user chose to work on that package.
|
|
7535
|
+
// 2026-09-14 16:40 EDT — Claude Code (Fable 5.1), at Bob's direction: "It shouldn't try
|
|
7536
|
+
// to publish to a scope not in the package. I need to fix the package and that's a
|
|
7537
|
+
// separate and messy project."
|
|
7538
|
+
// noprefix in the dep's .globalize.json5 is the same escape hatch the prescan honors.
|
|
7539
|
+
if (!isScoped && options._fromDep && effectiveNpmVisibility !== 'public' && currentAccess !== 'public'
|
|
7540
|
+
&& readConfig(cwd).noprefix !== true) {
|
|
7541
|
+
console.error(colors.red(`Dependency '${pkg.name}' is unscoped and cannot be published privately.`));
|
|
7542
|
+
console.error(colors.red(` npmglobalize does not rename dependencies. Scope it in ${path.join(cwd, 'package.json')},`));
|
|
7543
|
+
console.error(colors.red(` or set npmVisibility="public" or noprefix=true in its .globalize.json5.`));
|
|
7544
|
+
return false;
|
|
7545
|
+
}
|
|
7420
7546
|
if (effectiveNpmVisibility === 'private') {
|
|
7421
7547
|
// User explicitly wants private publication
|
|
7422
7548
|
if (!isScoped) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bobfrankston/npmglobalize",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.228",
|
|
4
4
|
"description": "Transform file: dependencies to npm versions for publishing",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"@bobfrankston/freezepak": "^0.1.9",
|
|
35
35
|
"@bobfrankston/importgen": "^0.1.43",
|
|
36
36
|
"@bobfrankston/themecolors": "^0.1.10",
|
|
37
|
-
"@bobfrankston/userconfig": "^1.0.
|
|
37
|
+
"@bobfrankston/userconfig": "^1.0.15",
|
|
38
38
|
"@npmcli/package-json": "^7.0.4",
|
|
39
39
|
"json5": "^2.2.3",
|
|
40
40
|
"libnpmversion": "^8.0.3",
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
"@bobfrankston/freezepak": "^0.1.9",
|
|
63
63
|
"@bobfrankston/importgen": "^0.1.43",
|
|
64
64
|
"@bobfrankston/themecolors": "^0.1.10",
|
|
65
|
-
"@bobfrankston/userconfig": "^1.0.
|
|
65
|
+
"@bobfrankston/userconfig": "^1.0.15",
|
|
66
66
|
"@npmcli/package-json": "^7.0.4",
|
|
67
67
|
"json5": "^2.2.3",
|
|
68
68
|
"libnpmversion": "^8.0.3",
|