@indigoai-us/hq-cli 5.77.7 → 5.77.9
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/CHANGELOG.md +27 -0
- package/dist/commands/group-grants.d.ts +1 -1
- package/dist/commands/group-grants.js +6 -6
- package/dist/commands/secrets.js +2 -1
- package/dist/lib/plan-limit-nag.d.ts +45 -0
- package/dist/lib/plan-limit-nag.js +212 -0
- package/dist/main.js +4 -0
- package/dist/utils/vault-api.js +62 -1
- package/dist/utils/version-gate.d.ts +97 -1
- package/dist/utils/version-gate.js +211 -32
- package/package.json +1 -1
- package/src/commands/group-grants.test.ts +41 -2
- package/src/commands/group-grants.ts +11 -8
- package/src/commands/reindex.test.ts +1 -1
- package/src/commands/secrets.test.ts +40 -0
- package/src/commands/secrets.ts +11 -2
- package/src/lib/plan-limit-nag.test.ts +317 -0
- package/src/lib/plan-limit-nag.ts +264 -0
- package/src/main.ts +4 -0
- package/src/utils/vault-api.test.ts +139 -0
- package/src/utils/vault-api.ts +61 -1
- package/src/utils/version-gate.test.ts +415 -6
- package/src/utils/version-gate.ts +259 -33
|
@@ -41,6 +41,9 @@ const ENDPOINT_PATH = "/v1/client-version/check";
|
|
|
41
41
|
const FETCH_TIMEOUT_MS = 3_000;
|
|
42
42
|
const LATEST_PACKAGE_SPEC = `${CLI_NAME}@latest`;
|
|
43
43
|
|
|
44
|
+
/** Which package manager owns the running global install. */
|
|
45
|
+
export type InstallManager = "npm" | "pnpm";
|
|
46
|
+
|
|
44
47
|
interface VersionCheckResponse {
|
|
45
48
|
clientId: string;
|
|
46
49
|
currentVersion: string;
|
|
@@ -90,20 +93,124 @@ export function npmPrefixFromPackageDir(pkgDir: string): string | null {
|
|
|
90
93
|
return prefix || null;
|
|
91
94
|
}
|
|
92
95
|
|
|
93
|
-
|
|
96
|
+
/** `$PNPM_HOME/global`, normalised to forward slashes, when PNPM_HOME is set. */
|
|
97
|
+
function pnpmHomeGlobalRoot(): string | null {
|
|
98
|
+
const home = process.env.PNPM_HOME;
|
|
99
|
+
if (!home) return null;
|
|
100
|
+
const normalized = home.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
101
|
+
return normalized ? `${normalized}/global` : null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Whether the running package lives inside a pnpm-managed **global** install.
|
|
106
|
+
*
|
|
107
|
+
* pnpm does not use npm's `<prefix>/lib/node_modules` layout. A global
|
|
108
|
+
* `pnpm add -g` puts the package in a versioned content store under the
|
|
109
|
+
* `global/<store-layout-version>` root and exposes it through a generated shim
|
|
110
|
+
* on PATH:
|
|
111
|
+
*
|
|
112
|
+
* $PNPM_HOME/hq <- shim on PATH
|
|
113
|
+
* $PNPM_HOME/global/5/node_modules/@indigoai-us/hq-cli <- symlink
|
|
114
|
+
* $PNPM_HOME/global/5/.pnpm/@indigoai-us+hq-cli@5.61.0/node_modules/…
|
|
115
|
+
*
|
|
116
|
+
* Both the symlinked and the resolved (`.pnpm`) form are recognised, because
|
|
117
|
+
* whether `import.meta.url` reports the link or its target depends on how node
|
|
118
|
+
* resolved the entrypoint.
|
|
119
|
+
*
|
|
120
|
+
* A bare `.pnpm` segment is deliberately NOT enough. It also appears in a local
|
|
121
|
+
* project dependency (`<proj>/node_modules/.pnpm/@indigoai-us+hq-cli@…`) and in
|
|
122
|
+
* a `pnpm dlx` cache. Neither of those is what `pnpm add -g` updates, so
|
|
123
|
+
* treating them as global would mutate the user's global install as a side
|
|
124
|
+
* effect of a local invocation while the copy actually running stayed stale —
|
|
125
|
+
* i.e. the gate would re-fire and re-install globally on every subsequent run.
|
|
126
|
+
* Those layouts fall through to the ordinary npm-prefix/manual handling.
|
|
127
|
+
*/
|
|
128
|
+
export function isPnpmManagedPackageDir(pkgDir: string): boolean {
|
|
129
|
+
const normalized = pkgDir.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
130
|
+
const segments = normalized.split("/").filter(Boolean);
|
|
131
|
+
|
|
132
|
+
// `global/<digits>` — pnpm's global root, in either the symlinked or the
|
|
133
|
+
// resolved (`.pnpm`) form, and independent of where PNPM_HOME points.
|
|
134
|
+
for (let i = 0; i < segments.length - 1; i += 1) {
|
|
135
|
+
if (segments[i] === "global" && /^\d+$/.test(segments[i + 1]!)) return true;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Belt-and-braces for a future pnpm layout whose store dir is not numeric:
|
|
139
|
+
// anything under `$PNPM_HOME/global` is global by construction. Still keyed
|
|
140
|
+
// on the `global` segment so `$PNPM_HOME/store/**` (dlx caches) stays out.
|
|
141
|
+
const globalRoot = pnpmHomeGlobalRoot();
|
|
142
|
+
if (globalRoot) {
|
|
143
|
+
if (normalized === globalRoot || normalized.startsWith(`${globalRoot}/`)) {
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Where the running CLI is installed and who owns it. Resolved in ONE pass so
|
|
153
|
+
* the package-root walk (which reads and parses a `package.json` per directory
|
|
154
|
+
* level) happens once per invocation, and so the manager and the prefix can
|
|
155
|
+
* never disagree because the filesystem shifted between two separate walks.
|
|
156
|
+
*
|
|
157
|
+
* `prefix` is `null` for a pnpm-managed install even though a prefix-shaped
|
|
158
|
+
* string *can* be derived from those paths: `npmPrefixFromPackageDir` would
|
|
159
|
+
* happily hand back the pnpm store directory, and `npm install -g --prefix
|
|
160
|
+
* <store>` then unpacks a fresh copy into `<store>/lib/node_modules`, which
|
|
161
|
+
* pnpm's shim never reads. npm exits 0, the gate reports success, and the next
|
|
162
|
+
* `hq` invocation still runs the old version — the reported loop where a stale
|
|
163
|
+
* pnpm shim kept resolving 5.61.0 after every "successful" update.
|
|
164
|
+
*
|
|
165
|
+
* Defaults to npm with no prefix when the layout can't be determined — that is
|
|
166
|
+
* the historical behaviour and the common case.
|
|
167
|
+
*/
|
|
168
|
+
export interface RunningInstall {
|
|
169
|
+
manager: InstallManager;
|
|
170
|
+
prefix: string | null;
|
|
171
|
+
packageRoot: string | null;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function resolveRunningInstall(): RunningInstall {
|
|
94
175
|
try {
|
|
95
|
-
const
|
|
96
|
-
if (!
|
|
97
|
-
|
|
176
|
+
const packageRoot = findRunningPackageRoot();
|
|
177
|
+
if (!packageRoot) return { manager: "npm", prefix: null, packageRoot: null };
|
|
178
|
+
if (isPnpmManagedPackageDir(packageRoot)) {
|
|
179
|
+
return { manager: "pnpm", prefix: null, packageRoot };
|
|
180
|
+
}
|
|
181
|
+
return {
|
|
182
|
+
manager: "npm",
|
|
183
|
+
prefix: npmPrefixFromPackageDir(packageRoot),
|
|
184
|
+
packageRoot,
|
|
185
|
+
};
|
|
98
186
|
} catch {
|
|
99
|
-
return null;
|
|
187
|
+
return { manager: "npm", prefix: null, packageRoot: null };
|
|
100
188
|
}
|
|
101
189
|
}
|
|
102
190
|
|
|
191
|
+
/** Convenience view of {@link resolveRunningInstall} for callers needing one field. */
|
|
192
|
+
export function resolveRunningManager(): InstallManager {
|
|
193
|
+
return resolveRunningInstall().manager;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** Convenience view of {@link resolveRunningInstall} for callers needing one field. */
|
|
197
|
+
export function resolveRunningPrefix(): string | null {
|
|
198
|
+
return resolveRunningInstall().prefix;
|
|
199
|
+
}
|
|
200
|
+
|
|
103
201
|
export function buildPrefixedInstallArgv(prefix: string): string[] {
|
|
104
202
|
return ["install", "-g", "--prefix", prefix, LATEST_PACKAGE_SPEC];
|
|
105
203
|
}
|
|
106
204
|
|
|
205
|
+
/**
|
|
206
|
+
* Argv for updating a pnpm-managed global install. `pnpm add -g` rewrites the
|
|
207
|
+
* PATH shim as part of the install, so the next invocation genuinely resolves
|
|
208
|
+
* the new version — which is the whole point of routing here instead of npm.
|
|
209
|
+
*/
|
|
210
|
+
export function buildPnpmInstallArgv(): string[] {
|
|
211
|
+
return ["add", "-g", LATEST_PACKAGE_SPEC];
|
|
212
|
+
}
|
|
213
|
+
|
|
107
214
|
/**
|
|
108
215
|
* Filesystem surface used by {@link cleanStalePartialInstall}. Injected so the
|
|
109
216
|
* cleanup logic is unit-testable without touching a real global prefix.
|
|
@@ -249,12 +356,50 @@ async function fetchVersionDecision(): Promise<VersionCheckResponse | null> {
|
|
|
249
356
|
* forcing a re-invocation would run twice on the same process and feel
|
|
250
357
|
* janky; instead we print a clear "rerun your command" message and exit.
|
|
251
358
|
*/
|
|
252
|
-
type UpdateResult = { ok: boolean; detail?: string };
|
|
359
|
+
type UpdateResult = { ok: boolean; detail?: string; code?: string };
|
|
253
360
|
type UpdateRunner = (cmd: string, args: string[]) => UpdateResult;
|
|
254
361
|
|
|
362
|
+
/**
|
|
363
|
+
* Quote an argv entry for a Windows `cmd.exe` invocation. Needed because Node
|
|
364
|
+
* does NOT quote argv when spawning with `shell: true` on Windows — it joins
|
|
365
|
+
* the array with spaces — so an npm prefix like `C:\Program Files\…` would be
|
|
366
|
+
* split into two arguments.
|
|
367
|
+
*/
|
|
368
|
+
export function quoteForWindowsShell(arg: string): string {
|
|
369
|
+
if (arg === "") return '""';
|
|
370
|
+
if (!/[\s"^&|<>()]/.test(arg)) return arg;
|
|
371
|
+
return `"${arg.replace(/"/g, '\\"')}"`;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* How to hand `<cmd> <args…>` to `spawnSync` on this platform.
|
|
376
|
+
*
|
|
377
|
+
* On Windows both `npm` and `pnpm` are `.cmd` shims, and since the
|
|
378
|
+
* CVE-2024-27980 hardening Node refuses to spawn a `.cmd`/`.bat` file without
|
|
379
|
+
* a shell. Without this the update would fail with EINVAL/ENOENT on every
|
|
380
|
+
* Windows install — including the pnpm layouts this gate claims to detect.
|
|
381
|
+
*/
|
|
382
|
+
export function buildSpawnPlan(
|
|
383
|
+
cmd: string,
|
|
384
|
+
args: readonly string[],
|
|
385
|
+
platform: NodeJS.Platform = process.platform,
|
|
386
|
+
): { cmd: string; args: string[]; shell: boolean } {
|
|
387
|
+
if (platform !== "win32") return { cmd, args: [...args], shell: false };
|
|
388
|
+
return { cmd, args: args.map(quoteForWindowsShell), shell: true };
|
|
389
|
+
}
|
|
390
|
+
|
|
255
391
|
function runUpdateCommand(cmd: string, args: string[]): UpdateResult {
|
|
256
392
|
try {
|
|
257
|
-
const
|
|
393
|
+
const plan = buildSpawnPlan(cmd, args);
|
|
394
|
+
const result = spawnSync(plan.cmd, plan.args, {
|
|
395
|
+
stdio: "inherit",
|
|
396
|
+
shell: plan.shell,
|
|
397
|
+
});
|
|
398
|
+
// spawnSync reports a missing executable via `error`, not a throw.
|
|
399
|
+
if (result.error) {
|
|
400
|
+
const code = (result.error as NodeJS.ErrnoException).code;
|
|
401
|
+
return { ok: false, code, detail: result.error.message };
|
|
402
|
+
}
|
|
258
403
|
if (result.status !== 0) {
|
|
259
404
|
return {
|
|
260
405
|
ok: false,
|
|
@@ -263,7 +408,11 @@ function runUpdateCommand(cmd: string, args: string[]): UpdateResult {
|
|
|
263
408
|
}
|
|
264
409
|
return { ok: true };
|
|
265
410
|
} catch (err) {
|
|
266
|
-
return {
|
|
411
|
+
return {
|
|
412
|
+
ok: false,
|
|
413
|
+
code: (err as NodeJS.ErrnoException | undefined)?.code,
|
|
414
|
+
detail: err instanceof Error ? err.message : String(err),
|
|
415
|
+
};
|
|
267
416
|
}
|
|
268
417
|
}
|
|
269
418
|
|
|
@@ -286,17 +435,46 @@ function performUpdate(
|
|
|
286
435
|
return performUpdateCommand(cmd, args, runner);
|
|
287
436
|
}
|
|
288
437
|
|
|
438
|
+
/**
|
|
439
|
+
* The command a user should run by hand for this install layout. hq-pro's
|
|
440
|
+
* `updateCommand` is npm-shaped for every client, so a pnpm-managed install
|
|
441
|
+
* must never be told to run it: `npm install -g …` drops a fresh copy under the
|
|
442
|
+
* npm global prefix while pnpm's PATH shim keeps resolving the old build. That
|
|
443
|
+
* is verbatim the reported symptom (a stale shim still reporting 5.61.0 after a
|
|
444
|
+
* "successful" update), so both the hard gate and the soft nudge below have to
|
|
445
|
+
* speak the running manager's language.
|
|
446
|
+
*/
|
|
447
|
+
function manualUpdateCommand(
|
|
448
|
+
install: RunningInstall,
|
|
449
|
+
decision: VersionCheckResponse,
|
|
450
|
+
): string | undefined {
|
|
451
|
+
if (install.manager === "pnpm") return `pnpm ${buildPnpmInstallArgv().join(" ")}`;
|
|
452
|
+
if (install.prefix) return `npm ${buildPrefixedInstallArgv(install.prefix).join(" ")}`;
|
|
453
|
+
return decision.updateCommand;
|
|
454
|
+
}
|
|
455
|
+
|
|
289
456
|
/**
|
|
290
457
|
* Soft notify when the server says we're below `latestVersion` but still ≥
|
|
291
458
|
* `minVersion`. Single chalk-yellow line on stderr; never blocks.
|
|
459
|
+
*
|
|
460
|
+
* This path fires for EVERY version below latest (the hard gate only fires
|
|
461
|
+
* below `minVersion`), so it is the far more frequently seen of the two and
|
|
462
|
+
* must be manager-aware for the same reason the gate is.
|
|
292
463
|
*/
|
|
293
|
-
function nudgeUpdateRecommended(
|
|
464
|
+
function nudgeUpdateRecommended(
|
|
465
|
+
decision: VersionCheckResponse,
|
|
466
|
+
install: RunningInstall = resolveRunningInstall(),
|
|
467
|
+
): void {
|
|
294
468
|
const msg = chalk.yellow(
|
|
295
469
|
`⚠ A new version of hq-cli is available: ${decision.latestVersion} (current: ${decision.currentVersion}).`,
|
|
296
470
|
);
|
|
297
471
|
console.error(msg);
|
|
298
|
-
|
|
299
|
-
|
|
472
|
+
const command =
|
|
473
|
+
install.manager === "pnpm"
|
|
474
|
+
? `pnpm ${buildPnpmInstallArgv().join(" ")}`
|
|
475
|
+
: decision.updateCommand;
|
|
476
|
+
if (command) {
|
|
477
|
+
console.error(chalk.dim(` Update: ${command}`));
|
|
300
478
|
}
|
|
301
479
|
}
|
|
302
480
|
|
|
@@ -314,7 +492,7 @@ function enforceUpdateRequired(
|
|
|
314
492
|
decision: VersionCheckResponse,
|
|
315
493
|
deps: {
|
|
316
494
|
performUpdateString?: (command: string) => UpdateResult;
|
|
317
|
-
|
|
495
|
+
resolveInstall?: () => RunningInstall;
|
|
318
496
|
runner?: UpdateRunner;
|
|
319
497
|
cleanStale?: (prefix: string) => string[];
|
|
320
498
|
} = {},
|
|
@@ -326,8 +504,14 @@ function enforceUpdateRequired(
|
|
|
326
504
|
if (decision.message) console.error(chalk.dim(` ${decision.message}`));
|
|
327
505
|
|
|
328
506
|
const command = decision.updateCommand;
|
|
329
|
-
|
|
330
|
-
|
|
507
|
+
// ONE package-root walk decides both the manager and the prefix. A
|
|
508
|
+
// pnpm-managed install must be updated by pnpm; the server's `updateCommand`
|
|
509
|
+
// and any npm prefix are both wrong for that layout, so `resolveRunningInstall`
|
|
510
|
+
// already reports `prefix: null` there and neither is consulted below.
|
|
511
|
+
const install = (deps.resolveInstall ?? resolveRunningInstall)();
|
|
512
|
+
const isPnpm = install.manager === "pnpm";
|
|
513
|
+
const prefix = install.prefix;
|
|
514
|
+
if (!isPnpm && !command && !prefix) {
|
|
331
515
|
console.error(
|
|
332
516
|
chalk.red(
|
|
333
517
|
" No updateCommand provided by hq-pro — see https://hq.indigo.ai/docs/cli-update for manual steps.",
|
|
@@ -344,7 +528,10 @@ function enforceUpdateRequired(
|
|
|
344
528
|
// the EXACT same command under elevation.
|
|
345
529
|
let primaryCmd: string;
|
|
346
530
|
let primaryArgs: string[];
|
|
347
|
-
if (
|
|
531
|
+
if (isPnpm) {
|
|
532
|
+
primaryCmd = "pnpm";
|
|
533
|
+
primaryArgs = buildPnpmInstallArgv();
|
|
534
|
+
} else if (prefix) {
|
|
348
535
|
primaryCmd = "npm";
|
|
349
536
|
primaryArgs = buildPrefixedInstallArgv(prefix);
|
|
350
537
|
} else {
|
|
@@ -353,18 +540,23 @@ function enforceUpdateRequired(
|
|
|
353
540
|
primaryArgs = parts.slice(1);
|
|
354
541
|
}
|
|
355
542
|
|
|
356
|
-
let result
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
543
|
+
let result: UpdateResult;
|
|
544
|
+
if (isPnpm) {
|
|
545
|
+
console.error(
|
|
546
|
+
chalk.dim(" Detected a pnpm-managed global install; updating with pnpm"),
|
|
547
|
+
);
|
|
548
|
+
console.error(chalk.dim(` Running: pnpm ${primaryArgs.join(" ")}`));
|
|
549
|
+
result = performUpdateCommand("pnpm", primaryArgs, runner);
|
|
550
|
+
} else if (prefix) {
|
|
551
|
+
console.error(chalk.dim(` Installing into npm prefix: ${prefix}`));
|
|
552
|
+
console.error(chalk.dim(` Running: npm ${primaryArgs.join(" ")}`));
|
|
553
|
+
result = performUpdateCommand("npm", primaryArgs, runner);
|
|
554
|
+
} else {
|
|
555
|
+
console.error(chalk.dim(` Running: ${command}`));
|
|
556
|
+
result = deps.performUpdateString
|
|
557
|
+
? deps.performUpdateString(command!)
|
|
558
|
+
: performUpdate(command!, runner);
|
|
559
|
+
}
|
|
368
560
|
|
|
369
561
|
// A partial/corrupt global install leaves npm unable to atomically rename its
|
|
370
562
|
// freshly-unpacked package over a leftover directory, so the install above
|
|
@@ -395,7 +587,11 @@ function enforceUpdateRequired(
|
|
|
395
587
|
// Homebrew on macOS, where the first attempt already succeeded anyway) fall
|
|
396
588
|
// through to the manual path unchanged, while headless boxes with passwordless
|
|
397
589
|
// sudo self-update cleanly.
|
|
398
|
-
|
|
590
|
+
//
|
|
591
|
+
// Never for pnpm: `sudo -n pnpm add -g` installs into ROOT's PNPM_HOME, which
|
|
592
|
+
// leaves the user's shim untouched while reporting success — the same silent
|
|
593
|
+
// no-op this fix exists to remove. A failed pnpm update must surface instead.
|
|
594
|
+
if (!result.ok && primaryCmd && !isPnpm) {
|
|
399
595
|
console.error(
|
|
400
596
|
chalk.dim(
|
|
401
597
|
` Update failed unprivileged; retrying with: sudo -n ${primaryCmd} ${primaryArgs.join(" ")}`,
|
|
@@ -413,10 +609,29 @@ function enforceUpdateRequired(
|
|
|
413
609
|
console.error(
|
|
414
610
|
chalk.red(`✗ Update failed${result.detail ? `: ${result.detail}` : ""}.`),
|
|
415
611
|
);
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
612
|
+
// The package manager itself is missing from this environment — the usual
|
|
613
|
+
// cause is a minimal-PATH parent (launchd, cron, a bare systemd unit) that
|
|
614
|
+
// never sourced the shell profile which puts PNPM_HOME (or nvm's npm) on
|
|
615
|
+
// PATH. Say so, because "exit 75" alone reads as a permissions problem.
|
|
616
|
+
if (result.code === "ENOENT") {
|
|
617
|
+
console.error(
|
|
618
|
+
chalk.red(
|
|
619
|
+
` \`${primaryCmd}\` was not found on PATH in this environment (launchd, cron and other minimal-PATH parents commonly lack it).`,
|
|
620
|
+
),
|
|
621
|
+
);
|
|
622
|
+
}
|
|
623
|
+
const manual = manualUpdateCommand(install, decision) ?? command!;
|
|
419
624
|
console.error(chalk.dim(` Try manually: ${manual}`));
|
|
625
|
+
// Only informational, and only when we could not run the right manager at
|
|
626
|
+
// all: hq-pro's command is npm-shaped, so following it on a pnpm layout is
|
|
627
|
+
// what produced the stale-shim loop in the first place.
|
|
628
|
+
if (isPnpm && result.code === "ENOENT" && command) {
|
|
629
|
+
console.error(
|
|
630
|
+
chalk.dim(
|
|
631
|
+
` (hq-pro suggests \`${command}\` — that is for npm-managed installs; use it only if you have switched this install to npm.)`,
|
|
632
|
+
),
|
|
633
|
+
);
|
|
634
|
+
}
|
|
420
635
|
process.exit(75);
|
|
421
636
|
}
|
|
422
637
|
|
|
@@ -442,11 +657,15 @@ export async function enforceVersionGate(): Promise<void> {
|
|
|
442
657
|
if (isOptedOut()) return;
|
|
443
658
|
const decision = await fetchVersionDecision();
|
|
444
659
|
if (!decision) return; // best-effort: silent on any failure
|
|
660
|
+
if (!decision.updateRequired && !decision.updateRecommended) return;
|
|
661
|
+
// Resolved once, here, so the up-to-date case never pays for the walk and
|
|
662
|
+
// neither downstream path repeats it.
|
|
663
|
+
const install = resolveRunningInstall();
|
|
445
664
|
if (decision.updateRequired) {
|
|
446
|
-
enforceUpdateRequired(decision); // exits process
|
|
665
|
+
enforceUpdateRequired(decision, { resolveInstall: () => install }); // exits process
|
|
447
666
|
}
|
|
448
667
|
if (decision.updateRecommended) {
|
|
449
|
-
nudgeUpdateRecommended(decision);
|
|
668
|
+
nudgeUpdateRecommended(decision, install);
|
|
450
669
|
}
|
|
451
670
|
}
|
|
452
671
|
|
|
@@ -465,12 +684,19 @@ export const __test__ = {
|
|
|
465
684
|
CLIENT_ID,
|
|
466
685
|
ENDPOINT_PATH,
|
|
467
686
|
FETCH_TIMEOUT_MS,
|
|
687
|
+
buildPnpmInstallArgv,
|
|
468
688
|
buildPrefixedInstallArgv,
|
|
689
|
+
buildSpawnPlan,
|
|
469
690
|
cleanStalePartialInstall,
|
|
470
691
|
enforceUpdateRequired,
|
|
692
|
+
isPnpmManagedPackageDir,
|
|
471
693
|
npmPrefixFromPackageDir,
|
|
694
|
+
nudgeUpdateRecommended,
|
|
472
695
|
performUpdate,
|
|
473
696
|
performUpdateCommand,
|
|
697
|
+
quoteForWindowsShell,
|
|
474
698
|
runUpdateCommand,
|
|
699
|
+
resolveRunningInstall,
|
|
700
|
+
resolveRunningManager,
|
|
475
701
|
resolveRunningPrefix,
|
|
476
702
|
};
|