@indigoai-us/hq-cli 5.50.1 → 5.50.2
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/dist/commands/onboard-warning.d.ts +7 -0
- package/dist/commands/onboard-warning.js +14 -0
- package/dist/commands/onboard.js +5 -5
- package/dist/commands/pack-install.d.ts +12 -0
- package/dist/commands/pack-install.js +74 -3
- package/dist/commands/packs.js +17 -3
- package/dist/types.d.ts +18 -0
- package/dist/utils/pack-contributions.d.ts +7 -0
- package/dist/utils/pack-contributions.js +12 -2
- package/dist/utils/version-gate.d.ts +40 -1
- package/dist/utils/version-gate.js +91 -20
- package/package.json +1 -1
- package/src/commands/onboard-warning.test.ts +26 -0
- package/src/commands/onboard-warning.ts +12 -0
- package/src/commands/onboard.ts +4 -7
- package/src/commands/pack-install.test.ts +144 -0
- package/src/commands/pack-install.ts +86 -1
- package/src/commands/packs.ts +19 -0
- package/src/types.ts +19 -1
- package/src/utils/pack-contributions.test.ts +53 -0
- package/src/utils/pack-contributions.ts +17 -0
- package/src/utils/version-gate.test.ts +122 -0
- package/src/utils/version-gate.ts +109 -13
|
@@ -29,13 +29,17 @@
|
|
|
29
29
|
*/
|
|
30
30
|
|
|
31
31
|
import { spawnSync } from "node:child_process";
|
|
32
|
+
import { readFileSync } from "node:fs";
|
|
33
|
+
import path from "node:path";
|
|
34
|
+
import { fileURLToPath } from "node:url";
|
|
32
35
|
import chalk from "chalk";
|
|
33
|
-
import { CLI_VERSION } from "../cli-version.js";
|
|
36
|
+
import { CLI_NAME, CLI_VERSION } from "../cli-version.js";
|
|
34
37
|
import { DEFAULT_VAULT_API_URL } from "./cognito-session.js";
|
|
35
38
|
|
|
36
39
|
const CLIENT_ID = "hq-cli";
|
|
37
40
|
const ENDPOINT_PATH = "/v1/client-version/check";
|
|
38
41
|
const FETCH_TIMEOUT_MS = 3_000;
|
|
42
|
+
const LATEST_PACKAGE_SPEC = `${CLI_NAME}@latest`;
|
|
39
43
|
|
|
40
44
|
interface VersionCheckResponse {
|
|
41
45
|
clientId: string;
|
|
@@ -53,6 +57,53 @@ function isOptedOut(): boolean {
|
|
|
53
57
|
return process.env.HQ_NO_UPDATE_CHECK === "1";
|
|
54
58
|
}
|
|
55
59
|
|
|
60
|
+
function findRunningPackageRoot(): string | null {
|
|
61
|
+
let dir = path.dirname(fileURLToPath(import.meta.url));
|
|
62
|
+
while (true) {
|
|
63
|
+
try {
|
|
64
|
+
const pkg = JSON.parse(
|
|
65
|
+
readFileSync(path.join(dir, "package.json"), "utf-8"),
|
|
66
|
+
) as { name?: unknown };
|
|
67
|
+
if (pkg.name === CLI_NAME) return dir;
|
|
68
|
+
} catch {
|
|
69
|
+
// Keep walking; compiled installs usually start under dist/.
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const parent = path.dirname(dir);
|
|
73
|
+
if (parent === dir) return null;
|
|
74
|
+
dir = parent;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function npmPrefixFromPackageDir(pkgDir: string): string | null {
|
|
79
|
+
const normalized = pkgDir.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
80
|
+
const segments = normalized.split("/");
|
|
81
|
+
const nodeModulesIndex = segments.lastIndexOf("node_modules");
|
|
82
|
+
if (nodeModulesIndex === -1) return null;
|
|
83
|
+
|
|
84
|
+
const prefixEnd =
|
|
85
|
+
segments[nodeModulesIndex - 1] === "lib"
|
|
86
|
+
? nodeModulesIndex - 1
|
|
87
|
+
: nodeModulesIndex;
|
|
88
|
+
const prefix = segments.slice(0, prefixEnd).join("/");
|
|
89
|
+
if (prefix === "" && normalized.startsWith("/")) return "/";
|
|
90
|
+
return prefix || null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function resolveRunningPrefix(): string | null {
|
|
94
|
+
try {
|
|
95
|
+
const pkgRoot = findRunningPackageRoot();
|
|
96
|
+
if (!pkgRoot) return null;
|
|
97
|
+
return npmPrefixFromPackageDir(pkgRoot);
|
|
98
|
+
} catch {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function buildPrefixedInstallArgv(prefix: string): string[] {
|
|
104
|
+
return ["install", "-g", "--prefix", prefix, LATEST_PACKAGE_SPEC];
|
|
105
|
+
}
|
|
106
|
+
|
|
56
107
|
/**
|
|
57
108
|
* Hit POST /v1/client-version/check. Returns the parsed body on 200, or
|
|
58
109
|
* `null` on any failure (caller treats as "no gate"). Tight 3s timeout —
|
|
@@ -95,13 +146,10 @@ async function fetchVersionDecision(): Promise<VersionCheckResponse | null> {
|
|
|
95
146
|
* forcing a re-invocation would run twice on the same process and feel
|
|
96
147
|
* janky; instead we print a clear "rerun your command" message and exit.
|
|
97
148
|
*/
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
if (parts.length === 0) return { ok: false, detail: "empty command" };
|
|
103
|
-
const cmd = parts[0]!;
|
|
104
|
-
const args = parts.slice(1);
|
|
149
|
+
type UpdateResult = { ok: boolean; detail?: string };
|
|
150
|
+
type UpdateRunner = (cmd: string, args: string[]) => UpdateResult;
|
|
151
|
+
|
|
152
|
+
function runUpdateCommand(cmd: string, args: string[]): UpdateResult {
|
|
105
153
|
try {
|
|
106
154
|
const result = spawnSync(cmd, args, { stdio: "inherit" });
|
|
107
155
|
if (result.status !== 0) {
|
|
@@ -116,6 +164,25 @@ function performUpdate(
|
|
|
116
164
|
}
|
|
117
165
|
}
|
|
118
166
|
|
|
167
|
+
function performUpdateCommand(
|
|
168
|
+
cmd: string,
|
|
169
|
+
args: string[],
|
|
170
|
+
runner: UpdateRunner = runUpdateCommand,
|
|
171
|
+
): UpdateResult {
|
|
172
|
+
return runner(cmd, args);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function performUpdate(
|
|
176
|
+
command: string,
|
|
177
|
+
runner: UpdateRunner = runUpdateCommand,
|
|
178
|
+
): UpdateResult {
|
|
179
|
+
const parts = command.split(/\s+/).filter(Boolean);
|
|
180
|
+
if (parts.length === 0) return { ok: false, detail: "empty command" };
|
|
181
|
+
const cmd = parts[0]!;
|
|
182
|
+
const args = parts.slice(1);
|
|
183
|
+
return performUpdateCommand(cmd, args, runner);
|
|
184
|
+
}
|
|
185
|
+
|
|
119
186
|
/**
|
|
120
187
|
* Soft notify when the server says we're below `latestVersion` but still ≥
|
|
121
188
|
* `minVersion`. Single chalk-yellow line on stderr; never blocks.
|
|
@@ -140,7 +207,14 @@ function nudgeUpdateRecommended(decision: VersionCheckResponse): void {
|
|
|
140
207
|
* 0 — update succeeded; user must rerun their command
|
|
141
208
|
* 75 — update failed (EX_TEMPFAIL; common for sudo/EACCES on system npm)
|
|
142
209
|
*/
|
|
143
|
-
function enforceUpdateRequired(
|
|
210
|
+
function enforceUpdateRequired(
|
|
211
|
+
decision: VersionCheckResponse,
|
|
212
|
+
deps: {
|
|
213
|
+
performUpdateString?: (command: string) => UpdateResult;
|
|
214
|
+
resolvePrefix?: () => string | null;
|
|
215
|
+
runner?: UpdateRunner;
|
|
216
|
+
} = {},
|
|
217
|
+
): never {
|
|
144
218
|
const banner = chalk.red.bold(
|
|
145
219
|
`✗ hq-cli ${decision.currentVersion} is below the minimum required version (${decision.minVersion}).`,
|
|
146
220
|
);
|
|
@@ -148,7 +222,8 @@ function enforceUpdateRequired(decision: VersionCheckResponse): never {
|
|
|
148
222
|
if (decision.message) console.error(chalk.dim(` ${decision.message}`));
|
|
149
223
|
|
|
150
224
|
const command = decision.updateCommand;
|
|
151
|
-
|
|
225
|
+
const prefix = (deps.resolvePrefix ?? resolveRunningPrefix)();
|
|
226
|
+
if (!command && !prefix) {
|
|
152
227
|
console.error(
|
|
153
228
|
chalk.red(
|
|
154
229
|
" No updateCommand provided by hq-pro — see https://hq.indigo.ai/docs/cli-update for manual steps.",
|
|
@@ -160,13 +235,28 @@ function enforceUpdateRequired(decision: VersionCheckResponse): never {
|
|
|
160
235
|
process.exit(75);
|
|
161
236
|
}
|
|
162
237
|
|
|
163
|
-
|
|
164
|
-
const result =
|
|
238
|
+
const runner = deps.runner ?? runUpdateCommand;
|
|
239
|
+
const result = prefix
|
|
240
|
+
? (() => {
|
|
241
|
+
const args = buildPrefixedInstallArgv(prefix);
|
|
242
|
+
console.error(chalk.dim(` Installing into npm prefix: ${prefix}`));
|
|
243
|
+
console.error(chalk.dim(` Running: npm ${args.join(" ")}`));
|
|
244
|
+
return performUpdateCommand("npm", args, runner);
|
|
245
|
+
})()
|
|
246
|
+
: (() => {
|
|
247
|
+
console.error(chalk.dim(` Running: ${command}`));
|
|
248
|
+
return deps.performUpdateString
|
|
249
|
+
? deps.performUpdateString(command!)
|
|
250
|
+
: performUpdate(command!, runner);
|
|
251
|
+
})();
|
|
165
252
|
if (!result.ok) {
|
|
166
253
|
console.error(
|
|
167
254
|
chalk.red(`✗ Update failed${result.detail ? `: ${result.detail}` : ""}.`),
|
|
168
255
|
);
|
|
169
|
-
|
|
256
|
+
const manual = prefix
|
|
257
|
+
? `npm ${buildPrefixedInstallArgv(prefix).join(" ")}`
|
|
258
|
+
: command!;
|
|
259
|
+
console.error(chalk.dim(` Try manually: ${manual}`));
|
|
170
260
|
process.exit(75);
|
|
171
261
|
}
|
|
172
262
|
|
|
@@ -215,5 +305,11 @@ export const __test__ = {
|
|
|
215
305
|
CLIENT_ID,
|
|
216
306
|
ENDPOINT_PATH,
|
|
217
307
|
FETCH_TIMEOUT_MS,
|
|
308
|
+
buildPrefixedInstallArgv,
|
|
309
|
+
enforceUpdateRequired,
|
|
310
|
+
npmPrefixFromPackageDir,
|
|
218
311
|
performUpdate,
|
|
312
|
+
performUpdateCommand,
|
|
313
|
+
runUpdateCommand,
|
|
314
|
+
resolveRunningPrefix,
|
|
219
315
|
};
|