@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.
- package/CHANGELOG.md +8 -0
- package/dist/commands/cloud.js +6 -5
- package/dist/commands/core-resolve-company-native.d.ts +2 -0
- package/dist/commands/core-resolve-company-native.js +54 -17
- package/dist/commands/install-global.d.ts +1 -1
- package/dist/commands/install-global.js +5 -4
- package/dist/commands/pkg-install.js +5 -3
- package/dist/hq-cloud-sync-runner/bin/sync-runner.js +7226 -4092
- package/dist/hq-cloud-sync-runner/package.json +1 -1
- package/dist/lib/flag-registry-worker-source.generated.d.ts +1 -1
- package/dist/lib/flag-registry-worker-source.generated.js +1 -1
- package/dist/utils/package-use-lease.d.ts +17 -0
- package/dist/utils/package-use-lease.js +36 -0
- package/dist/utils/self-update.d.ts +4 -3
- package/dist/utils/self-update.js +12 -5
- package/dist/utils/sync-operation-lock.d.ts +37 -0
- package/dist/utils/sync-operation-lock.js +40 -0
- package/dist/utils/version-gate.d.ts +20 -4
- package/dist/utils/version-gate.js +112 -11
- package/package.json +2 -2
|
@@ -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
|
-
|
|
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("--
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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.
|
|
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.
|
|
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",
|