@bobfrankston/npmglobalize 1.0.168 → 1.0.170
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/cli.js +9 -0
- package/lib/config.js +1 -1
- package/lib/types.d.ts +2 -0
- package/lib.d.ts +6 -0
- package/lib.js +119 -28
- package/package.json +1 -1
package/cli.js
CHANGED
|
@@ -37,6 +37,12 @@ Dependency Options:
|
|
|
37
37
|
-publish-deps Auto-publish file: dependencies (default)
|
|
38
38
|
-pd Like -publish-deps, plus auto-yes to dep-cascade prompts (private only)
|
|
39
39
|
-no-publish-deps, -npd Don't auto-publish file: dependencies (use with caution)
|
|
40
|
+
-public-deps Cascade npmVisibility:"public" to all transitive workspace/file:
|
|
41
|
+
deps. In workspace mode: auto-promotes every reachable workspace
|
|
42
|
+
member without prompting (writes npmVisibility:"public" into each
|
|
43
|
+
dep's .globalize.json5). In single-package mode: also propagates
|
|
44
|
+
public visibility to already-published deps in the cascade.
|
|
45
|
+
Use when a public consumer's deps weren't all marked public.
|
|
40
46
|
-no-prescan, -nps Skip upfront dep-graph prescan
|
|
41
47
|
-force-publish Republish dependencies even if version exists
|
|
42
48
|
-fix Run npm audit fix after transformation
|
|
@@ -298,6 +304,9 @@ function parseArgs(args) {
|
|
|
298
304
|
case '-npd':
|
|
299
305
|
options.publishDeps = false; // Disable auto-publishing
|
|
300
306
|
break;
|
|
307
|
+
case '-public-deps':
|
|
308
|
+
options.publicDeps = true;
|
|
309
|
+
break;
|
|
301
310
|
case '-no-use-paths':
|
|
302
311
|
case '-nup':
|
|
303
312
|
options.usePaths = false; // Skip sibling-path resolution; use latest npm version for file: deps
|
package/lib/config.js
CHANGED
|
@@ -65,7 +65,7 @@ export function writeConfig(dir, config, explicitKeys) {
|
|
|
65
65
|
const existing = readConfig(dir);
|
|
66
66
|
// Filter out temporary flags and default values (unless explicitly set)
|
|
67
67
|
const filtered = {};
|
|
68
|
-
const omitKeys = new Set(['cleanup', 'init', 'dryRun', 'message', 'conform', 'asis', 'help', 'error', 'updateDeps', 'updateMajor', 'publishDeps', 'forcePublish', 'once', 'cleanNestedModules']);
|
|
68
|
+
const omitKeys = new Set(['cleanup', 'init', 'dryRun', 'message', 'conform', 'asis', 'help', 'error', 'updateDeps', 'updateMajor', 'publishDeps', 'publicDeps', 'forcePublish', 'once', 'cleanNestedModules']);
|
|
69
69
|
for (const [key, value] of Object.entries(config)) {
|
|
70
70
|
if (omitKeys.has(key))
|
|
71
71
|
continue;
|
package/lib/types.d.ts
CHANGED
|
@@ -54,6 +54,8 @@ export interface GlobalizeOptions {
|
|
|
54
54
|
publishDeps?: boolean;
|
|
55
55
|
/** Auto-yes to dep-cascade prompts (add scope for private); does NOT auto-yes public prompts */
|
|
56
56
|
publishDepsYes?: boolean;
|
|
57
|
+
/** Cascade npmVisibility:"public" to all transitive workspace/file: deps without prompting. */
|
|
58
|
+
publicDeps?: boolean;
|
|
57
59
|
/** Skip the upfront dep-graph prescan */
|
|
58
60
|
noPrescan?: boolean;
|
|
59
61
|
/** Force republish dependencies even if version exists on npm */
|
package/lib.d.ts
CHANGED
|
@@ -73,6 +73,12 @@ export interface GlobalizeOptions {
|
|
|
73
73
|
publishDeps?: boolean;
|
|
74
74
|
/** Auto-yes to dep-cascade prompts (add scope to make private, etc.); does NOT auto-yes public prompts */
|
|
75
75
|
publishDepsYes?: boolean;
|
|
76
|
+
/** Cascade npmVisibility:"public" to all transitive workspace/file: deps without prompting.
|
|
77
|
+
* In workspace mode: fixpoint-promotes every workspace member reachable from a public consumer.
|
|
78
|
+
* In single-package mode: drops the "first publish only" guard so already-published deps also
|
|
79
|
+
* get the parent's npmVisibility propagated. Use to fix a workspace where a public member's
|
|
80
|
+
* deps weren't all marked public (and the published tarball would 404 on install). */
|
|
81
|
+
publicDeps?: boolean;
|
|
76
82
|
/** Force republish dependencies even if version exists on npm */
|
|
77
83
|
forcePublish?: boolean;
|
|
78
84
|
/** Run npm audit and fix vulnerabilities */
|
package/lib.js
CHANGED
|
@@ -238,7 +238,7 @@ export function writeConfig(dir, config, explicitKeys) {
|
|
|
238
238
|
const existing = readConfig(dir);
|
|
239
239
|
// Filter out temporary flags and default values (unless explicitly set)
|
|
240
240
|
const filtered = {};
|
|
241
|
-
const omitKeys = new Set(['cleanup', 'init', 'dryRun', 'message', 'conform', 'asis', 'help', 'error', 'updateDeps', 'updateMajor', 'publishDeps', 'forcePublish', 'once', 'cleanNestedModules']);
|
|
241
|
+
const omitKeys = new Set(['cleanup', 'init', 'dryRun', 'message', 'conform', 'asis', 'help', 'error', 'updateDeps', 'updateMajor', 'publishDeps', 'publicDeps', 'forcePublish', 'once', 'cleanNestedModules']);
|
|
242
242
|
for (const [key, value] of Object.entries(config)) {
|
|
243
243
|
if (omitKeys.has(key))
|
|
244
244
|
continue;
|
|
@@ -2042,9 +2042,18 @@ export function getGitStatus(cwd) {
|
|
|
2042
2042
|
remoteBranch: '',
|
|
2043
2043
|
isBehindRemote: false
|
|
2044
2044
|
};
|
|
2045
|
-
// Check if git repo
|
|
2046
|
-
|
|
2047
|
-
|
|
2045
|
+
// Check if git repo — walk up the directory tree to find the enclosing
|
|
2046
|
+
// .git, like git itself does. Previously this only looked for `.git/` at
|
|
2047
|
+
// cwd, which broke for any subdir inside a repo (e.g., a workspace
|
|
2048
|
+
// member that lives inside a parent monorepo's git tree).
|
|
2049
|
+
let gitDir;
|
|
2050
|
+
try {
|
|
2051
|
+
const result = execSync('git rev-parse --git-dir', { cwd, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'ignore'] }).trim();
|
|
2052
|
+
if (!result)
|
|
2053
|
+
return status;
|
|
2054
|
+
gitDir = path.isAbsolute(result) ? result : path.resolve(cwd, result);
|
|
2055
|
+
}
|
|
2056
|
+
catch {
|
|
2048
2057
|
return status;
|
|
2049
2058
|
}
|
|
2050
2059
|
status.isRepo = true;
|
|
@@ -3099,6 +3108,7 @@ async function doLocalInstall(cwd, options) {
|
|
|
3099
3108
|
export async function globalize(cwd, options = {}, configOptions = {}) {
|
|
3100
3109
|
const { bump = 'patch', noPublish = false, cleanup = false, install = false, link = false, wsl = false, force = false, files = true, dryRun = false, quiet = true, verbose = false, init = false, gitVisibility = 'private', npmVisibility = 'private', message, conform = false, asis = false, updateDeps = false, updateMajor = false, publishDeps = true, // Default to publishing deps for safety
|
|
3101
3110
|
publishDepsYes = false, // -pd: auto-yes to dep-cascade prompts (private only)
|
|
3111
|
+
publicDeps = false, // -public-deps: cascade public visibility to all deps
|
|
3102
3112
|
noPrescan = false, forcePublish = false, fix = true, fixTags = false, rebase = false, show = false, local = false, freeze = false, usePaths = true, allowTs } = options;
|
|
3103
3113
|
// Show tool version only for recursive dep calls (CLI already prints it at startup)
|
|
3104
3114
|
const toolVersion = getToolVersion();
|
|
@@ -3976,8 +3986,11 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
|
|
|
3976
3986
|
if (!dryRun) {
|
|
3977
3987
|
// Check if package has EVER been published to npm (any version)
|
|
3978
3988
|
const hasBeenPublished = checkPackageExists(name);
|
|
3979
|
-
// Recursively call globalize on the dependency
|
|
3980
|
-
//
|
|
3989
|
+
// Recursively call globalize on the dependency.
|
|
3990
|
+
// Normally only pass npmVisibility to truly NEW packages (never published
|
|
3991
|
+
// before) — already-published deps keep their own visibility setting.
|
|
3992
|
+
// -public-deps overrides this: propagate npmVisibility regardless so a
|
|
3993
|
+
// public consumer's transitive deps all get promoted to public.
|
|
3981
3994
|
const depSuccess = await globalize(path, {
|
|
3982
3995
|
bump: 'patch', // Use existing version, don't bump
|
|
3983
3996
|
verbose,
|
|
@@ -3985,13 +3998,14 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
|
|
|
3985
3998
|
force,
|
|
3986
3999
|
files,
|
|
3987
4000
|
gitVisibility,
|
|
3988
|
-
npmVisibility: hasBeenPublished ? undefined : npmVisibility,
|
|
4001
|
+
npmVisibility: (hasBeenPublished && !publicDeps) ? undefined : npmVisibility,
|
|
3989
4002
|
updateDeps,
|
|
3990
4003
|
updateMajor,
|
|
3991
4004
|
fix,
|
|
3992
4005
|
conform, // Propagate conform to dependencies
|
|
3993
4006
|
publishDeps, // Propagate so transitive deps also get published
|
|
3994
4007
|
publishDepsYes, // Propagate auto-yes through cascade
|
|
4008
|
+
publicDeps, // Propagate so transitive deps also get promoted to public
|
|
3995
4009
|
forcePublish, // Propagate so transitive deps get force-published too
|
|
3996
4010
|
_fromDep: true, // Suppress nested prescan
|
|
3997
4011
|
rebase, // Propagate so behind-remote deps get rebased automatically
|
|
@@ -5355,32 +5369,109 @@ export async function globalizeWorkspace(rootDir, options = {}, configOptions =
|
|
|
5355
5369
|
}
|
|
5356
5370
|
}
|
|
5357
5371
|
const wsNameSet = new Set(packages.map(p => p.name));
|
|
5358
|
-
|
|
5359
|
-
|
|
5360
|
-
|
|
5361
|
-
|
|
5362
|
-
|
|
5363
|
-
|
|
5364
|
-
|
|
5365
|
-
|
|
5372
|
+
// Fixpoint: each promotion can expose new transitive deps (a newly-public B may
|
|
5373
|
+
// itself depend on a private C). Iterate until no further promotions happen so
|
|
5374
|
+
// chains like public A → private B → private C are fully resolved regardless of
|
|
5375
|
+
// packages[] order.
|
|
5376
|
+
{
|
|
5377
|
+
let changed = true;
|
|
5378
|
+
let guard = 0;
|
|
5379
|
+
while (changed && guard++ < 50) {
|
|
5380
|
+
changed = false;
|
|
5381
|
+
for (const pkgInfo of packages) {
|
|
5382
|
+
if (visibilityMap.get(pkgInfo.name) !== 'public')
|
|
5383
|
+
continue;
|
|
5384
|
+
const deps = { ...pkgInfo.pkg.dependencies, ...pkgInfo.pkg.devDependencies };
|
|
5385
|
+
for (const depName of Object.keys(deps || {})) {
|
|
5386
|
+
if (!wsNameSet.has(depName))
|
|
5387
|
+
continue;
|
|
5388
|
+
if (visibilityMap.get(depName) === 'public')
|
|
5389
|
+
continue;
|
|
5390
|
+
const depInfo = packages.find(p => p.name === depName);
|
|
5391
|
+
if (!depInfo)
|
|
5392
|
+
continue;
|
|
5393
|
+
console.log(colors.yellow(`⚠ Public package ${pkgInfo.name} depends on ${depName} which has no public visibility set.`));
|
|
5394
|
+
let makePublic = false;
|
|
5395
|
+
if (options.publicDeps) {
|
|
5396
|
+
console.log(colors.green(` -public-deps: auto-promoting ${depName} to public`));
|
|
5397
|
+
makePublic = true;
|
|
5398
|
+
}
|
|
5399
|
+
else if (options.dryRun) {
|
|
5400
|
+
console.log(colors.dim(` [dry-run] Would ask to make ${depName} public`));
|
|
5401
|
+
}
|
|
5402
|
+
else {
|
|
5403
|
+
makePublic = await confirm(`Make ${depName} public too?`, true);
|
|
5404
|
+
}
|
|
5405
|
+
if (makePublic) {
|
|
5406
|
+
if (!options.dryRun) {
|
|
5407
|
+
const depConfig = readConfig(depInfo.dir);
|
|
5408
|
+
depConfig.npmVisibility = 'public';
|
|
5409
|
+
writeConfig(depInfo.dir, depConfig, new Set(['npmVisibility']));
|
|
5410
|
+
console.log(colors.green(`✓ Set ${depName} to public in .globalize.json5`));
|
|
5411
|
+
}
|
|
5412
|
+
else {
|
|
5413
|
+
console.log(colors.dim(` [dry-run] Would set ${depName} npmVisibility:"public"`));
|
|
5414
|
+
}
|
|
5415
|
+
visibilityMap.set(depName, 'public');
|
|
5416
|
+
changed = true;
|
|
5417
|
+
}
|
|
5418
|
+
}
|
|
5419
|
+
}
|
|
5420
|
+
}
|
|
5421
|
+
}
|
|
5422
|
+
// Fail-loud: refuse to publish a public workspace member whose file: deps point
|
|
5423
|
+
// at non-publishable workspace siblings (private:true, noPublish:true, or still
|
|
5424
|
+
// not marked public after the prompt loop). A registry tarball declaring such
|
|
5425
|
+
// deps would 404 on a clean install. See docs/npmglobalize-transitive-workspace-deps.md.
|
|
5426
|
+
{
|
|
5427
|
+
const allMembers = resolveAllWorkspacePackages(rootDir);
|
|
5428
|
+
const memberByName = new Map();
|
|
5429
|
+
for (const m of allMembers)
|
|
5430
|
+
memberByName.set(m.name, m);
|
|
5431
|
+
const blockers = [];
|
|
5432
|
+
const filteredSet = new Set(filteredOrder);
|
|
5433
|
+
for (const pkgInfo of packages) {
|
|
5434
|
+
if (visibilityMap.get(pkgInfo.name) !== 'public')
|
|
5366
5435
|
continue;
|
|
5367
|
-
|
|
5368
|
-
if (!
|
|
5436
|
+
// Only check members that will actually be processed this run.
|
|
5437
|
+
if (!filteredSet.has(pkgInfo.name))
|
|
5369
5438
|
continue;
|
|
5370
|
-
|
|
5371
|
-
|
|
5372
|
-
|
|
5373
|
-
|
|
5374
|
-
|
|
5375
|
-
|
|
5376
|
-
|
|
5377
|
-
|
|
5378
|
-
|
|
5439
|
+
const deps = { ...pkgInfo.pkg.dependencies, ...pkgInfo.pkg.devDependencies };
|
|
5440
|
+
for (const [depName, depVal] of Object.entries(deps)) {
|
|
5441
|
+
if (typeof depVal !== 'string')
|
|
5442
|
+
continue;
|
|
5443
|
+
const member = memberByName.get(depName);
|
|
5444
|
+
if (!member)
|
|
5445
|
+
continue; // External dep, not workspace-internal — out of scope.
|
|
5446
|
+
if (member.pkg.private) {
|
|
5447
|
+
blockers.push({ pkg: pkgInfo.name, dep: depName, reason: `${depName} has "private": true in package.json (cannot publish)` });
|
|
5448
|
+
continue;
|
|
5449
|
+
}
|
|
5450
|
+
const depConfig = readConfig(member.dir);
|
|
5451
|
+
if (depConfig.noPublish) {
|
|
5452
|
+
blockers.push({ pkg: pkgInfo.name, dep: depName, reason: `${depName} has noPublish:true in .globalize.json5` });
|
|
5453
|
+
continue;
|
|
5454
|
+
}
|
|
5455
|
+
if (visibilityMap.get(depName) !== 'public') {
|
|
5456
|
+
blockers.push({ pkg: pkgInfo.name, dep: depName, reason: `${depName} is not marked public (set npmVisibility:"public" in its .globalize.json5, or rerun with -public-deps)` });
|
|
5457
|
+
continue;
|
|
5379
5458
|
}
|
|
5380
5459
|
}
|
|
5381
|
-
|
|
5382
|
-
|
|
5460
|
+
}
|
|
5461
|
+
if (blockers.length > 0) {
|
|
5462
|
+
console.log('');
|
|
5463
|
+
console.log(colors.red('✗ Refusing to publish: public workspace members depend on non-publishable siblings.'));
|
|
5464
|
+
console.log(colors.red(' The published tarball would 404 on a clean install.'));
|
|
5465
|
+
console.log('');
|
|
5466
|
+
for (const b of blockers) {
|
|
5467
|
+
console.log(colors.red(` ${b.pkg} → ${b.dep}: ${b.reason}`));
|
|
5383
5468
|
}
|
|
5469
|
+
console.log('');
|
|
5470
|
+
console.log(colors.dim('Fixes:'));
|
|
5471
|
+
console.log(colors.dim(' - Set npmVisibility:"public" in each offending dep\'s .globalize.json5'));
|
|
5472
|
+
console.log(colors.dim(' - Or rerun with -public-deps to auto-promote all transitive workspace deps'));
|
|
5473
|
+
console.log(colors.dim(' - Or remove npmVisibility:"public" from the consumer (keep it bundled under the workspace root)'));
|
|
5474
|
+
return { success: false, packages: [], publishOrder };
|
|
5384
5475
|
}
|
|
5385
5476
|
}
|
|
5386
5477
|
// Sync workspace-root node_modules with member package.json files. Catches
|