@indigoai-us/hq-cli 5.108.4 → 5.108.6
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 +126 -0
- package/dist/commands/feedback.d.ts +38 -0
- package/dist/commands/feedback.js +80 -0
- package/dist/commands/meetings.js +55 -1
- package/dist/commands/signals.js +12 -0
- package/dist/commands/sources.js +12 -0
- package/dist/utils/client-health.js +17 -1
- package/dist/utils/feedback-diagnostics.d.ts +11 -0
- package/dist/utils/feedback-log-bundle.d.ts +149 -0
- package/dist/utils/feedback-log-bundle.js +430 -0
- package/dist/utils/feedback-logs.d.ts +329 -0
- package/dist/utils/feedback-logs.js +669 -0
- package/dist/utils/self-update.d.ts +20 -0
- package/dist/utils/self-update.js +22 -1
- package/dist/utils/version-check.d.ts +7 -0
- package/dist/utils/version-check.js +46 -1
- package/package.json +2 -2
|
@@ -71,6 +71,15 @@ export type SelfUpdateAction =
|
|
|
71
71
|
| "update-failed"
|
|
72
72
|
/** Updated, but the re-exec couldn't start; continue on the current (in-memory) version. */
|
|
73
73
|
| "updated-no-reexec"
|
|
74
|
+
/**
|
|
75
|
+
* The install reported success but the `hq` on PATH still resolves the old
|
|
76
|
+
* version (a shadowing/ghost install, a prefix/PATH mismatch, or a stale
|
|
77
|
+
* tarball). Re-installing the same target can never converge, so we do NOT
|
|
78
|
+
* re-exec, we record the target as ineffective so the startup path stops
|
|
79
|
+
* auto-retrying it, and the command runs on the current version. This is the
|
|
80
|
+
* guard against the self-update loop.
|
|
81
|
+
*/
|
|
82
|
+
| "update-ineffective"
|
|
74
83
|
/** Updated and the command re-ran on the new version; exit with `reexecStatus`. */
|
|
75
84
|
| "reexec"
|
|
76
85
|
/**
|
|
@@ -127,6 +136,17 @@ export interface SelfUpdateDeps {
|
|
|
127
136
|
runner?: (cmd: string, args: string[], env?: NodeJS.ProcessEnv) => UpdateResult;
|
|
128
137
|
reexec?: (argv: string[], env: NodeJS.ProcessEnv) => number | null;
|
|
129
138
|
acquireLock?: () => (() => void) | null;
|
|
139
|
+
/**
|
|
140
|
+
* Read-your-writes convergence check run after a "successful" install:
|
|
141
|
+
* returns false only when the `hq` on PATH still resolves a build older than
|
|
142
|
+
* the target we just installed. Defaults to {@link checkUpdateConvergence}.
|
|
143
|
+
*/
|
|
144
|
+
checkConvergence?: (targetVersion: string) => boolean;
|
|
145
|
+
/**
|
|
146
|
+
* Persist that `version` installed but never took effect, so the startup path
|
|
147
|
+
* stops auto-retrying it. Defaults to {@link markLatestIneffective}.
|
|
148
|
+
*/
|
|
149
|
+
markIneffective?: (version: string) => void;
|
|
130
150
|
/**
|
|
131
151
|
* Whether a human is watching this invocation. Defaults to "stderr is a TTY",
|
|
132
152
|
* which is false for exactly the callers that must not replace the CLI
|
|
@@ -60,8 +60,9 @@ import { spawnSync } from "node:child_process";
|
|
|
60
60
|
import semver from "semver";
|
|
61
61
|
import chalk from "chalk";
|
|
62
62
|
import { CLI_NAME, CLI_VERSION } from "../cli-version.js";
|
|
63
|
-
import { buildBunInstallArgv, buildPnpmInstallArgv, buildPrefixedInstallArgv, buildSpawnPlan, inOwnProcessGroup, isLocalDependencyInstall, openInstallOutput, pnpmUpdateEnv, resolveRunningInstall, runUpdateCommand, } from "./version-gate.js";
|
|
63
|
+
import { buildBunInstallArgv, buildPnpmInstallArgv, buildPrefixedInstallArgv, buildSpawnPlan, checkUpdateConvergence, inOwnProcessGroup, isLocalDependencyInstall, openInstallOutput, pnpmUpdateEnv, resolveRunningInstall, runUpdateCommand, } from "./version-gate.js";
|
|
64
64
|
import { acquireUpdateLock as acquireSharedUpdateLock } from "./update-lock.js";
|
|
65
|
+
import { markLatestIneffective } from "./version-check.js";
|
|
65
66
|
/**
|
|
66
67
|
* Set on the re-exec'd child so it can never self-update (and re-exec) again.
|
|
67
68
|
* One update + one re-exec per user invocation, ever.
|
|
@@ -278,6 +279,26 @@ async function updateAndReexec(argv, flavor, known, deps) {
|
|
|
278
279
|
console.error(chalk.dim(` Continuing the ${flavor.noun} on ${current}.`));
|
|
279
280
|
return { action: "update-failed", latest };
|
|
280
281
|
}
|
|
282
|
+
// Read-your-writes: a "successful" `npm install -g` proves only that the
|
|
283
|
+
// prefix was rewritten, NOT that the `hq` this user's PATH resolves is the
|
|
284
|
+
// copy we just wrote. When a ghost/shadowing install keeps winning PATH
|
|
285
|
+
// resolution (or a prefix/PATH mismatch or stale tarball leaves the running
|
|
286
|
+
// copy behind), re-exec'ing here runs the command on the SAME stale version,
|
|
287
|
+
// and — because `staleAgainstCachedLatest` still sees the baked CLI_VERSION
|
|
288
|
+
// as behind `latest` — the next invocation installs and re-execs again,
|
|
289
|
+
// forever. The hard version gate already verifies convergence; the soft
|
|
290
|
+
// startup path did not, which is the 5.105.0 → 5.105.1 self-update loop.
|
|
291
|
+
//
|
|
292
|
+
// So verify before announcing success. On non-convergence: mark the target
|
|
293
|
+
// ineffective (suppresses auto-retry for a cooldown), skip the pointless
|
|
294
|
+
// re-exec into a stale copy, and let the command run on the current version.
|
|
295
|
+
const checkConvergence = deps.checkConvergence ?? checkUpdateConvergence;
|
|
296
|
+
const converged = checkConvergence(latestValid);
|
|
297
|
+
if (converged === false) {
|
|
298
|
+
(deps.markIneffective ?? markLatestIneffective)(latest);
|
|
299
|
+
console.error(chalk.dim(` Continuing the ${flavor.noun} on ${current}.`));
|
|
300
|
+
return { action: "update-ineffective", latest };
|
|
301
|
+
}
|
|
281
302
|
const childEnv = { ...env, [REEXEC_GUARD_ENV]: "1" };
|
|
282
303
|
const status = (deps.reexec ?? reexecHq)([...argv.slice(2)], childEnv);
|
|
283
304
|
if (status === null) {
|
|
@@ -10,6 +10,13 @@ declare function isKnownNoninteractiveStatusProbe(argv?: readonly string[]): boo
|
|
|
10
10
|
* command, and warns only if that fails.
|
|
11
11
|
*/
|
|
12
12
|
export declare function staleAgainstCachedLatest(now?: number): string | null;
|
|
13
|
+
/**
|
|
14
|
+
* Record that installing `version` did not move the on-PATH `hq` forward, so
|
|
15
|
+
* {@link staleAgainstCachedLatest} stops auto-retrying it for a cooldown. Called
|
|
16
|
+
* by the self-updater's convergence check. Best-effort: a write failure only
|
|
17
|
+
* means the loop guard is skipped this once, never a broken CLI.
|
|
18
|
+
*/
|
|
19
|
+
export declare function markLatestIneffective(version: string, now?: number): void;
|
|
13
20
|
export declare function refreshVersionCache(): Promise<void>;
|
|
14
21
|
export declare const __test__: {
|
|
15
22
|
CACHE_TTL_MS: number;
|
|
@@ -9,6 +9,12 @@ const CACHE_TTL_MS = 60 * 60 * 1000; // 1h — catch fresh releases within the h
|
|
|
9
9
|
const CACHE_TTL_JITTER_MS = 5 * 60 * 1000; // up to 5m early, to spread a fleet's refreshes off a single instant
|
|
10
10
|
const FETCH_TIMEOUT_MS = 3_000;
|
|
11
11
|
const REFRESH_LOCK_STALE_MS = 10 * 60 * 1000;
|
|
12
|
+
/**
|
|
13
|
+
* How long to stop auto-retrying an `ineffectiveLatest` target. Long enough to
|
|
14
|
+
* break the per-invocation loop on a busy agent box, short enough that a box
|
|
15
|
+
* whose PATH is later fixed recovers on its own without a manual `hq rescue`.
|
|
16
|
+
*/
|
|
17
|
+
const INEFFECTIVE_COOLDOWN_MS = 6 * 60 * 60 * 1000; // 6h
|
|
12
18
|
function cachePath() {
|
|
13
19
|
return path.join(os.homedir(), ".hq", "version-check.json");
|
|
14
20
|
}
|
|
@@ -26,7 +32,16 @@ function readCache() {
|
|
|
26
32
|
typeof parsed.fetchedAt !== "number") {
|
|
27
33
|
return null;
|
|
28
34
|
}
|
|
29
|
-
return {
|
|
35
|
+
return {
|
|
36
|
+
latest: parsed.latest,
|
|
37
|
+
fetchedAt: parsed.fetchedAt,
|
|
38
|
+
...(typeof parsed.ineffectiveLatest === "string"
|
|
39
|
+
? { ineffectiveLatest: parsed.ineffectiveLatest }
|
|
40
|
+
: {}),
|
|
41
|
+
...(typeof parsed.ineffectiveAt === "number"
|
|
42
|
+
? { ineffectiveAt: parsed.ineffectiveAt }
|
|
43
|
+
: {}),
|
|
44
|
+
};
|
|
30
45
|
}
|
|
31
46
|
catch {
|
|
32
47
|
return null;
|
|
@@ -119,8 +134,38 @@ export function staleAgainstCachedLatest(now = Date.now()) {
|
|
|
119
134
|
return null;
|
|
120
135
|
if (!semver.gt(latest, current))
|
|
121
136
|
return null;
|
|
137
|
+
// Loop guard: a previous self-update installed this exact `latest` but the
|
|
138
|
+
// on-PATH `hq` never converged to it (see markLatestIneffective). Retrying
|
|
139
|
+
// the same target re-installs it and re-execs into the same stale copy on
|
|
140
|
+
// every invocation without ever making progress, so suppress it for a
|
|
141
|
+
// cooldown. A newer `latest` (different target) is never suppressed.
|
|
142
|
+
if (entry.ineffectiveLatest === entry.latest &&
|
|
143
|
+
typeof entry.ineffectiveAt === "number" &&
|
|
144
|
+
now - entry.ineffectiveAt <= INEFFECTIVE_COOLDOWN_MS) {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
122
147
|
return entry.latest;
|
|
123
148
|
}
|
|
149
|
+
/**
|
|
150
|
+
* Record that installing `version` did not move the on-PATH `hq` forward, so
|
|
151
|
+
* {@link staleAgainstCachedLatest} stops auto-retrying it for a cooldown. Called
|
|
152
|
+
* by the self-updater's convergence check. Best-effort: a write failure only
|
|
153
|
+
* means the loop guard is skipped this once, never a broken CLI.
|
|
154
|
+
*/
|
|
155
|
+
export function markLatestIneffective(version, now = Date.now()) {
|
|
156
|
+
const entry = readCache();
|
|
157
|
+
if (!entry)
|
|
158
|
+
return;
|
|
159
|
+
// Only mark the target we actually believe is `latest`; marking anything else
|
|
160
|
+
// would risk suppressing a legitimately newer release.
|
|
161
|
+
if (entry.latest !== version)
|
|
162
|
+
return;
|
|
163
|
+
writeCache({
|
|
164
|
+
...entry,
|
|
165
|
+
ineffectiveLatest: version,
|
|
166
|
+
ineffectiveAt: now,
|
|
167
|
+
});
|
|
168
|
+
}
|
|
124
169
|
export async function refreshVersionCache() {
|
|
125
170
|
if (isOptedOut())
|
|
126
171
|
return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@indigoai-us/hq-cli",
|
|
3
|
-
"version": "5.108.
|
|
3
|
+
"version": "5.108.6",
|
|
4
4
|
"description": "HQ by Indigo management CLI — modules and cloud sync",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@aws-sdk/client-iot-data-plane": "^3.1096.0",
|
|
33
33
|
"@aws-sdk/client-s3": "^3.1049.0",
|
|
34
|
-
"@indigoai-us/hq-cloud": "~6.16.
|
|
34
|
+
"@indigoai-us/hq-cloud": "~6.16.11",
|
|
35
35
|
"@indigoai-us/hq-flags-client": "^0.1.2",
|
|
36
36
|
"@indigoai-us/hq-onboarding": "^0.1.0",
|
|
37
37
|
"@sentry/node": "^10.49.0",
|