@bobfrankston/npmglobalize 1.0.159 → 1.0.160
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/lib.d.ts +17 -3
- package/lib.js +94 -14
- package/package.json +1 -1
package/lib.d.ts
CHANGED
|
@@ -243,12 +243,26 @@ export declare function parseVersionTag(tag: string): number[] | null;
|
|
|
243
243
|
export declare function compareVersions(a: number[], b: number[]): number;
|
|
244
244
|
/** Fix version/tag mismatches */
|
|
245
245
|
export declare function fixVersionTagMismatch(cwd: string, pkg: any, verbose?: boolean): boolean;
|
|
246
|
+
/** Return declared deps (dependencies + devDependencies) that don't resolve from
|
|
247
|
+
* `pkgDir`. Skips `file:`/`workspace:`/`link:` specs — those are handled elsewhere.
|
|
248
|
+
* A declared dep that doesn't resolve means `package.json` and installed
|
|
249
|
+
* `node_modules/` are out of sync (e.g. dep added but `npm install` not re-run). */
|
|
250
|
+
export declare function missingDeps(pkgDir: string, pkg: any): string[];
|
|
246
251
|
/** Walk `file:` deps transitively and run `npm install` in any target whose
|
|
247
|
-
*
|
|
248
|
-
*
|
|
249
|
-
*
|
|
252
|
+
* declared deps aren't all resolvable from its directory. Covers both
|
|
253
|
+
* "fresh clone, no `node_modules/`" and "dep added but `npm install` not
|
|
254
|
+
* re-run" (partial-sync) cases. Also covers `cwd` itself on the first call.
|
|
250
255
|
* Cycle-safe via the shared `visited` set. */
|
|
251
256
|
export declare function ensureFileDepModules(cwd: string, verbose?: boolean, visited?: Set<string>): void;
|
|
257
|
+
/** Ensure the workspace-root `node_modules/` is in sync with every member's
|
|
258
|
+
* declared deps. Workspaces hoist deps to the root, so a dep added to any
|
|
259
|
+
* member `package.json` without a follow-up `npm install` at the root leaves
|
|
260
|
+
* the root `node_modules/` stale — the case where `member/` dirs exist but
|
|
261
|
+
* the actual package (e.g. `marked`) is not hoisted. */
|
|
262
|
+
export declare function ensureWorkspaceDepModules(rootDir: string, members: Array<{
|
|
263
|
+
dir: string;
|
|
264
|
+
pkg: any;
|
|
265
|
+
}>, verbose?: boolean): void;
|
|
252
266
|
/** Run a command and return success status */
|
|
253
267
|
export declare function runCommand(cmd: string, args: string[], options?: {
|
|
254
268
|
silent?: boolean;
|
package/lib.js
CHANGED
|
@@ -1322,10 +1322,51 @@ function restoreNestedDepModules(stashed, verbose) {
|
|
|
1322
1322
|
}
|
|
1323
1323
|
}
|
|
1324
1324
|
}
|
|
1325
|
+
/** Walk up the directory tree looking for `node_modules/<depName>/package.json`.
|
|
1326
|
+
* Matches Node's module resolution so hoisted workspace deps are found. */
|
|
1327
|
+
function depResolves(startDir, depName) {
|
|
1328
|
+
let dir = path.resolve(startDir);
|
|
1329
|
+
const segs = depName.split('/');
|
|
1330
|
+
while (true) {
|
|
1331
|
+
const candidate = path.join(dir, 'node_modules', ...segs, 'package.json');
|
|
1332
|
+
if (fs.existsSync(candidate))
|
|
1333
|
+
return true;
|
|
1334
|
+
const parent = path.dirname(dir);
|
|
1335
|
+
if (parent === dir)
|
|
1336
|
+
return false;
|
|
1337
|
+
dir = parent;
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
/** Return declared deps (dependencies + devDependencies) that don't resolve from
|
|
1341
|
+
* `pkgDir`. Skips `file:`/`workspace:`/`link:` specs — those are handled elsewhere.
|
|
1342
|
+
* A declared dep that doesn't resolve means `package.json` and installed
|
|
1343
|
+
* `node_modules/` are out of sync (e.g. dep added but `npm install` not re-run). */
|
|
1344
|
+
export function missingDeps(pkgDir, pkg) {
|
|
1345
|
+
const missing = [];
|
|
1346
|
+
for (const key of ['dependencies', 'devDependencies']) {
|
|
1347
|
+
const deps = pkg?.[key];
|
|
1348
|
+
if (!deps || typeof deps !== 'object')
|
|
1349
|
+
continue;
|
|
1350
|
+
for (const [name, spec] of Object.entries(deps)) {
|
|
1351
|
+
if (typeof spec !== 'string')
|
|
1352
|
+
continue;
|
|
1353
|
+
if (spec.startsWith('file:') || spec.startsWith('workspace:') || spec.startsWith('link:'))
|
|
1354
|
+
continue;
|
|
1355
|
+
if (!depResolves(pkgDir, name))
|
|
1356
|
+
missing.push(name);
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
return missing;
|
|
1360
|
+
}
|
|
1361
|
+
function formatMissingReason(missing) {
|
|
1362
|
+
if (missing.length === 1)
|
|
1363
|
+
return `missing dep: ${missing[0]}`;
|
|
1364
|
+
return `${missing.length} missing deps: ${missing.slice(0, 3).join(', ')}${missing.length > 3 ? ', …' : ''}`;
|
|
1365
|
+
}
|
|
1325
1366
|
/** Walk `file:` deps transitively and run `npm install` in any target whose
|
|
1326
|
-
*
|
|
1327
|
-
*
|
|
1328
|
-
*
|
|
1367
|
+
* declared deps aren't all resolvable from its directory. Covers both
|
|
1368
|
+
* "fresh clone, no `node_modules/`" and "dep added but `npm install` not
|
|
1369
|
+
* re-run" (partial-sync) cases. Also covers `cwd` itself on the first call.
|
|
1329
1370
|
* Cycle-safe via the shared `visited` set. */
|
|
1330
1371
|
export function ensureFileDepModules(cwd, verbose = false, visited = new Set()) {
|
|
1331
1372
|
const abs = path.resolve(cwd);
|
|
@@ -1339,11 +1380,13 @@ export function ensureFileDepModules(cwd, verbose = false, visited = new Set())
|
|
|
1339
1380
|
catch {
|
|
1340
1381
|
return;
|
|
1341
1382
|
}
|
|
1342
|
-
const
|
|
1343
|
-
|
|
1344
|
-
|
|
1383
|
+
const cwdMissing = missingDeps(abs, pkg);
|
|
1384
|
+
if (cwdMissing.length > 0) {
|
|
1385
|
+
// Only clear a stale lockfile if node_modules is entirely absent (fresh clone).
|
|
1386
|
+
// For partial-sync cases, keep the lockfile so pinned versions stay honored.
|
|
1387
|
+
const nm = path.join(abs, 'node_modules');
|
|
1345
1388
|
const lock = path.join(abs, 'package-lock.json');
|
|
1346
|
-
if (fs.existsSync(lock)) {
|
|
1389
|
+
if (!fs.existsSync(nm) && fs.existsSync(lock)) {
|
|
1347
1390
|
if (verbose)
|
|
1348
1391
|
console.log(colors.dim(` removing stale ${lock}`));
|
|
1349
1392
|
try {
|
|
@@ -1351,7 +1394,7 @@ export function ensureFileDepModules(cwd, verbose = false, visited = new Set())
|
|
|
1351
1394
|
}
|
|
1352
1395
|
catch { /* best-effort */ }
|
|
1353
1396
|
}
|
|
1354
|
-
console.log(colors.yellow(`↻ installing node_modules in ${pkg?.name || abs}`));
|
|
1397
|
+
console.log(colors.yellow(`↻ installing node_modules in ${pkg?.name || abs} (${formatMissingReason(cwdMissing)})`));
|
|
1355
1398
|
const r = runCommand('npm', ['install'], { cwd: abs, silent: !verbose });
|
|
1356
1399
|
if (!r.success) {
|
|
1357
1400
|
console.error(colors.red(` ✗ npm install failed in ${abs}`));
|
|
@@ -1376,12 +1419,11 @@ export function ensureFileDepModules(cwd, verbose = false, visited = new Set())
|
|
|
1376
1419
|
catch {
|
|
1377
1420
|
continue;
|
|
1378
1421
|
}
|
|
1379
|
-
const
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
if (hasDeps && !fs.existsSync(nm)) {
|
|
1422
|
+
const targetMissing = missingDeps(target, targetPkg);
|
|
1423
|
+
if (targetMissing.length > 0) {
|
|
1424
|
+
const nm = path.join(target, 'node_modules');
|
|
1383
1425
|
const lock = path.join(target, 'package-lock.json');
|
|
1384
|
-
if (fs.existsSync(lock)) {
|
|
1426
|
+
if (!fs.existsSync(nm) && fs.existsSync(lock)) {
|
|
1385
1427
|
if (verbose)
|
|
1386
1428
|
console.log(colors.dim(` removing stale ${lock}`));
|
|
1387
1429
|
try {
|
|
@@ -1389,7 +1431,7 @@ export function ensureFileDepModules(cwd, verbose = false, visited = new Set())
|
|
|
1389
1431
|
}
|
|
1390
1432
|
catch { /* best-effort */ }
|
|
1391
1433
|
}
|
|
1392
|
-
console.log(colors.yellow(`↻ restoring node_modules in ${name} (${target})`));
|
|
1434
|
+
console.log(colors.yellow(`↻ restoring node_modules in ${name} (${target}) (${formatMissingReason(targetMissing)})`));
|
|
1393
1435
|
const r = runCommand('npm', ['install'], { cwd: target, silent: !verbose });
|
|
1394
1436
|
if (!r.success) {
|
|
1395
1437
|
console.error(colors.red(` ✗ npm install failed in ${target}`));
|
|
@@ -1401,6 +1443,38 @@ export function ensureFileDepModules(cwd, verbose = false, visited = new Set())
|
|
|
1401
1443
|
}
|
|
1402
1444
|
}
|
|
1403
1445
|
}
|
|
1446
|
+
/** Ensure the workspace-root `node_modules/` is in sync with every member's
|
|
1447
|
+
* declared deps. Workspaces hoist deps to the root, so a dep added to any
|
|
1448
|
+
* member `package.json` without a follow-up `npm install` at the root leaves
|
|
1449
|
+
* the root `node_modules/` stale — the case where `member/` dirs exist but
|
|
1450
|
+
* the actual package (e.g. `marked`) is not hoisted. */
|
|
1451
|
+
export function ensureWorkspaceDepModules(rootDir, members, verbose = false) {
|
|
1452
|
+
const root = path.resolve(rootDir);
|
|
1453
|
+
let rootPkg;
|
|
1454
|
+
try {
|
|
1455
|
+
rootPkg = readPackageJson(root);
|
|
1456
|
+
}
|
|
1457
|
+
catch {
|
|
1458
|
+
return;
|
|
1459
|
+
}
|
|
1460
|
+
const allMissing = new Set();
|
|
1461
|
+
for (const name of missingDeps(root, rootPkg))
|
|
1462
|
+
allMissing.add(name);
|
|
1463
|
+
for (const m of members) {
|
|
1464
|
+
for (const name of missingDeps(m.dir, m.pkg))
|
|
1465
|
+
allMissing.add(name);
|
|
1466
|
+
}
|
|
1467
|
+
if (allMissing.size === 0)
|
|
1468
|
+
return;
|
|
1469
|
+
const list = [...allMissing];
|
|
1470
|
+
console.log(colors.yellow(`↻ installing workspace node_modules in ${rootPkg?.name || path.basename(root)} (${formatMissingReason(list)})`));
|
|
1471
|
+
const r = runCommand('npm', ['install'], { cwd: root, silent: !verbose });
|
|
1472
|
+
if (!r.success) {
|
|
1473
|
+
console.error(colors.red(` ✗ npm install failed in ${root}`));
|
|
1474
|
+
if (r.stderr)
|
|
1475
|
+
console.error(colors.dim(r.stderr.split('\n').slice(0, 5).join('\n')));
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1404
1478
|
/** Run npm install -g with retries for registry propagation delay */
|
|
1405
1479
|
function installGlobalWithRetry(pkgSpec, cwd, maxRetries = 3) {
|
|
1406
1480
|
let result = runCommand('npm', ['install', '-g', pkgSpec], { cwd, silent: false, showCommand: true });
|
|
@@ -4931,6 +5005,12 @@ export async function globalizeWorkspace(rootDir, options = {}, configOptions =
|
|
|
4931
5005
|
}
|
|
4932
5006
|
}
|
|
4933
5007
|
}
|
|
5008
|
+
// Sync workspace-root node_modules with member package.json files. Catches
|
|
5009
|
+
// the case where a dep was added to a member package.json but `npm install`
|
|
5010
|
+
// wasn't re-run — the builds would otherwise fail resolving the new dep.
|
|
5011
|
+
if (!options.dryRun) {
|
|
5012
|
+
ensureWorkspaceDepModules(rootDir, packages.map(p => ({ dir: p.dir, pkg: p.pkg })), !!options.verbose);
|
|
5013
|
+
}
|
|
4934
5014
|
// Prescan: decide which packages actually need processing so we don't waste
|
|
4935
5015
|
// time rebuilding+republishing ones with no relevant changes.
|
|
4936
5016
|
// A package is SKIPPED only if ALL of:
|