@bobfrankston/npmglobalize 1.0.194 → 1.0.195
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 -0
- package/cli.js +15 -6
- package/lib.d.ts +17 -1
- package/lib.js +177 -22
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -454,6 +454,19 @@ Workspace mode is auto-detected when run from a root with `"private": true` and
|
|
|
454
454
|
target. Fixes arborist "Cannot read properties of null"
|
|
455
455
|
crashes caused by sibling file: deps with nested
|
|
456
456
|
node_modules. Suggested automatically when the error hits.
|
|
457
|
+
-ts7-report, -deprecation-report
|
|
458
|
+
Report-only: scan this package and its file: deps for
|
|
459
|
+
compilerOptions removed in TypeScript 7 and list a migration
|
|
460
|
+
to-do. Writes nothing.
|
|
461
|
+
-tsfix, -ts7-fix
|
|
462
|
+
One-off utility, NOT part of the main flow: apply the TS7
|
|
463
|
+
tsconfig migration to a package (and its file: deps) and exit —
|
|
464
|
+
no build, no commit, no push, no publish. Intended as a
|
|
465
|
+
temporary tool for fixing a local/subdirectory tsconfig in
|
|
466
|
+
place. The normal release flow already applies the same
|
|
467
|
+
migration automatically when a build hits a TS7 deprecation
|
|
468
|
+
error, so day-to-day you never need this flag.
|
|
469
|
+
npmglobalize <path> -tsfix
|
|
457
470
|
-show Show package.json dependency changes
|
|
458
471
|
-package, -pkg Update package.json scripts to use npmglobalize (see below)
|
|
459
472
|
-h, -help Show help
|
package/cli.js
CHANGED
|
@@ -56,6 +56,9 @@ Dependency Options:
|
|
|
56
56
|
public visibility to already-published deps in the cascade.
|
|
57
57
|
Use when a public consumer's deps weren't all marked public.
|
|
58
58
|
-no-prescan, -nps Skip upfront dep-graph prescan
|
|
59
|
+
-force-build, -fb Rebuild file: deps even when their output looks up
|
|
60
|
+
to date (freshness check compares .ts/tsconfig
|
|
61
|
+
mtimes against emitted .js/.d.ts)
|
|
59
62
|
-force-publish Republish dependencies even if version exists
|
|
60
63
|
-fix Run npm audit fix after transformation
|
|
61
64
|
-no-use-paths, -nup Declare this package standalone — file: deps shouldn't
|
|
@@ -122,10 +125,12 @@ Other Options:
|
|
|
122
125
|
moduleResolution, target es3, deprecated flags). Lists a
|
|
123
126
|
migration to-do list and exits. Writes nothing.
|
|
124
127
|
-tsfix, -ts7-fix
|
|
125
|
-
|
|
126
|
-
and its file: deps, then exit.
|
|
127
|
-
no build, no commit, no push,
|
|
128
|
-
|
|
128
|
+
One-off utility, not the main flow: apply the TS7 tsconfig
|
|
129
|
+
migration to this package and its file: deps, then exit.
|
|
130
|
+
Only tsconfig files change — no build, no commit, no push,
|
|
131
|
+
no publish. The release flow auto-applies the same migration
|
|
132
|
+
on a TS7 build error; use this only for a temporary in-place
|
|
133
|
+
fix of a local directory: npmglobalize <path> -tsfix
|
|
129
134
|
-show Show package.json dependency changes
|
|
130
135
|
-package, -pkg Update package.json scripts to use npmglobalize
|
|
131
136
|
-h, -help Show this help
|
|
@@ -243,6 +248,10 @@ function parseArgs(args) {
|
|
|
243
248
|
case '-force':
|
|
244
249
|
options.force = true;
|
|
245
250
|
break;
|
|
251
|
+
case '-force-build':
|
|
252
|
+
case '-fb':
|
|
253
|
+
options.forceBuild = true;
|
|
254
|
+
break;
|
|
246
255
|
case '-files':
|
|
247
256
|
options.files = true;
|
|
248
257
|
options.explicitKeys.add('files');
|
|
@@ -555,14 +564,14 @@ export async function main() {
|
|
|
555
564
|
// whose source has changed since their last build.
|
|
556
565
|
if (!cliOptions.cleanup) {
|
|
557
566
|
await ensureFileDepModules(cwd, !!cliOptions.verbose);
|
|
558
|
-
const depsOk = await buildFileDepsTopologically(cwd, { verbose: !!cliOptions.verbose, force: !!cliOptions.force });
|
|
567
|
+
const depsOk = await buildFileDepsTopologically(cwd, { verbose: !!cliOptions.verbose, force: !!cliOptions.force, forceBuild: !!cliOptions.forceBuild });
|
|
559
568
|
if (!depsOk && !cliOptions.force) {
|
|
560
569
|
printBuildSummary();
|
|
561
570
|
process.exit(1);
|
|
562
571
|
}
|
|
563
572
|
if (!depsOk)
|
|
564
573
|
console.log(colors.warn('Continuing with --force despite dep build failure...'));
|
|
565
|
-
const targetOk = await buildProject(cwd, { verbose: !!cliOptions.verbose, force: !!cliOptions.force });
|
|
574
|
+
const targetOk = await buildProject(cwd, { verbose: !!cliOptions.verbose, force: !!cliOptions.force, forceBuild: !!cliOptions.forceBuild });
|
|
566
575
|
if (!targetOk) {
|
|
567
576
|
if (!cliOptions.force) {
|
|
568
577
|
printBuildSummary();
|
package/lib.d.ts
CHANGED
|
@@ -108,6 +108,8 @@ export interface GlobalizeOptions {
|
|
|
108
108
|
usePaths?: boolean;
|
|
109
109
|
/** Local install only — skip transform/publish, just npm install -g . */
|
|
110
110
|
local?: boolean;
|
|
111
|
+
/** Build every file: dep even when outputs look up to date (skips the freshness check) */
|
|
112
|
+
forceBuild?: boolean;
|
|
111
113
|
/** Freeze node_modules: replace symlinks/junctions with real copies for network share use */
|
|
112
114
|
freeze?: boolean;
|
|
113
115
|
/** Before `npm pack`, delete `node_modules/` inside each `file:` dep target.
|
|
@@ -311,13 +313,26 @@ export declare function missingDeps(pkgDir: string, pkg: any): string[];
|
|
|
311
313
|
* re-run" (partial-sync) cases. Also covers `cwd` itself on the first call.
|
|
312
314
|
* Cycle-safe via the shared `visited` set. */
|
|
313
315
|
export declare function ensureFileDepModules(cwd: string, verbose?: boolean, visited?: Set<string>): Promise<void>;
|
|
316
|
+
/** Cheap freshness check so the build cascade can skip packages whose output is
|
|
317
|
+
* already current. Returns true only when provably up to date:
|
|
318
|
+
* - outDir projects: newest source (.ts/.tsx/.mts/.cts + tsconfig*.json) must
|
|
319
|
+
* not be newer than the newest file under outDir.
|
|
320
|
+
* - side-by-side projects: every source must have an emitted sibling
|
|
321
|
+
* (.js/.jsx/.mjs/.cjs or declaration) at least as new.
|
|
322
|
+
* Conservative: project references, allowJs, missing outputs, or unreadable
|
|
323
|
+
* tsconfig all report stale (→ build). package.json mtime is deliberately
|
|
324
|
+
* ignored — npmglobalize itself rewrites it around every publish, which would
|
|
325
|
+
* otherwise force a rebuild on every run. */
|
|
326
|
+
export declare function isBuildUpToDate(cwd: string): boolean;
|
|
314
327
|
/** Build a single project: detect tsconfig, prompt to add `build: tsc` if a
|
|
315
328
|
* TypeScript project lacks a build script, run `npm run build`, record
|
|
316
329
|
* failures. Returns true if build succeeded (or was skipped because no
|
|
317
|
-
* tsconfig / noEmit / no build script after declining the prompt
|
|
330
|
+
* tsconfig / noEmit / no build script after declining the prompt / output
|
|
331
|
+
* already up to date). Pass `forceBuild` to skip the freshness check. */
|
|
318
332
|
export declare function buildProject(cwd: string, opts?: {
|
|
319
333
|
verbose?: boolean;
|
|
320
334
|
force?: boolean;
|
|
335
|
+
forceBuild?: boolean;
|
|
321
336
|
}): Promise<boolean>;
|
|
322
337
|
/** Walk `file:` deps depth-first (deps before consumers) and build each one
|
|
323
338
|
* that has a tsconfig. Mirrors `ensureFileDepModules`'s traversal but invokes
|
|
@@ -327,6 +342,7 @@ export declare function buildProject(cwd: string, opts?: {
|
|
|
327
342
|
export declare function buildFileDepsTopologically(cwd: string, opts?: {
|
|
328
343
|
verbose?: boolean;
|
|
329
344
|
force?: boolean;
|
|
345
|
+
forceBuild?: boolean;
|
|
330
346
|
}, visited?: Set<string>): Promise<boolean>;
|
|
331
347
|
/** A single TS7-readiness problem found in a package's effective tsconfig. */
|
|
332
348
|
export interface Ts7Finding {
|
package/lib.js
CHANGED
|
@@ -1720,6 +1720,41 @@ export function printPnpmSuggestionSummary() {
|
|
|
1720
1720
|
console.error(l);
|
|
1721
1721
|
console.error('');
|
|
1722
1722
|
}
|
|
1723
|
+
/** Run `npm install` in `dir`; on failure, offer to delete `node_modules/`
|
|
1724
|
+
* and `package-lock.json` and retry once — the standard fix for arborist's
|
|
1725
|
+
* null-`package` crash on stale lockfile / junction state — before falling
|
|
1726
|
+
* back to the pnpm suggestion banner. Returns true if install succeeded.
|
|
1727
|
+
* `rmSync` unlinks junctions without descending, so file: sibling targets
|
|
1728
|
+
* are never touched. */
|
|
1729
|
+
async function npmInstallWithCleanRetry(dir, verbose, context) {
|
|
1730
|
+
let r = await runCommandAsync('npm', ['install'], { cwd: dir, silent: !verbose });
|
|
1731
|
+
if (r.success)
|
|
1732
|
+
return true;
|
|
1733
|
+
console.error(colors.red(` ✗ npm install failed in ${dir}`));
|
|
1734
|
+
if (r.stderr)
|
|
1735
|
+
console.error(colors.dim(r.stderr.split('\n').slice(0, 5).join('\n')));
|
|
1736
|
+
if (await confirm(` Delete node_modules/ and package-lock.json in ${dir} and retry npm install?`, true)) {
|
|
1737
|
+
try {
|
|
1738
|
+
fs.rmSync(path.join(dir, 'node_modules'), { recursive: true, force: true });
|
|
1739
|
+
}
|
|
1740
|
+
catch { /* best-effort */ }
|
|
1741
|
+
try {
|
|
1742
|
+
fs.rmSync(path.join(dir, 'package-lock.json'), { force: true });
|
|
1743
|
+
}
|
|
1744
|
+
catch { /* best-effort */ }
|
|
1745
|
+
console.log(colors.yellow(`↻ clean reinstall in ${dir}`));
|
|
1746
|
+
r = await runCommandAsync('npm', ['install'], { cwd: dir, silent: !verbose });
|
|
1747
|
+
if (r.success) {
|
|
1748
|
+
console.log(colors.green(` ✓ clean reinstall succeeded in ${dir}`));
|
|
1749
|
+
return true;
|
|
1750
|
+
}
|
|
1751
|
+
console.error(colors.red(` ✗ npm install still failing in ${dir} after clean reinstall`));
|
|
1752
|
+
if (r.stderr)
|
|
1753
|
+
console.error(colors.dim(r.stderr.split('\n').slice(0, 5).join('\n')));
|
|
1754
|
+
}
|
|
1755
|
+
await suggestPnpmOnInstallFailure(context);
|
|
1756
|
+
return false;
|
|
1757
|
+
}
|
|
1723
1758
|
/** Wait for a package version to appear on the npm registry.
|
|
1724
1759
|
* First-time publishes (brand-new package name) take much longer to
|
|
1725
1760
|
* propagate than version bumps — npm has no cached metadata to update,
|
|
@@ -1877,13 +1912,7 @@ export async function ensureFileDepModules(cwd, verbose = false, visited = new S
|
|
|
1877
1912
|
catch { /* best-effort */ }
|
|
1878
1913
|
}
|
|
1879
1914
|
console.log(colors.yellow(`↻ installing node_modules in ${pkg?.name || abs} (${formatMissingReason(cwdMissing)})`));
|
|
1880
|
-
|
|
1881
|
-
if (!r.success) {
|
|
1882
|
-
console.error(colors.red(` ✗ npm install failed in ${abs}`));
|
|
1883
|
-
if (r.stderr)
|
|
1884
|
-
console.error(colors.dim(r.stderr.split('\n').slice(0, 5).join('\n')));
|
|
1885
|
-
await suggestPnpmOnInstallFailure(`ensureFileDepModules (cwd): ${abs}`);
|
|
1886
|
-
}
|
|
1915
|
+
await npmInstallWithCleanRetry(abs, verbose, `ensureFileDepModules (cwd): ${abs}`);
|
|
1887
1916
|
}
|
|
1888
1917
|
for (const key of ['dependencies', 'devDependencies']) {
|
|
1889
1918
|
const deps = pkg?.[key];
|
|
@@ -1915,13 +1944,7 @@ export async function ensureFileDepModules(cwd, verbose = false, visited = new S
|
|
|
1915
1944
|
catch { /* best-effort */ }
|
|
1916
1945
|
}
|
|
1917
1946
|
console.log(colors.yellow(`↻ restoring node_modules in ${name} (${target}) (${formatMissingReason(targetMissing)})`));
|
|
1918
|
-
|
|
1919
|
-
if (!r.success) {
|
|
1920
|
-
console.error(colors.red(` ✗ npm install failed in ${target}`));
|
|
1921
|
-
if (r.stderr)
|
|
1922
|
-
console.error(colors.dim(r.stderr.split('\n').slice(0, 5).join('\n')));
|
|
1923
|
-
await suggestPnpmOnInstallFailure(`ensureFileDepModules (file: dep ${name}): ${target}`);
|
|
1924
|
-
}
|
|
1947
|
+
await npmInstallWithCleanRetry(target, verbose, `ensureFileDepModules (file: dep ${name}): ${target}`);
|
|
1925
1948
|
}
|
|
1926
1949
|
await ensureFileDepModules(target, verbose, visited);
|
|
1927
1950
|
}
|
|
@@ -2244,10 +2267,144 @@ function migrateTsconfigDeprecations(cwd) {
|
|
|
2244
2267
|
}
|
|
2245
2268
|
return changed;
|
|
2246
2269
|
}
|
|
2270
|
+
/** Newest mtime of any regular file under `dir` (recursive). Symlinks/junctions
|
|
2271
|
+
* are not followed — the file: dep webs here link back into sibling checkouts. */
|
|
2272
|
+
function newestMtimeUnder(dir) {
|
|
2273
|
+
let newest = 0;
|
|
2274
|
+
let entries;
|
|
2275
|
+
try {
|
|
2276
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
2277
|
+
}
|
|
2278
|
+
catch {
|
|
2279
|
+
return 0;
|
|
2280
|
+
}
|
|
2281
|
+
for (const e of entries) {
|
|
2282
|
+
if (e.isSymbolicLink())
|
|
2283
|
+
continue;
|
|
2284
|
+
const full = path.join(dir, e.name);
|
|
2285
|
+
if (e.isDirectory()) {
|
|
2286
|
+
newest = Math.max(newest, newestMtimeUnder(full));
|
|
2287
|
+
}
|
|
2288
|
+
else if (e.isFile()) {
|
|
2289
|
+
try {
|
|
2290
|
+
newest = Math.max(newest, fs.statSync(full).mtimeMs);
|
|
2291
|
+
}
|
|
2292
|
+
catch { /* ignore */ }
|
|
2293
|
+
}
|
|
2294
|
+
}
|
|
2295
|
+
return newest;
|
|
2296
|
+
}
|
|
2297
|
+
/** Cheap freshness check so the build cascade can skip packages whose output is
|
|
2298
|
+
* already current. Returns true only when provably up to date:
|
|
2299
|
+
* - outDir projects: newest source (.ts/.tsx/.mts/.cts + tsconfig*.json) must
|
|
2300
|
+
* not be newer than the newest file under outDir.
|
|
2301
|
+
* - side-by-side projects: every source must have an emitted sibling
|
|
2302
|
+
* (.js/.jsx/.mjs/.cjs or declaration) at least as new.
|
|
2303
|
+
* Conservative: project references, allowJs, missing outputs, or unreadable
|
|
2304
|
+
* tsconfig all report stale (→ build). package.json mtime is deliberately
|
|
2305
|
+
* ignored — npmglobalize itself rewrites it around every publish, which would
|
|
2306
|
+
* otherwise force a rebuild on every run. */
|
|
2307
|
+
export function isBuildUpToDate(cwd) {
|
|
2308
|
+
let tsconfig;
|
|
2309
|
+
try {
|
|
2310
|
+
tsconfig = JSON5.parse(fs.readFileSync(path.join(cwd, 'tsconfig.json'), 'utf-8'));
|
|
2311
|
+
}
|
|
2312
|
+
catch {
|
|
2313
|
+
return false;
|
|
2314
|
+
}
|
|
2315
|
+
const co = tsconfig.compilerOptions || {};
|
|
2316
|
+
if (tsconfig.references || co.allowJs || co.checkJs)
|
|
2317
|
+
return false;
|
|
2318
|
+
const outDir = typeof co.outDir === 'string' ? path.resolve(cwd, co.outDir) : null;
|
|
2319
|
+
// tsconfig "exclude" dirs aren't compiled, so their .ts files never get
|
|
2320
|
+
// outputs — don't count them as sources. Match on the literal path prefix
|
|
2321
|
+
// (glob tails like "tests/**" reduce to "tests").
|
|
2322
|
+
const excludes = (Array.isArray(tsconfig.exclude) ? tsconfig.exclude : [])
|
|
2323
|
+
.filter((x) => typeof x === 'string')
|
|
2324
|
+
.map((x) => x.replace(/^\.\//, '').replace(/[\\/]?\*.*$/, '').replace(/[\\/]+$/, '').toLowerCase())
|
|
2325
|
+
.filter((x) => x.length > 0 && !x.includes('*'));
|
|
2326
|
+
const sources = [];
|
|
2327
|
+
const collect = (dir, rel) => {
|
|
2328
|
+
let entries;
|
|
2329
|
+
try {
|
|
2330
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
2331
|
+
}
|
|
2332
|
+
catch {
|
|
2333
|
+
return;
|
|
2334
|
+
}
|
|
2335
|
+
for (const e of entries) {
|
|
2336
|
+
if (e.isSymbolicLink())
|
|
2337
|
+
continue;
|
|
2338
|
+
const full = path.join(dir, e.name);
|
|
2339
|
+
const relPath = rel ? `${rel}/${e.name.toLowerCase()}` : e.name.toLowerCase();
|
|
2340
|
+
if (e.isDirectory()) {
|
|
2341
|
+
const lower = e.name.toLowerCase();
|
|
2342
|
+
if (lower === 'node_modules' || lower === 'prev' || e.name.startsWith('.'))
|
|
2343
|
+
continue;
|
|
2344
|
+
if (outDir && path.resolve(full) === outDir)
|
|
2345
|
+
continue;
|
|
2346
|
+
if (excludes.includes(relPath))
|
|
2347
|
+
continue;
|
|
2348
|
+
collect(full, relPath);
|
|
2349
|
+
}
|
|
2350
|
+
else if (/\.(ts|tsx|mts|cts)$/.test(e.name) && !/\.d\.(ts|mts|cts)$/.test(e.name)) {
|
|
2351
|
+
if (excludes.includes(relPath))
|
|
2352
|
+
continue;
|
|
2353
|
+
sources.push(full);
|
|
2354
|
+
}
|
|
2355
|
+
}
|
|
2356
|
+
};
|
|
2357
|
+
collect(cwd, '');
|
|
2358
|
+
if (sources.length === 0)
|
|
2359
|
+
return true;
|
|
2360
|
+
const mtime = (p) => {
|
|
2361
|
+
try {
|
|
2362
|
+
return fs.statSync(p).mtimeMs;
|
|
2363
|
+
}
|
|
2364
|
+
catch {
|
|
2365
|
+
return -1;
|
|
2366
|
+
}
|
|
2367
|
+
};
|
|
2368
|
+
// Any tsconfig*.json edit (own or extended base in the package) invalidates the build.
|
|
2369
|
+
let configTime = 0;
|
|
2370
|
+
try {
|
|
2371
|
+
for (const name of fs.readdirSync(cwd)) {
|
|
2372
|
+
if (/^tsconfig.*\.json$/.test(name))
|
|
2373
|
+
configTime = Math.max(configTime, mtime(path.join(cwd, name)));
|
|
2374
|
+
}
|
|
2375
|
+
}
|
|
2376
|
+
catch { /* ignore */ }
|
|
2377
|
+
if (outDir) {
|
|
2378
|
+
const newestOut = newestMtimeUnder(outDir);
|
|
2379
|
+
if (newestOut === 0)
|
|
2380
|
+
return false;
|
|
2381
|
+
const newestSrc = Math.max(configTime, ...sources.map(mtime));
|
|
2382
|
+
return newestSrc <= newestOut;
|
|
2383
|
+
}
|
|
2384
|
+
// Side-by-side emit: each source needs an output sibling at least as new.
|
|
2385
|
+
let newestOut = 0;
|
|
2386
|
+
for (const src of sources) {
|
|
2387
|
+
const m = src.match(/\.(ts|tsx|mts|cts)$/);
|
|
2388
|
+
const base = src.slice(0, -m[0].length);
|
|
2389
|
+
const candidates = m[1] === 'mts' ? ['.mjs', '.d.mts']
|
|
2390
|
+
: m[1] === 'cts' ? ['.cjs', '.d.cts']
|
|
2391
|
+
: ['.js', '.jsx', '.d.ts'];
|
|
2392
|
+
let outTime = -1;
|
|
2393
|
+
for (const ext of candidates)
|
|
2394
|
+
outTime = Math.max(outTime, mtime(base + ext));
|
|
2395
|
+
if (outTime < 0)
|
|
2396
|
+
return false;
|
|
2397
|
+
if (mtime(src) > outTime)
|
|
2398
|
+
return false;
|
|
2399
|
+
newestOut = Math.max(newestOut, outTime);
|
|
2400
|
+
}
|
|
2401
|
+
return configTime <= newestOut;
|
|
2402
|
+
}
|
|
2247
2403
|
/** Build a single project: detect tsconfig, prompt to add `build: tsc` if a
|
|
2248
2404
|
* TypeScript project lacks a build script, run `npm run build`, record
|
|
2249
2405
|
* failures. Returns true if build succeeded (or was skipped because no
|
|
2250
|
-
* tsconfig / noEmit / no build script after declining the prompt
|
|
2406
|
+
* tsconfig / noEmit / no build script after declining the prompt / output
|
|
2407
|
+
* already up to date). Pass `forceBuild` to skip the freshness check. */
|
|
2251
2408
|
export async function buildProject(cwd, opts = {}) {
|
|
2252
2409
|
let shouldBuild = false;
|
|
2253
2410
|
try {
|
|
@@ -2277,6 +2434,10 @@ export async function buildProject(cwd, opts = {}) {
|
|
|
2277
2434
|
}
|
|
2278
2435
|
}
|
|
2279
2436
|
ensureTsconfigNodeTypes(cwd);
|
|
2437
|
+
if (!opts.forceBuild && isBuildUpToDate(cwd)) {
|
|
2438
|
+
console.log(colors.dim(`– Build up to date (${pkg.name || path.basename(cwd)})`));
|
|
2439
|
+
return true;
|
|
2440
|
+
}
|
|
2280
2441
|
console.log(`Building ${pkg.name || cwd}...`);
|
|
2281
2442
|
let buildResult = await runCommandAsync('npm', ['run', 'build'], { cwd, silent: true });
|
|
2282
2443
|
if (!buildResult.success) {
|
|
@@ -2549,13 +2710,7 @@ export async function ensureWorkspaceDepModules(rootDir, members, verbose = fals
|
|
|
2549
2710
|
return;
|
|
2550
2711
|
const list = [...allMissing];
|
|
2551
2712
|
console.log(colors.yellow(`↻ installing workspace node_modules in ${rootPkg?.name || path.basename(root)} (${formatMissingReason(list)})`));
|
|
2552
|
-
|
|
2553
|
-
if (!r.success) {
|
|
2554
|
-
console.error(colors.red(` ✗ npm install failed in ${root}`));
|
|
2555
|
-
if (r.stderr)
|
|
2556
|
-
console.error(colors.dim(r.stderr.split('\n').slice(0, 5).join('\n')));
|
|
2557
|
-
await suggestPnpmOnInstallFailure(`ensureWorkspaceDepModules: ${root}`);
|
|
2558
|
-
}
|
|
2713
|
+
await npmInstallWithCleanRetry(root, verbose, `ensureWorkspaceDepModules: ${root}`);
|
|
2559
2714
|
}
|
|
2560
2715
|
/** Extract the lines a human needs from a failed npm command's output: the
|
|
2561
2716
|
* `npm error` lines minus stack frames and brace-only object-dump fragments,
|