@bobfrankston/npmglobalize 1.0.207 → 1.0.209
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 +8 -0
- package/lib.js +176 -18
- package/package.json +1 -1
package/lib.d.ts
CHANGED
|
@@ -485,6 +485,14 @@ export declare function ensureWorkspaceDepModules(rootDir: string, members: Arra
|
|
|
485
485
|
* `npm error` lines minus stack frames and brace-only object-dump fragments,
|
|
486
486
|
* deduped. The full output belongs in a log file (saveNpmLog), not the terminal. */
|
|
487
487
|
export declare function distillNpmErrors(combined: string): string[];
|
|
488
|
+
/** True when a failed install is npm's "no such version" (ETARGET/E404) for the
|
|
489
|
+
* very spec we just published — registry propagation or a stale local
|
|
490
|
+
* packument, not a broken package. `pkgSpec` is `name@version`. */
|
|
491
|
+
export declare function isPropagationFailure(combined: string, pkgSpec: string): boolean;
|
|
492
|
+
/** What to tell the user when an install fails purely because npm cannot see a
|
|
493
|
+
* just-published version yet. Says what IS true (published, will install),
|
|
494
|
+
* what is NOT (nothing wrong with the package), and one command that works. */
|
|
495
|
+
export declare function propagationHelp(pkgSpec: string): string[];
|
|
488
496
|
/** Scan a puppeteer browser cache (layout <root>/<browser>/<platform-version>)
|
|
489
497
|
* and repair every version folder that has no browser executable in it — the
|
|
490
498
|
* residue of a truncated extraction (see extractBrowserArchive for the Node
|
package/lib.js
CHANGED
|
@@ -2128,13 +2128,30 @@ async function npmInstallWithCleanRetry(dir, verbose, context) {
|
|
|
2128
2128
|
* resolvable. We wait longer and re-probe `npm view` for the version
|
|
2129
2129
|
* string until it shows up (or we hit the cap). */
|
|
2130
2130
|
function waitForNpmVersion(pkgName, version, isNewPackage = false, maxWaitMs) {
|
|
2131
|
-
|
|
2132
|
-
|
|
2131
|
+
// 2026-09-01 10:15 EDT — Claude Code (Opus 5), at Bob's direction.
|
|
2132
|
+
// This loop was polling npm's LOCAL cache, not the registry. Registry
|
|
2133
|
+
// packuments carry `cache-control: public, max-age=300`, and npm's default
|
|
2134
|
+
// (prefer-online=false) serves a cached packument for that whole window
|
|
2135
|
+
// without revalidating — so once the first probe cached a packument that
|
|
2136
|
+
// predates the publish, every probe for the next 5 minutes returned the
|
|
2137
|
+
// same stale answer no matter what the registry actually held. Verified
|
|
2138
|
+
// 2026-09-01 on @bobfrankston/brother-label@1.1.22: the npm debug log for
|
|
2139
|
+
// the failing install shows `http cache … (cache hit)` — it never asked
|
|
2140
|
+
// the registry — while the cached entry's own resHeaders read
|
|
2141
|
+
// `cache-control: public, max-age=300`. `--prefer-online` forces the
|
|
2142
|
+
// staleness check (a conditional request), which is the only way this
|
|
2143
|
+
// poll can observe anything new.
|
|
2144
|
+
// Same run: publish at 13:52:48Z, packument `last-modified 13:57:59Z` —
|
|
2145
|
+
// real propagation took 5m11s, past the old 180s cap for an existing
|
|
2146
|
+
// package. Raised; with a probe that actually revalidates, an early
|
|
2147
|
+
// success still exits immediately, so the higher cap costs nothing.
|
|
2148
|
+
const effectiveMaxWait = maxWaitMs ?? (isNewPackage ? 600000 : 420000);
|
|
2149
|
+
const interval = 5000;
|
|
2133
2150
|
const maxAttempts = Math.ceil(effectiveMaxWait / interval);
|
|
2134
2151
|
const suffix = isNewPackage ? ' (new package, may take several minutes)' : '';
|
|
2135
2152
|
process.stdout.write(`${timestamp()} Waiting for ${pkgName}@${version} on npm registry${suffix}`);
|
|
2136
2153
|
for (let i = 0; i < maxAttempts; i++) {
|
|
2137
|
-
const result = spawnSafe('npm', ['view', `${pkgName}@${version}`, 'version'], {
|
|
2154
|
+
const result = spawnSafe('npm', ['view', '--prefer-online', `${pkgName}@${version}`, 'version'], {
|
|
2138
2155
|
shell: process.platform === 'win32',
|
|
2139
2156
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
2140
2157
|
encoding: 'utf-8'
|
|
@@ -2147,6 +2164,10 @@ function waitForNpmVersion(pkgName, version, isNewPackage = false, maxWaitMs) {
|
|
|
2147
2164
|
sleepSync(interval);
|
|
2148
2165
|
}
|
|
2149
2166
|
process.stdout.write(' timed out\n');
|
|
2167
|
+
// Timing out here is not a publish failure — the version is on npm, this
|
|
2168
|
+
// client just cannot see it yet. Say so, or the install error that follows
|
|
2169
|
+
// reads as though the package itself were broken.
|
|
2170
|
+
console.log(colors.yellow(` ${pkgName}@${version} was published but is still not visible to npm after ${Math.round(effectiveMaxWait / 1000)}s — trying the install anyway.`));
|
|
2150
2171
|
return false;
|
|
2151
2172
|
}
|
|
2152
2173
|
function cleanNestedDepModules(pkg, cwd, verbose) {
|
|
@@ -3729,6 +3750,38 @@ export function distillNpmErrors(combined) {
|
|
|
3729
3750
|
}
|
|
3730
3751
|
return key.slice(0, 12);
|
|
3731
3752
|
}
|
|
3753
|
+
// 2026-09-01 10:40 EDT — Claude Code (Opus 5), at Bob's direction: "it installed
|
|
3754
|
+
// correctly on a retry - need better error messages". What the run printed was
|
|
3755
|
+
// npm's own text — "No matching version found ... In most cases you or one of
|
|
3756
|
+
// your dependencies are requesting a package version that doesn't exist" —
|
|
3757
|
+
// which is flatly wrong here: WE published that exact version 4 seconds
|
|
3758
|
+
// earlier, and a manual retry a minute later installed it. The two helpers
|
|
3759
|
+
// below name the real situation instead.
|
|
3760
|
+
/** True when a failed install is npm's "no such version" (ETARGET/E404) for the
|
|
3761
|
+
* very spec we just published — registry propagation or a stale local
|
|
3762
|
+
* packument, not a broken package. `pkgSpec` is `name@version`. */
|
|
3763
|
+
export function isPropagationFailure(combined, pkgSpec) {
|
|
3764
|
+
if (pkgSpec === '.')
|
|
3765
|
+
return false; // local-directory install can't be a registry-visibility problem
|
|
3766
|
+
if (!/npm (?:error|ERR!)\s+code E(?:TARGET|404)\b/.test(combined))
|
|
3767
|
+
return false;
|
|
3768
|
+
const name = pkgSpec.replace(/@[^@/]+$/, ''); // strip the trailing @version, keep any @scope
|
|
3769
|
+
return combined.includes(pkgSpec) || combined.includes(name);
|
|
3770
|
+
}
|
|
3771
|
+
/** What to tell the user when an install fails purely because npm cannot see a
|
|
3772
|
+
* just-published version yet. Says what IS true (published, will install),
|
|
3773
|
+
* what is NOT (nothing wrong with the package), and one command that works. */
|
|
3774
|
+
export function propagationHelp(pkgSpec) {
|
|
3775
|
+
return [
|
|
3776
|
+
`${pkgSpec} is published — npm just can't see it from this machine yet.`,
|
|
3777
|
+
` The registry takes up to several minutes to propagate a new version, and npm`,
|
|
3778
|
+
` caches the package's version list for 5 minutes (cache-control: max-age=300),`,
|
|
3779
|
+
` so a retry inside that window can still miss it unless the cache is bypassed.`,
|
|
3780
|
+
` Nothing is wrong with the package or the publish; the global copy is simply the`,
|
|
3781
|
+
` previous version until you re-run:`,
|
|
3782
|
+
` npm install -g --prefer-online ${pkgSpec}`,
|
|
3783
|
+
];
|
|
3784
|
+
}
|
|
3732
3785
|
/** Write the full output of a failed npm command to a log file under the temp
|
|
3733
3786
|
* dir and return its path (null if the write itself fails). */
|
|
3734
3787
|
function saveNpmLog(label, content) {
|
|
@@ -4054,8 +4107,16 @@ async function installGlobalWithRetry(pkgSpec, cwd, isNewPackage = false, maxRet
|
|
|
4054
4107
|
// stays behind npm's allowScripts gate.
|
|
4055
4108
|
const allowArgs = allowOwnScriptsArgs(cwd);
|
|
4056
4109
|
while (true) {
|
|
4057
|
-
|
|
4058
|
-
|
|
4110
|
+
// 2026-09-01 10:15 EDT — Claude Code (Opus 5), at Bob's direction.
|
|
4111
|
+
// `--prefer-online`: this install always targets a version published
|
|
4112
|
+
// seconds ago, so a cached packument is exactly the hazard. Without it
|
|
4113
|
+
// all three ETARGET retries re-read the same stale local cache and are
|
|
4114
|
+
// guaranteed to fail identically — confirmed in npm's debug log for
|
|
4115
|
+
// brother-label@1.1.22 (`http cache … (cache hit)`, no registry
|
|
4116
|
+
// request at all). Costs one conditional request per packument.
|
|
4117
|
+
const online = pkgSpec === '.' ? [] : ['--prefer-online'];
|
|
4118
|
+
console.log(colors.cyan(`> npm install -g ${[...online, pkgSpec, ...allowArgs].join(' ')}`));
|
|
4119
|
+
result = await runCommandAsync('npm', ['install', '-g', ...online, pkgSpec, ...allowArgs], { cwd, silent: true });
|
|
4059
4120
|
const combined = `${result.output}\n${result.stderr}`;
|
|
4060
4121
|
fullLog += `===== attempt ${attempt + 1}: npm install -g ${pkgSpec} =====\n${combined}\n`;
|
|
4061
4122
|
if (result.success) {
|
|
@@ -4076,8 +4137,19 @@ async function installGlobalWithRetry(pkgSpec, cwd, isNewPackage = false, maxRet
|
|
|
4076
4137
|
const cleanupFails = (combined.match(/npm warn cleanup\s+\[Error: EPERM/g) || []).length;
|
|
4077
4138
|
if (cleanupFails)
|
|
4078
4139
|
console.log(colors.dim(` (npm couldn't remove ${cleanupFails} leftover dir(s) — EPERM cleanup warnings, details in log)`));
|
|
4079
|
-
|
|
4140
|
+
const propagating = isPropagationFailure(combined, pkgSpec);
|
|
4141
|
+
for (const line of distillNpmErrors(combined)) {
|
|
4142
|
+
// npm's stock follow-up to ETARGET blames the request ("you or one of
|
|
4143
|
+
// your dependencies are requesting a version that doesn't exist").
|
|
4144
|
+
// For a version we published seconds ago that sends the reader
|
|
4145
|
+
// hunting a nonexistent dependency bug — drop it and say the real
|
|
4146
|
+
// thing below.
|
|
4147
|
+
if (propagating && /In most cases you or one of your dependencies/.test(line))
|
|
4148
|
+
continue;
|
|
4080
4149
|
console.error(colors.red(` ${line}`));
|
|
4150
|
+
}
|
|
4151
|
+
if (propagating)
|
|
4152
|
+
console.error(colors.yellow(` ↻ ${pkgSpec} not visible to npm yet — registry propagation, not a bad package.`));
|
|
4081
4153
|
// Known-fixable failure: repair and retry immediately — the cause is
|
|
4082
4154
|
// local, so waiting for registry propagation is pointless. Puppeteer
|
|
4083
4155
|
// reports one corrupted browser per run, so allow a few repair rounds
|
|
@@ -4288,7 +4360,10 @@ async function waitForNpmVersionInWsl(spec, maxWaitMs = 180000) {
|
|
|
4288
4360
|
const maxAttempts = Math.ceil(maxWaitMs / interval);
|
|
4289
4361
|
process.stdout.write(`${timestamp()} Waiting for ${spec} on npm registry (WSL view)`);
|
|
4290
4362
|
for (let i = 0; i < maxAttempts; i++) {
|
|
4291
|
-
|
|
4363
|
+
// 2026-09-01 10:15 EDT — Claude Code: --prefer-online for the same
|
|
4364
|
+
// reason as waitForNpmVersion — WSL's npm has its own cache with the
|
|
4365
|
+
// same 300s packument freshness window to poll past.
|
|
4366
|
+
const r = await runCommandAsync('wsl', ['npm', 'view', '--prefer-online', spec, 'version'], { silent: true });
|
|
4292
4367
|
if (r.success && (r.output || '').trim()) {
|
|
4293
4368
|
process.stdout.write(' ready\n');
|
|
4294
4369
|
return true;
|
|
@@ -4299,6 +4374,56 @@ async function waitForNpmVersionInWsl(spec, maxWaitMs = 180000) {
|
|
|
4299
4374
|
process.stdout.write(' timed out\n');
|
|
4300
4375
|
return false;
|
|
4301
4376
|
}
|
|
4377
|
+
/** After a global WSL install, confirm a NON-interactive shell actually resolves
|
|
4378
|
+
* the binaries we just installed, rather than older copies of them.
|
|
4379
|
+
*
|
|
4380
|
+
* WSL's npm prefix is per-user (~/.npm-global), and that directory reaches PATH
|
|
4381
|
+
* only via ~/.bashrc — which returns immediately when the shell is not
|
|
4382
|
+
* interactive. Every `wsl <cmd>` is non-interactive, so it searches the default
|
|
4383
|
+
* PATH from /etc/environment instead, where a stale /usr/local/bin entry left by
|
|
4384
|
+
* an earlier root-prefix install keeps winning. The install genuinely succeeds
|
|
4385
|
+
* and the version reported by `wsl <tool>` is genuinely stale, which is exactly
|
|
4386
|
+
* why this went unnoticed for four months. Repair with a symlink rather than a
|
|
4387
|
+
* copy, so it tracks later installs on its own. */
|
|
4388
|
+
async function verifyWslGlobalBins(cwd) {
|
|
4389
|
+
let names = [];
|
|
4390
|
+
try {
|
|
4391
|
+
const pkg = readPackageJson(cwd);
|
|
4392
|
+
if (typeof pkg?.bin === 'string')
|
|
4393
|
+
names = [String(pkg.name || '').split('/').pop()].filter(Boolean);
|
|
4394
|
+
else if (pkg?.bin && typeof pkg.bin === 'object')
|
|
4395
|
+
names = Object.keys(pkg.bin);
|
|
4396
|
+
}
|
|
4397
|
+
catch {
|
|
4398
|
+
return;
|
|
4399
|
+
}
|
|
4400
|
+
if (!names.length)
|
|
4401
|
+
return;
|
|
4402
|
+
const prefixResult = await runCommandAsync('wsl', ['npm', 'config', 'get', 'prefix'], { silent: true });
|
|
4403
|
+
const prefix = (prefixResult.output || '').trim();
|
|
4404
|
+
if (!prefixResult.success || !prefix.startsWith('/'))
|
|
4405
|
+
return;
|
|
4406
|
+
for (const name of names) {
|
|
4407
|
+
const expected = `${prefix}/bin/${name}`;
|
|
4408
|
+
// `bash -c` is non-interactive — the same shape as a bare `wsl <cmd>`,
|
|
4409
|
+
// which is the case that breaks. A login shell would mask the problem.
|
|
4410
|
+
const lookup = await runCommandAsync('wsl', ['bash', '-c', `command -v ${name} || true`], { silent: true });
|
|
4411
|
+
const found = (lookup.output || '').trim();
|
|
4412
|
+
if (!found || found === expected)
|
|
4413
|
+
continue;
|
|
4414
|
+
console.log(colors.yellow(` WSL resolves ${name} to ${found}, not the copy just installed at ${expected}`));
|
|
4415
|
+
if (!found.startsWith('/usr/local/bin/')) {
|
|
4416
|
+
recordBuildIssue(name, 'warning', `WSL resolves ${name} to ${found} instead of ${expected}, so scripts run a different version than an interactive shell does. Left alone: only /usr/local/bin shadows are repaired automatically.`);
|
|
4417
|
+
continue;
|
|
4418
|
+
}
|
|
4419
|
+
const repair = await runCommandAsync('wsl', ['bash', '-c', `sudo -n rm -f ${found} && sudo -n ln -s ${expected} ${found}`], { silent: true });
|
|
4420
|
+
if (repair.success) {
|
|
4421
|
+
console.log(colors.green(` ✓ Repaired WSL shadow: ${found} -> ${expected}`));
|
|
4422
|
+
continue;
|
|
4423
|
+
}
|
|
4424
|
+
recordBuildIssue(name, 'warning', `WSL resolves ${name} to a stale ${found}. Repair with: wsl sudo rm ${found} && wsl sudo ln -s ${expected} ${found}`);
|
|
4425
|
+
}
|
|
4426
|
+
}
|
|
4302
4427
|
export async function installInWsl(wslArgs, opts = {}) {
|
|
4303
4428
|
// Same trust rule as the Windows installs: allow our own packages'
|
|
4304
4429
|
// install scripts, leave third-party ones gated. Probed against WSL's
|
|
@@ -4317,9 +4442,16 @@ export async function installInWsl(wslArgs, opts = {}) {
|
|
|
4317
4442
|
process.stderr.write(r.stderr);
|
|
4318
4443
|
return r;
|
|
4319
4444
|
};
|
|
4445
|
+
// Every success path leaves through here, so the shadow check cannot be
|
|
4446
|
+
// skipped by whichever retry happened to be the one that worked.
|
|
4447
|
+
const succeed = async (fixed) => {
|
|
4448
|
+
if (opts.cwd && wslArgs.includes('-g'))
|
|
4449
|
+
await verifyWslGlobalBins(opts.cwd);
|
|
4450
|
+
return { success: true, fixed };
|
|
4451
|
+
};
|
|
4320
4452
|
let result = await runOnce();
|
|
4321
4453
|
if (result.success)
|
|
4322
|
-
return
|
|
4454
|
+
return await succeed(false);
|
|
4323
4455
|
let combined = (result.output || '') + '\n' + (result.stderr || '');
|
|
4324
4456
|
// EACCES on a root-owned npm prefix → switch to a user prefix and retry.
|
|
4325
4457
|
if (/EACCES/.test(combined) && /\/usr\/(?:local\/)?lib\/node_modules/.test(combined)) {
|
|
@@ -4335,7 +4467,7 @@ export async function installInWsl(wslArgs, opts = {}) {
|
|
|
4335
4467
|
console.log(colors.green('✓ WSL npm prefix set to ~/.npm-global; PATH appended to ~/.bashrc'));
|
|
4336
4468
|
result = await runOnce();
|
|
4337
4469
|
if (result.success)
|
|
4338
|
-
return
|
|
4470
|
+
return await succeed(true);
|
|
4339
4471
|
combined = (result.output || '') + '\n' + (result.stderr || '');
|
|
4340
4472
|
}
|
|
4341
4473
|
// E404 on a scoped package has TWO causes, and npm gives the same error for
|
|
@@ -4352,7 +4484,7 @@ export async function installInWsl(wslArgs, opts = {}) {
|
|
|
4352
4484
|
console.log(colors.green('✓ Synced npm token to WSL'));
|
|
4353
4485
|
result = await runOnce();
|
|
4354
4486
|
if (result.success)
|
|
4355
|
-
return
|
|
4487
|
+
return await succeed(true);
|
|
4356
4488
|
}
|
|
4357
4489
|
else {
|
|
4358
4490
|
console.error(colors.yellow(' Could not authenticate WSL npm. Run `wsl npm login` (or sync your ~/.npmrc token) and retry.'));
|
|
@@ -4364,7 +4496,7 @@ export async function installInWsl(wslArgs, opts = {}) {
|
|
|
4364
4496
|
if (await waitForNpmVersionInWsl(spec)) {
|
|
4365
4497
|
result = await runOnce();
|
|
4366
4498
|
if (result.success)
|
|
4367
|
-
return
|
|
4499
|
+
return await succeed(true);
|
|
4368
4500
|
}
|
|
4369
4501
|
else {
|
|
4370
4502
|
console.error(colors.yellow(` ${spec} still not visible to WSL's npm after waiting — try the WSL install again shortly.`));
|
|
@@ -5759,7 +5891,9 @@ async function doLocalInstall(cwd, options) {
|
|
|
5759
5891
|
console.log(' [dry-run] Would run: wsl npm install -g .');
|
|
5760
5892
|
return true;
|
|
5761
5893
|
}
|
|
5762
|
-
|
|
5894
|
+
// 2026-09-01 10:55 EDT — Claude Code: see the note on the sibling local-install
|
|
5895
|
+
// path — captured, distilled output instead of raw npm chatter.
|
|
5896
|
+
const result = await installGlobalWithRetry('.', cwd, false, 1);
|
|
5763
5897
|
if (result.success) {
|
|
5764
5898
|
console.log(colors.green(`✓ Installed locally: ${pkgName}@${pkgVersion}`));
|
|
5765
5899
|
}
|
|
@@ -5875,7 +6009,16 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
|
|
|
5875
6009
|
console.log(' [dry-run] Would run: wsl npm install -g .');
|
|
5876
6010
|
return true;
|
|
5877
6011
|
}
|
|
5878
|
-
|
|
6012
|
+
// 2026-09-01 10:55 EDT — Claude Code (Opus 5), at Bob's direction. Was a
|
|
6013
|
+
// raw streamed `npm install -g .`, so the terminal got npm's chatter
|
|
6014
|
+
// verbatim — including `npm warn allow-scripts .npmrc allow-scripts
|
|
6015
|
+
// setting is being ignored because --allow-scripts was passed`, a
|
|
6016
|
+
// warning npmglobalize provokes on itself by passing that flag, and
|
|
6017
|
+
// which no reader can act on. installGlobalWithRetry captures the
|
|
6018
|
+
// output and prints only what matters (the added/changed/removed line,
|
|
6019
|
+
// or distilled errors plus a log file), which is what every other
|
|
6020
|
+
// install path here already does.
|
|
6021
|
+
const result = await installGlobalWithRetry('.', cwd, false, 1);
|
|
5879
6022
|
if (result.success) {
|
|
5880
6023
|
console.log(colors.green(`✓ Installed locally: ${pkgName}@${pkgVersion}`));
|
|
5881
6024
|
}
|
|
@@ -7186,8 +7329,15 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
|
|
|
7186
7329
|
console.error(colors.yellow(` Run 'npm login' to fix, then: npm install -g ${pkgName}@${pkgVersion}`));
|
|
7187
7330
|
}
|
|
7188
7331
|
else {
|
|
7189
|
-
|
|
7190
|
-
|
|
7332
|
+
const spec = `${pkgName}@${pkgVersion}`;
|
|
7333
|
+
if (isPropagationFailure(`${registryInstallResult.output}\n${registryInstallResult.stderr}`, spec)) {
|
|
7334
|
+
for (const line of propagationHelp(spec))
|
|
7335
|
+
console.error(colors.yellow(` ${line}`));
|
|
7336
|
+
}
|
|
7337
|
+
else {
|
|
7338
|
+
console.error(colors.red(`✗ Global install failed`));
|
|
7339
|
+
console.error(colors.yellow(` Try running manually: npm install -g --prefer-online ${spec}`));
|
|
7340
|
+
}
|
|
7191
7341
|
}
|
|
7192
7342
|
}
|
|
7193
7343
|
}
|
|
@@ -8031,9 +8181,17 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
|
|
|
8031
8181
|
recordBuildIssue(pkgName, 'warning', `Global install failed (${auth.error || 'auth issue'}) — run 'npm login'`);
|
|
8032
8182
|
}
|
|
8033
8183
|
else {
|
|
8034
|
-
|
|
8035
|
-
|
|
8036
|
-
|
|
8184
|
+
const spec = `${pkgName}@${pkgVersion}`;
|
|
8185
|
+
if (isPropagationFailure(`${installResult.output}\n${installResult.stderr}`, spec)) {
|
|
8186
|
+
for (const line of propagationHelp(spec))
|
|
8187
|
+
console.error(colors.yellow(` ${line}`));
|
|
8188
|
+
recordBuildIssue(pkgName, 'warning', `Published, but npm could not see ${spec} yet — install with: npm install -g --prefer-online ${spec}`);
|
|
8189
|
+
}
|
|
8190
|
+
else {
|
|
8191
|
+
console.error(colors.red(`✗ Global install failed`));
|
|
8192
|
+
console.error(colors.yellow(` Try running manually: npm install -g --prefer-online ${spec}`));
|
|
8193
|
+
recordBuildIssue(pkgName, 'warning', 'Global install failed');
|
|
8194
|
+
}
|
|
8037
8195
|
}
|
|
8038
8196
|
}
|
|
8039
8197
|
}
|