@indigoai-us/hq-cli 5.345.34 → 5.345.36

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.
@@ -50,5 +50,22 @@ export declare const __test__: {
50
50
  waitForUpdateRequest: typeof waitForUpdateRequest;
51
51
  setCurrentPackageUseLease(lease: PackageUseLease | null): void;
52
52
  };
53
+ /**
54
+ * Run `wait` with this process's package-use lease DROPPED, then reacquire the
55
+ * lease before returning.
56
+ *
57
+ * `hq sync` runs under purpose `"command"`, and
58
+ * {@link releasePackageUseLeaseAfterCommandLoad} deliberately keeps that lease
59
+ * for the whole command. hq-cloud's per-root operation lock then waits — by
60
+ * default without a bound — for whichever process is already syncing, so an old
61
+ * `hq` install pinned `hq pkg update` for the entire length of somebody else's
62
+ * lock wait (HQ-DESKTOP-8H). Queuing is not using the package: drop the lease
63
+ * while this process is only waiting, and take it again once the wait is over.
64
+ *
65
+ * A reacquire that throws {@link PackageUseUpdatePendingError} is the correct
66
+ * outcome — an update landed while we queued — and propagates to the caller. On
67
+ * the failure path the original error wins and the reacquire is best-effort.
68
+ */
69
+ export declare function withPackageUseLeaseReleasedDuring<T>(wait: () => Promise<T>, argv?: readonly string[], release?: () => void, reacquire?: (argv: readonly string[]) => unknown): Promise<T>;
53
70
  export {};
54
71
  //# sourceMappingURL=package-use-lease.d.ts.map
@@ -413,4 +413,40 @@ export const __test__ = {
413
413
  currentPackageUseLease = lease;
414
414
  },
415
415
  };
416
+ /**
417
+ * Run `wait` with this process's package-use lease DROPPED, then reacquire the
418
+ * lease before returning.
419
+ *
420
+ * `hq sync` runs under purpose `"command"`, and
421
+ * {@link releasePackageUseLeaseAfterCommandLoad} deliberately keeps that lease
422
+ * for the whole command. hq-cloud's per-root operation lock then waits — by
423
+ * default without a bound — for whichever process is already syncing, so an old
424
+ * `hq` install pinned `hq pkg update` for the entire length of somebody else's
425
+ * lock wait (HQ-DESKTOP-8H). Queuing is not using the package: drop the lease
426
+ * while this process is only waiting, and take it again once the wait is over.
427
+ *
428
+ * A reacquire that throws {@link PackageUseUpdatePendingError} is the correct
429
+ * outcome — an update landed while we queued — and propagates to the caller. On
430
+ * the failure path the original error wins and the reacquire is best-effort.
431
+ */
432
+ export async function withPackageUseLeaseReleasedDuring(wait, argv = process.argv, release = releaseCurrentPackageUseLease, reacquire = acquireCurrentPackageUseLease) {
433
+ if (currentPackageUseLease === null)
434
+ return wait();
435
+ release();
436
+ let result;
437
+ try {
438
+ result = await wait();
439
+ }
440
+ catch (error) {
441
+ try {
442
+ reacquire(argv);
443
+ }
444
+ catch {
445
+ // The caller's error is the one worth reporting.
446
+ }
447
+ throw error;
448
+ }
449
+ reacquire(argv);
450
+ return result;
451
+ }
416
452
  //# sourceMappingURL=package-use-lease.js.map
@@ -58,7 +58,7 @@
58
58
  */
59
59
  import { spawn } from "node:child_process";
60
60
  import * as fs from "node:fs";
61
- import { type RunningInstall, type UpdateResult } from "./version-gate.js";
61
+ import { probePnpmVersion, type RunningInstall, type UpdateResult } from "./version-gate.js";
62
62
  import { type RestartDaemonResult } from "../lib/mesh/live/daemon/install.js";
63
63
  /**
64
64
  * Set on the re-exec'd child so it can never self-update (and re-exec) again.
@@ -116,7 +116,7 @@ export declare function buildSelfUpdatePlan(install: RunningInstall): {
116
116
  * to stdout. Progress is summarised on stderr by the caller instead; captured
117
117
  * stderr is kept only to explain a failure.
118
118
  */
119
- export declare function runUpdateQuiet(cmd: string, args: string[], env?: NodeJS.ProcessEnv): UpdateResult;
119
+ export declare function runUpdateQuiet(cmd: string, args: string[], env?: NodeJS.ProcessEnv, cwd?: string): UpdateResult;
120
120
  /**
121
121
  * Serialize self-updates across concurrent `hq` processes AND the hq-sync
122
122
  * menubar app. Without this, a machine running several HQ agents can fire many
@@ -144,7 +144,8 @@ export interface SelfUpdateDeps {
144
144
  homeDir?: string;
145
145
  /** Same-filesystem install rename, injectable for rollback tests. */
146
146
  renamePath?: typeof fs.renameSync;
147
- runner?: (cmd: string, args: string[], env?: NodeJS.ProcessEnv) => UpdateResult;
147
+ runner?: (cmd: string, args: string[], env?: NodeJS.ProcessEnv, cwd?: string) => UpdateResult;
148
+ probePnpmVersion?: typeof probePnpmVersion;
148
149
  /** Delay between bounded npm retries when the just-published target is not visible yet. */
149
150
  sleep?: (ms: number) => Promise<void>;
150
151
  reexec?: (argv: string[], env: NodeJS.ProcessEnv) => number | null;
@@ -65,7 +65,7 @@ import chalk from "chalk";
65
65
  import { Sentry } from "../sentry.js";
66
66
  import { isCliTelemetryDisabled } from "./cli-telemetry.js";
67
67
  import { CLI_NAME, CLI_VERSION } from "../cli-version.js";
68
- import { buildBunInstallArgv, buildPnpmInstallArgv, buildPrefixedInstallArgv, buildSpawnPlan, canWriteNpmInstall, captureNonWritableNpmPrefixNotice, claimNonWritableNpmPrefixNotice, checkUpdateConvergence, derivePnpmHome, detachUpdateSupervisor, isBunManagedPackageDir, isLocalDependencyInstall, isPnpmManagedPackageDir, nonWritablePrefixNote, openInstallOutput, pnpmUpdateEnv, resolveRunningInstall, runUpdateCommand, runSupervisedUpdateCommand, UPDATE_RECURSION_GUARD_ENV, } from "./version-gate.js";
68
+ import { buildBunInstallArgv, buildPnpmInstallArgv, buildPrefixedInstallArgv, buildSpawnPlan, canWriteNpmInstall, captureNonWritableNpmPrefixNotice, claimNonWritableNpmPrefixNotice, checkUpdateConvergence, derivePnpmHome, detachUpdateSupervisor, isBunManagedPackageDir, isLocalDependencyInstall, isPnpmManagedPackageDir, nonWritablePrefixNote, openInstallOutput, pnpmUpdateEnv, pnpmInstalledVersion, performPnpmUpdate, resolveRunningInstall, runUpdateCommand, runSupervisedUpdateCommand, UPDATE_RECURSION_GUARD_ENV, } from "./version-gate.js";
69
69
  import { canStageInstall, stagedNpmInstall } from "./staged-install.js";
70
70
  import { acquireUpdateLock as acquireSharedUpdateLock } from "./update-lock.js";
71
71
  import { markLatestIneffective } from "./version-check.js";
@@ -548,8 +548,12 @@ async function fetchLatestVersion() {
548
548
  * through the resolved prefix so the copy that is actually running is replaced.
549
549
  */
550
550
  export function buildSelfUpdatePlan(install) {
551
- if (install.manager === "pnpm")
552
- return { cmd: "pnpm", args: buildPnpmInstallArgv(install) };
551
+ if (install.manager === "pnpm") {
552
+ const version = pnpmInstalledVersion(install);
553
+ return version
554
+ ? { cmd: "corepack", args: [`pnpm@${version}`, ...buildPnpmInstallArgv(install)] }
555
+ : { cmd: "pnpm", args: buildPnpmInstallArgv(install) };
556
+ }
553
557
  if (install.manager === "bun")
554
558
  return { cmd: "bun", args: buildBunInstallArgv() };
555
559
  if (install.prefix)
@@ -563,12 +567,12 @@ export function buildSelfUpdatePlan(install) {
563
567
  * to stdout. Progress is summarised on stderr by the caller instead; captured
564
568
  * stderr is kept only to explain a failure.
565
569
  */
566
- export function runUpdateQuiet(cmd, args, env) {
570
+ export function runUpdateQuiet(cmd, args, env, cwd) {
567
571
  // File sinks rather than pipes: a pipe dies with this process and would take
568
572
  // the installer down with it mid-write. See `openInstallOutput`.
569
573
  const output = openInstallOutput(false);
570
574
  try {
571
- return runSupervisedUpdateCommand(cmd, args, output, env);
575
+ return runSupervisedUpdateCommand(cmd, args, output, env, cwd);
572
576
  }
573
577
  finally {
574
578
  output.dispose();
@@ -826,6 +830,9 @@ async function updateAndReexec(argv, flavor, known, deps) {
826
830
  if (staged) {
827
831
  return stagedNpmInstall(install, latestValid, { run: runner, env, rename: renamePath });
828
832
  }
833
+ if (install.manager === "pnpm") {
834
+ return performPnpmUpdate(install, runner, updateEnv ?? env, deps.probePnpmVersion);
835
+ }
829
836
  return updateEnv === undefined
830
837
  ? runner(plan.cmd, plan.args)
831
838
  : runner(plan.cmd, plan.args, updateEnv);
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Take hq-cloud's per-root operation lock for a sync pass from hq-cli, so the
3
+ * wait for an existing holder happens OUTSIDE this process's package-use lease.
4
+ *
5
+ * `sync()` normally wraps itself in `withOperationLock`, which waits for a live
6
+ * holder with no bound unless `HQ_OP_LOCK_TIMEOUT` says otherwise. `hq sync`
7
+ * runs under package-use purpose `"command"`, whose lease is held for the whole
8
+ * command, so the queued process pinned the installed `hq` version — and
9
+ * therefore blocked `hq pkg update` — for the entire length of somebody else's
10
+ * sync (HQ-DESKTOP-8H).
11
+ *
12
+ * Acquiring the lock here instead lets the lease be dropped across the wait and
13
+ * retaken the moment the lock is held, before any sync work runs.
14
+ * `operationLockAlreadyHeld` is the seam hq-cloud exposes for a caller that owns
15
+ * the lock; the lock is NOT reentrant in-process, so the flag is required rather
16
+ * than optional.
17
+ */
18
+ import { type SyncOptions, type SyncResult } from "@indigoai-us/hq-cloud";
19
+ import { type LockHandle } from "@indigoai-us/hq-cloud/operation-lock";
20
+ export interface SyncUnderOperationLockDeps<Options, Result> {
21
+ sync: (options: Options & {
22
+ operationLockAlreadyHeld: true;
23
+ }) => Promise<Result>;
24
+ /** Injected in tests; defaults to hq-cloud's wait-capable acquisition. */
25
+ acquireLock?: (hqRoot: string) => Promise<LockHandle>;
26
+ /** Injected in tests; defaults to the real lease drop/retake. */
27
+ withLeaseReleased?: <T>(wait: () => Promise<T>) => Promise<T>;
28
+ }
29
+ export declare function syncUnderOperationLock<Options extends {
30
+ hqRoot: string;
31
+ }, Result>(options: Options, deps: SyncUnderOperationLockDeps<Options, Result>): Promise<Result>;
32
+ /**
33
+ * The production sync leg: hq-cloud's `sync()` under a lock this process takes
34
+ * itself. Every `hq sync` path in `src/commands/cloud.ts` calls this.
35
+ */
36
+ export declare function syncLeg(options: SyncOptions): Promise<SyncResult>;
37
+ //# sourceMappingURL=sync-operation-lock.d.ts.map
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Take hq-cloud's per-root operation lock for a sync pass from hq-cli, so the
3
+ * wait for an existing holder happens OUTSIDE this process's package-use lease.
4
+ *
5
+ * `sync()` normally wraps itself in `withOperationLock`, which waits for a live
6
+ * holder with no bound unless `HQ_OP_LOCK_TIMEOUT` says otherwise. `hq sync`
7
+ * runs under package-use purpose `"command"`, whose lease is held for the whole
8
+ * command, so the queued process pinned the installed `hq` version — and
9
+ * therefore blocked `hq pkg update` — for the entire length of somebody else's
10
+ * sync (HQ-DESKTOP-8H).
11
+ *
12
+ * Acquiring the lock here instead lets the lease be dropped across the wait and
13
+ * retaken the moment the lock is held, before any sync work runs.
14
+ * `operationLockAlreadyHeld` is the seam hq-cloud exposes for a caller that owns
15
+ * the lock; the lock is NOT reentrant in-process, so the flag is required rather
16
+ * than optional.
17
+ */
18
+ import { sync } from "@indigoai-us/hq-cloud";
19
+ import { acquireOperationLockAsync, } from "@indigoai-us/hq-cloud/operation-lock";
20
+ import { withPackageUseLeaseReleasedDuring } from "./package-use-lease.js";
21
+ export async function syncUnderOperationLock(options, deps) {
22
+ const acquireLock = deps.acquireLock ?? ((hqRoot) => acquireOperationLockAsync(hqRoot, "sync"));
23
+ const withLeaseReleased = deps.withLeaseReleased ??
24
+ ((wait) => withPackageUseLeaseReleasedDuring(wait));
25
+ const handle = await withLeaseReleased(() => acquireLock(options.hqRoot));
26
+ try {
27
+ return await deps.sync({ ...options, operationLockAlreadyHeld: true });
28
+ }
29
+ finally {
30
+ handle.release();
31
+ }
32
+ }
33
+ /**
34
+ * The production sync leg: hq-cloud's `sync()` under a lock this process takes
35
+ * itself. Every `hq sync` path in `src/commands/cloud.ts` calls this.
36
+ */
37
+ export function syncLeg(options) {
38
+ return syncUnderOperationLock(options, { sync });
39
+ }
40
+ //# sourceMappingURL=sync-operation-lock.js.map
@@ -229,9 +229,19 @@ export declare function buildPnpmInstallArgv(install?: RunningInstall): string[]
229
229
  * forward the paths recorded for the install it is replacing.
230
230
  */
231
231
  export declare function pnpmInstallDirs(install: RunningInstall): {
232
+ globalDir: string;
232
233
  storeDir: string;
233
234
  virtualStoreDir: string;
234
235
  } | null;
236
+ /** Version recorded by pnpm for the global install that owns this package. */
237
+ export declare function pnpmInstalledVersion(install: RunningInstall): string | null;
238
+ /**
239
+ * Resolve a bounded pnpm executable probe. pnpm 10 and older reject
240
+ * --virtual-store-dir for global installs; pnpm 11+ accepts the flag.
241
+ */
242
+ export declare function probePnpmVersion(cmd: string, args: string[], env: NodeJS.ProcessEnv, cwd: string): string | null;
243
+ /** Run the install-owned pnpm first; use PATH only if that version cannot run. */
244
+ export declare function performPnpmUpdate(install: RunningInstall, runner?: UpdateRunner, env?: NodeJS.ProcessEnv, probe?: typeof probePnpmVersion): UpdateResult;
235
245
  /** Argv for updating a Bun-managed global install. */
236
246
  export declare function buildBunInstallArgv(): string[];
237
247
  /**
@@ -278,11 +288,12 @@ export type UpdateResult = {
278
288
  ok: boolean;
279
289
  detail?: string;
280
290
  code?: string;
291
+ stdout?: string;
281
292
  /** Sanitized installer error code for diagnostics; never drives retry or exit behavior. */
282
293
  diagnosticCode?: string;
283
294
  failureKind?: "package_manager_exit";
284
295
  };
285
- type UpdateRunner = (cmd: string, args: string[], env?: NodeJS.ProcessEnv) => UpdateResult;
296
+ type UpdateRunner = (cmd: string, args: string[], env?: NodeJS.ProcessEnv, cwd?: string) => UpdateResult;
286
297
  export { buildSpawnPlan, quoteForWindowsShell };
287
298
  /**
288
299
  * Add `detached` to a **synchronous** spawn's options.
@@ -366,9 +377,9 @@ export interface InstallOutput {
366
377
  dispose: () => void;
367
378
  }
368
379
  export declare function openInstallOutput(verbose: boolean, isTty?: boolean): InstallOutput;
369
- export declare function runSupervisedUpdateCommand(cmd: string, args: string[], output: InstallOutput, env?: NodeJS.ProcessEnv): UpdateResult;
370
- export declare function runUpdateCommand(cmd: string, args: string[], env?: NodeJS.ProcessEnv): UpdateResult;
371
- declare function performUpdateCommand(cmd: string, args: string[], runner?: UpdateRunner, env?: NodeJS.ProcessEnv): UpdateResult;
380
+ export declare function runSupervisedUpdateCommand(cmd: string, args: string[], output: InstallOutput, env?: NodeJS.ProcessEnv, cwd?: string): UpdateResult;
381
+ export declare function runUpdateCommand(cmd: string, args: string[], env?: NodeJS.ProcessEnv, cwd?: string): UpdateResult;
382
+ declare function performUpdateCommand(cmd: string, args: string[], runner?: UpdateRunner, env?: NodeJS.ProcessEnv, cwd?: string): UpdateResult;
372
383
  declare function performUpdate(command: string, runner?: UpdateRunner): UpdateResult;
373
384
  /**
374
385
  * Soft notify when the server says we're below `latestVersion` but still ≥
@@ -434,6 +445,7 @@ export declare function checkUpdateConvergence(targetVersion: string, deps?: {
434
445
  /** Injectable surface for {@link enforceUpdateRequired} (unit tests). */
435
446
  interface EnforceUpdateDeps {
436
447
  performUpdateString?: (command: string) => UpdateResult;
448
+ probePnpmVersion?: typeof probePnpmVersion;
437
449
  resolveInstall?: () => RunningInstall;
438
450
  canWriteNpmInstall?: (install: RunningInstall) => boolean;
439
451
  homeDir?: string;
@@ -518,6 +530,10 @@ export declare const __test__: {
518
530
  enforceUpdateRequired: typeof enforceUpdateRequired;
519
531
  isBunManagedPackageDir: typeof isBunManagedPackageDir;
520
532
  pnpmUpdateEnv: typeof pnpmUpdateEnv;
533
+ pnpmInstallDirs: typeof pnpmInstallDirs;
534
+ pnpmInstalledVersion: typeof pnpmInstalledVersion;
535
+ performPnpmUpdate: typeof performPnpmUpdate;
536
+ probePnpmVersion: typeof probePnpmVersion;
521
537
  isLocalDependencyInstall: typeof isLocalDependencyInstall;
522
538
  isLocalDependencyPackageDir: typeof isLocalDependencyPackageDir;
523
539
  isNewerVersion: typeof isNewerVersion;
@@ -626,8 +626,10 @@ export function buildPrefixedInstallArgv(prefix) {
626
626
  export function buildPnpmInstallArgv(install) {
627
627
  const args = ["add", "-g", LATEST_PACKAGE_SPEC];
628
628
  const dirs = install ? pnpmInstallDirs(install) : null;
629
- if (dirs)
630
- args.push("--store-dir", dirs.storeDir, "--virtual-store-dir", dirs.virtualStoreDir);
629
+ if (dirs) {
630
+ args.push("--global-dir", dirs.globalDir, "--store-dir", dirs.storeDir);
631
+ args.push("--virtual-store-dir", dirs.virtualStoreDir);
632
+ }
631
633
  return args;
632
634
  }
633
635
  /**
@@ -655,7 +657,10 @@ export function pnpmInstallDirs(install) {
655
657
  const virtualStoreDir = path.isAbsolute(configuredVirtualStore)
656
658
  ? configuredVirtualStore
657
659
  : path.resolve(globalNodeModules, configuredVirtualStore);
658
- return { storeDir, virtualStoreDir };
660
+ const pnpmHome = derivePnpmHome(install);
661
+ if (!pnpmHome)
662
+ return null;
663
+ return { globalDir: path.join(pnpmHome, "global"), storeDir, virtualStoreDir };
659
664
  }
660
665
  catch {
661
666
  // The metadata file is absent in older pnpm global layouts. The global
@@ -664,11 +669,93 @@ export function pnpmInstallDirs(install) {
664
669
  if (!pnpmHome)
665
670
  return null;
666
671
  return {
672
+ globalDir: path.join(pnpmHome, "global"),
667
673
  storeDir: path.join(pnpmHome, "store", "v11"),
668
674
  virtualStoreDir: path.join(globalNodeModules, ".pnpm"),
669
675
  };
670
676
  }
671
677
  }
678
+ /** Version recorded by pnpm for the global install that owns this package. */
679
+ export function pnpmInstalledVersion(install) {
680
+ if (install.manager !== "pnpm" || !install.packageRoot)
681
+ return null;
682
+ const normalized = install.packageRoot.replace(/\\/g, "/");
683
+ const match = normalized.match(/^(.*\/global\/\d+\/node_modules)(?:\/|$)|^(.*\/global\/v\d+\/[^/]+\/node_modules)(?:\/|$)/);
684
+ const globalNodeModules = match?.[1] ?? match?.[2];
685
+ if (!globalNodeModules)
686
+ return null;
687
+ try {
688
+ const metadata = yaml.load(readFileSync(path.join(globalNodeModules, ".modules.yaml"), "utf8"));
689
+ const value = typeof metadata?.packageManager === "string" ? metadata.packageManager : "";
690
+ const version = value.match(/^pnpm@(.+)$/)?.[1];
691
+ return version && semver.valid(version) ? version : null;
692
+ }
693
+ catch {
694
+ return null;
695
+ }
696
+ }
697
+ /**
698
+ * Resolve a bounded pnpm executable probe. pnpm 10 and older reject
699
+ * --virtual-store-dir for global installs; pnpm 11+ accepts the flag.
700
+ */
701
+ export function probePnpmVersion(cmd, args, env, cwd) {
702
+ try {
703
+ const plan = buildSpawnPlan(cmd, args);
704
+ const result = spawnSync(plan.cmd, plan.args, {
705
+ encoding: "utf8",
706
+ timeout: 10_000,
707
+ cwd,
708
+ env,
709
+ shell: plan.shell,
710
+ windowsHide: true,
711
+ });
712
+ if (result.error || result.status !== 0)
713
+ return null;
714
+ const match = (result.stdout ?? "").trim().match(/(?:^|\s)(\d+\.\d+\.\d+(?:-[\w.-]+)?)(?:\s|$)/);
715
+ return match?.[1] && semver.valid(match[1]) ? match[1] : null;
716
+ }
717
+ catch (error) {
718
+ console.error(chalk.dim(`hq: pnpm version probe failed (${error instanceof Error ? error.name : "unknown"}).`));
719
+ return null;
720
+ }
721
+ }
722
+ /** Run the install-owned pnpm first; use PATH only if that version cannot run. */
723
+ export function performPnpmUpdate(install, runner = runUpdateCommand, env = process.env, probe = probePnpmVersion) {
724
+ const cwd = mkdtempSync(path.join(os.tmpdir(), "hq-cli-pnpm-update-"));
725
+ const updateEnv = {
726
+ ...env,
727
+ COREPACK_ENABLE_PROJECT_SPEC: "0",
728
+ COREPACK_ENABLE_STRICT: "0",
729
+ COREPACK_DEFAULT_TO_LATEST: "0",
730
+ npm_config_manage_package_manager_versions: "false",
731
+ };
732
+ try {
733
+ const installedVersion = pnpmInstalledVersion(install);
734
+ if (installedVersion) {
735
+ const command = `pnpm@${installedVersion}`;
736
+ if (probe("corepack", [command, "--version"], updateEnv, cwd) === installedVersion) {
737
+ return runner("corepack", [command, ...buildPnpmInstallArgv(install)], updateEnv, cwd);
738
+ }
739
+ }
740
+ const fallbackVersion = probe("pnpm", ["--version"], updateEnv, cwd);
741
+ if (!fallbackVersion) {
742
+ return { ok: false, code: "PNPM_UNAVAILABLE", detail: "pnpm not found on PATH; install-owned Corepack pnpm could not run" };
743
+ }
744
+ // pnpm 10.28.1 and earlier reject --virtual-store-dir with -g. Keep the
745
+ // existing global directory and store, but omit only that unsupported flag.
746
+ const supportsGlobalVirtualStoreDir = semver.gte(fallbackVersion, "11.0.0");
747
+ const args = buildPnpmInstallArgv(install);
748
+ if (!supportsGlobalVirtualStoreDir) {
749
+ const index = args.indexOf("--virtual-store-dir");
750
+ if (index !== -1)
751
+ args.splice(index, 2);
752
+ }
753
+ return runner("pnpm", args, updateEnv, cwd);
754
+ }
755
+ finally {
756
+ rmSync(cwd, { recursive: true, force: true });
757
+ }
758
+ }
672
759
  /** Argv for updating a Bun-managed global install. */
673
760
  export function buildBunInstallArgv() {
674
761
  return ["add", "-g", LATEST_PACKAGE_SPEC];
@@ -1037,7 +1124,7 @@ export function openInstallOutput(verbose, isTty = process.stdout.isTTY === true
1037
1124
  },
1038
1125
  };
1039
1126
  }
1040
- export function runSupervisedUpdateCommand(cmd, args, output, env) {
1127
+ export function runSupervisedUpdateCommand(cmd, args, output, env, cwd) {
1041
1128
  try {
1042
1129
  const plan = buildSpawnPlan(cmd, args);
1043
1130
  const timeoutMs = updateTimeoutMs(env);
@@ -1061,6 +1148,7 @@ export function runSupervisedUpdateCommand(cmd, args, output, env) {
1061
1148
  ], inOwnProcessGroup({
1062
1149
  stdio: output.stdio,
1063
1150
  ...(env ? { env } : {}),
1151
+ ...(cwd ? { cwd } : {}),
1064
1152
  }));
1065
1153
  // spawnSync reports a missing executable via `error`, not a throw.
1066
1154
  if (result.error) {
@@ -1108,20 +1196,22 @@ export function runSupervisedUpdateCommand(cmd, args, output, env) {
1108
1196
  };
1109
1197
  }
1110
1198
  }
1111
- export function runUpdateCommand(cmd, args, env) {
1199
+ export function runUpdateCommand(cmd, args, env, cwd) {
1112
1200
  const output = openInstallOutput(true);
1113
1201
  try {
1114
- return runSupervisedUpdateCommand(cmd, args, output, env);
1202
+ return runSupervisedUpdateCommand(cmd, args, output, env, cwd);
1115
1203
  }
1116
1204
  finally {
1117
1205
  output.dispose();
1118
1206
  }
1119
1207
  }
1120
- function performUpdateCommand(cmd, args, runner = runUpdateCommand, env) {
1208
+ function performUpdateCommand(cmd, args, runner = runUpdateCommand, env, cwd) {
1121
1209
  // Forward env only when there is one to forward, so the common path keeps the
1122
1210
  // two-argument runner call it has always made (and existing runner spies see
1123
1211
  // no phantom `undefined` third argument).
1124
- return env === undefined ? runner(cmd, args) : runner(cmd, args, env);
1212
+ return env === undefined && cwd === undefined
1213
+ ? runner(cmd, args)
1214
+ : runner(cmd, args, env, cwd);
1125
1215
  }
1126
1216
  function performUpdate(command, runner = runUpdateCommand) {
1127
1217
  const parts = command.split(/\s+/).filter(Boolean);
@@ -1141,8 +1231,13 @@ function performUpdate(command, runner = runUpdateCommand) {
1141
1231
  * speak the running manager's language.
1142
1232
  */
1143
1233
  function manualUpdateCommand(install, decision) {
1144
- if (install.manager === "pnpm")
1145
- return `pnpm ${buildPnpmInstallArgv(install).join(" ")}`;
1234
+ if (install.manager === "pnpm") {
1235
+ const version = pnpmInstalledVersion(install);
1236
+ const plan = version
1237
+ ? `corepack pnpm@${version} ${buildPnpmInstallArgv(install).join(" ")}`
1238
+ : `pnpm ${buildPnpmInstallArgv(install).join(" ")}`;
1239
+ return plan;
1240
+ }
1146
1241
  if (install.manager === "bun")
1147
1242
  return `bun ${buildBunInstallArgv().join(" ")}`;
1148
1243
  if (install.prefix)
@@ -1522,7 +1617,9 @@ function attemptRequiredUpdate(decision, deps, install) {
1522
1617
  if (isManagedOutsideNpm) {
1523
1618
  console.error(chalk.dim(` Detected a ${install.manager}-managed global install; updating with ${install.manager}`));
1524
1619
  console.error(chalk.dim(` Running: ${primaryCmd} ${primaryArgs.join(" ")}`));
1525
- result = performUpdateCommand(primaryCmd, primaryArgs, runner, updateEnv);
1620
+ result = install.manager === "pnpm"
1621
+ ? performPnpmUpdate(install, runner, updateEnv ?? process.env, deps.probePnpmVersion)
1622
+ : performUpdateCommand(primaryCmd, primaryArgs, runner, updateEnv);
1526
1623
  }
1527
1624
  else if (prefix) {
1528
1625
  console.error(chalk.dim(` Installing into npm prefix: ${prefix}`));
@@ -1719,6 +1816,10 @@ export const __test__ = {
1719
1816
  enforceUpdateRequired,
1720
1817
  isBunManagedPackageDir,
1721
1818
  pnpmUpdateEnv,
1819
+ pnpmInstallDirs,
1820
+ pnpmInstalledVersion,
1821
+ performPnpmUpdate,
1822
+ probePnpmVersion,
1722
1823
  isLocalDependencyInstall,
1723
1824
  isLocalDependencyPackageDir,
1724
1825
  isNewerVersion,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indigoai-us/hq-cli",
3
- "version": "5.345.34",
3
+ "version": "5.345.36",
4
4
  "description": "HQ by Indigo management CLI — modules and cloud sync",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -48,7 +48,7 @@
48
48
  "@aws-sdk/client-iot-data-plane": "^3.1096.0",
49
49
  "@aws-sdk/client-s3": "^3.1049.0",
50
50
  "@aws-sdk/dsql-signer": "^3.1146.0",
51
- "@indigoai-us/hq-cloud": "~6.18.33",
51
+ "@indigoai-us/hq-cloud": "~6.18.50",
52
52
  "@indigoai-us/hq-flags-client": "^0.1.2",
53
53
  "@modelcontextprotocol/sdk": "1.29.0",
54
54
  "@sentry/node": "^10.49.0",