@indigoai-us/hq-cli 5.50.1 → 5.51.0
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/bin/hq-auth-refresh.d.ts +1 -1
- package/dist/bin/hq-auth-refresh.js +5 -2
- package/dist/commands/members.d.ts +17 -0
- package/dist/commands/members.js +65 -28
- 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/commands/people.d.ts +26 -1
- package/dist/commands/people.js +70 -7
- package/dist/commands/secrets-scope.d.ts +20 -0
- package/dist/commands/secrets-scope.js +19 -0
- package/dist/commands/secrets.js +21 -6
- package/dist/index.d.ts +1 -1
- package/dist/index.js +44 -14
- package/dist/node-preflight.d.ts +39 -0
- package/dist/node-preflight.js +55 -0
- package/dist/sentry.d.ts +12 -0
- package/dist/sentry.js +19 -3
- package/dist/types.d.ts +18 -0
- package/dist/utils/epipe.d.ts +8 -0
- package/dist/utils/epipe.js +30 -0
- package/dist/utils/intercepted-process-exit.d.ts +7 -0
- package/dist/utils/intercepted-process-exit.js +38 -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/e2e/cli.test.ts +35 -0
- package/package.json +1 -1
- package/src/bin/hq-auth-refresh.ts +3 -0
- package/src/commands/members.test.ts +176 -0
- package/src/commands/members.ts +113 -28
- 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/commands/people.test.ts +212 -5
- package/src/commands/people.ts +141 -5
- package/src/commands/secrets-scope.test.ts +56 -0
- package/src/commands/secrets-scope.ts +32 -0
- package/src/commands/secrets.ts +24 -10
- package/src/index.ts +40 -12
- package/src/node-preflight.test.ts +60 -0
- package/src/node-preflight.ts +67 -0
- package/src/sentry-epipe.test.ts +37 -0
- package/src/sentry-release.test.ts +54 -0
- package/src/sentry.ts +21 -1
- package/src/types.ts +19 -1
- package/src/utils/epipe.test.ts +28 -0
- package/src/utils/epipe.ts +29 -0
- package/src/utils/intercepted-process-exit.test.ts +37 -0
- package/src/utils/intercepted-process-exit.ts +36 -0
- 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
package/dist/commands/secrets.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="
|
|
2
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="96461f1a-6d5b-5dc0-b00a-0a9fa30a0dad")}catch(e){}}();
|
|
3
3
|
import chalk from "chalk";
|
|
4
4
|
import * as readline from "node:readline";
|
|
5
5
|
import { spawn } from "node:child_process";
|
|
@@ -8,6 +8,7 @@ import { ensureCognitoToken } from "../utils/cognito-session.js";
|
|
|
8
8
|
import { DEFAULT_SECRETS_CACHE_TTL_MS, readCache, writeCache, removeCacheEntry, clearAllCache, } from "../utils/secrets-cache.js";
|
|
9
9
|
import { computeSha256 } from "../utils/integrity.js";
|
|
10
10
|
import { SECRET_NAME_PATTERN, GROUP_ID_PATTERN } from "./_patterns.js";
|
|
11
|
+
import { describeSecretsScope, formatSecretSaved, formatSecretsListEmpty, formatSecretsListHeader, } from "./secrets-scope.js";
|
|
11
12
|
import { vaultApiFetch, getCompanyUid, getEntityUid, } from "../utils/vault-api.js";
|
|
12
13
|
export { vaultApiFetch, getCompanyUid, getEntityUid };
|
|
13
14
|
function scopeOpts(opts) {
|
|
@@ -344,7 +345,13 @@ export function registerSecretsCommand(program) {
|
|
|
344
345
|
process.exit(1);
|
|
345
346
|
}
|
|
346
347
|
const token = await ensureCognitoToken();
|
|
347
|
-
const
|
|
348
|
+
const scope = scopeOpts(secrets.opts());
|
|
349
|
+
const companyUid = await getEntityUid(token, scope);
|
|
350
|
+
const scopeLabel = describeSecretsScope({
|
|
351
|
+
personal: scope.personal,
|
|
352
|
+
companySlug: scope.companySlug,
|
|
353
|
+
companyUid,
|
|
354
|
+
});
|
|
348
355
|
const res = await vaultApiFetch({
|
|
349
356
|
token,
|
|
350
357
|
path: `/secrets/${encodeURIComponent(companyUid)}`,
|
|
@@ -357,7 +364,7 @@ export function registerSecretsCommand(program) {
|
|
|
357
364
|
process.exit(1);
|
|
358
365
|
}
|
|
359
366
|
removeCacheEntry(companyUid, name);
|
|
360
|
-
console.log(chalk.green(
|
|
367
|
+
console.log(chalk.green(formatSecretSaved(name, scopeLabel)));
|
|
361
368
|
}
|
|
362
369
|
catch (err) {
|
|
363
370
|
console.error(chalk.red("Error:"), err instanceof Error ? err.message : String(err));
|
|
@@ -487,7 +494,13 @@ export function registerSecretsCommand(program) {
|
|
|
487
494
|
normalizedPrefix = normalized;
|
|
488
495
|
}
|
|
489
496
|
const token = await ensureCognitoToken();
|
|
490
|
-
const
|
|
497
|
+
const scope = scopeOpts(secrets.opts());
|
|
498
|
+
const companyUid = await getEntityUid(token, scope);
|
|
499
|
+
const scopeLabel = describeSecretsScope({
|
|
500
|
+
personal: scope.personal,
|
|
501
|
+
companySlug: scope.companySlug,
|
|
502
|
+
companyUid,
|
|
503
|
+
});
|
|
491
504
|
const query = {};
|
|
492
505
|
if (normalizedPrefix) {
|
|
493
506
|
query.prefix = normalizedPrefix;
|
|
@@ -504,7 +517,7 @@ export function registerSecretsCommand(program) {
|
|
|
504
517
|
}
|
|
505
518
|
const data = (await res.json());
|
|
506
519
|
if (data.secrets.length === 0) {
|
|
507
|
-
console.log(chalk.dim(
|
|
520
|
+
console.log(chalk.dim(formatSecretsListEmpty(scopeLabel)));
|
|
508
521
|
return;
|
|
509
522
|
}
|
|
510
523
|
const nameWidth = Math.max(4, ...data.secrets.map((s) => s.name.length));
|
|
@@ -514,6 +527,7 @@ export function registerSecretsCommand(program) {
|
|
|
514
527
|
if (hasPermission) {
|
|
515
528
|
const accessWidth = Math.max(6, ...data.secrets.map((s) => (s.permission ?? "-").length));
|
|
516
529
|
const header = `${"NAME".padEnd(nameWidth)} ${"ACCESS".padEnd(accessWidth)} ${"TIER".padEnd(tierWidth)} ${"SCRIPT LOCK".padEnd(scriptLockWidth)} LAST MODIFIED`;
|
|
530
|
+
console.log(chalk.dim(formatSecretsListHeader(scopeLabel)));
|
|
517
531
|
console.log(chalk.bold(header));
|
|
518
532
|
for (const s of data.secrets) {
|
|
519
533
|
const access = s.permission ?? "-";
|
|
@@ -525,6 +539,7 @@ export function registerSecretsCommand(program) {
|
|
|
525
539
|
}
|
|
526
540
|
else {
|
|
527
541
|
const header = `${"NAME".padEnd(nameWidth)} ${"TIER".padEnd(tierWidth)} ${"SCRIPT LOCK".padEnd(scriptLockWidth)} LAST MODIFIED`;
|
|
542
|
+
console.log(chalk.dim(formatSecretsListHeader(scopeLabel)));
|
|
528
543
|
console.log(chalk.bold(header));
|
|
529
544
|
for (const s of data.secrets) {
|
|
530
545
|
const tier = normalizeSecretTier(s.tier);
|
|
@@ -1148,4 +1163,4 @@ export function registerSecretsCommand(program) {
|
|
|
1148
1163
|
});
|
|
1149
1164
|
}
|
|
1150
1165
|
//# sourceMappingURL=secrets.js.map
|
|
1151
|
-
//# debugId=
|
|
1166
|
+
//# debugId=96461f1a-6d5b-5dc0-b00a-0a9fa30a0dad
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -2,8 +2,11 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* HQ CLI - Module management, package management, and cloud sync for HQ
|
|
4
4
|
*/
|
|
5
|
+
// MUST be first: guard the Node version before any dependency that needs a
|
|
6
|
+
// Node 20+ API (e.g. util.styleText) or a newer native ABI is evaluated.
|
|
5
7
|
|
|
6
|
-
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="
|
|
8
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="82c44c14-a953-5644-9bf2-dd2c6d487d63")}catch(e){}}();
|
|
9
|
+
import "./node-preflight.js";
|
|
7
10
|
import { Command } from "commander";
|
|
8
11
|
import { initSentry, Sentry } from "./sentry.js";
|
|
9
12
|
import { registerAddCommand } from "./commands/add.js";
|
|
@@ -47,12 +50,18 @@ import { registerRescueCommand } from "./commands/rescue.js";
|
|
|
47
50
|
import { registerMcpCommand } from "./commands/mcp-status.js";
|
|
48
51
|
import { sanitizeArgv } from "./utils/feedback-diagnostics.js";
|
|
49
52
|
import { environmentalFsErrorMessage } from "./utils/environmental-error.js";
|
|
53
|
+
import { isEpipe } from "./utils/epipe.js";
|
|
54
|
+
import { isInterceptedProcessExit } from "./utils/intercepted-process-exit.js";
|
|
50
55
|
import { maybeWarnNewVersion, refreshVersionCache, } from "./utils/version-check.js";
|
|
51
56
|
import { enforceVersionGate, shouldSkipGate, } from "./utils/version-gate.js";
|
|
52
57
|
import { CLI_VERSION } from "./cli-version.js";
|
|
53
|
-
// Swallow EPIPE when a downstream reader (e.g. `source <(…)`, `| head`) closes
|
|
58
|
+
// Swallow EPIPE when a downstream reader (e.g. `source <(…)`, `| head`) closes
|
|
59
|
+
// the pipe early. This covers the ASYNC path — an 'error' event emitted on the
|
|
60
|
+
// stream. The SYNCHRONOUS path (a `write EPIPE` thrown straight out of
|
|
61
|
+
// console.log inside a command) is handled in the top-level catch below; both
|
|
62
|
+
// share `isEpipe` (HQ-6B).
|
|
54
63
|
const onPipeError = (err) => {
|
|
55
|
-
if (err
|
|
64
|
+
if (isEpipe(err)) {
|
|
56
65
|
process.exit(0);
|
|
57
66
|
}
|
|
58
67
|
throw err;
|
|
@@ -182,19 +191,40 @@ registerMcpCommand(program);
|
|
|
182
191
|
await program.parseAsync();
|
|
183
192
|
}
|
|
184
193
|
catch (err) {
|
|
185
|
-
// A
|
|
186
|
-
//
|
|
187
|
-
//
|
|
188
|
-
//
|
|
189
|
-
//
|
|
190
|
-
|
|
191
|
-
if (
|
|
192
|
-
process.
|
|
194
|
+
// A broken pipe (EPIPE) means the reader of `hq`'s output closed it early
|
|
195
|
+
// (`hq … | head`, `source <(hq …)`, a parent that exited). That is normal
|
|
196
|
+
// Unix behavior with no user-facing degradation — exit cleanly (0) and
|
|
197
|
+
// skip Sentry capture instead of shipping a fatal (HQ-6B). A synchronous
|
|
198
|
+
// `write EPIPE` thrown out of console.log lands here rather than on the
|
|
199
|
+
// stream 'error' listener above.
|
|
200
|
+
if (isEpipe(err)) {
|
|
201
|
+
process.exitCode = 0;
|
|
202
|
+
}
|
|
203
|
+
else if (isInterceptedProcessExit(err)) {
|
|
204
|
+
// A security/audit FUZZ harness replaced `process.exit` with a throw so it
|
|
205
|
+
// can keep exercising the binary. Commander calling `process.exit` for
|
|
206
|
+
// normal CLI control flow (e.g. an unknown command → exit 1) then surfaces
|
|
207
|
+
// here as that synthetic marker. It is a test-harness artifact, NOT an
|
|
208
|
+
// hq-cli defect — a real user's `process.exit` just exits, so nothing is
|
|
209
|
+
// thrown or captured. Skip Sentry capture (no signal, no user-facing
|
|
210
|
+
// degradation) and preserve the intended non-zero exit (HQ-CLI-3).
|
|
211
|
+
process.exitCode = 1;
|
|
193
212
|
}
|
|
194
213
|
else {
|
|
195
|
-
|
|
214
|
+
// A full disk / exhausted quota / read-only filesystem is the user's
|
|
215
|
+
// machine, not an HQ code defect. Surface a clear, actionable message and
|
|
216
|
+
// skip Sentry capture so one full disk doesn't flood the tracker with
|
|
217
|
+
// identical, unfixable crash reports (HQ-CLI-2). Genuine errors still go
|
|
218
|
+
// to Sentry and still exit 1.
|
|
219
|
+
const envMsg = environmentalFsErrorMessage(err);
|
|
220
|
+
if (envMsg) {
|
|
221
|
+
process.stderr.write(`hq: ${envMsg}\n`);
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
Sentry.captureException(err);
|
|
225
|
+
}
|
|
226
|
+
process.exitCode = 1;
|
|
196
227
|
}
|
|
197
|
-
process.exitCode = 1;
|
|
198
228
|
}
|
|
199
229
|
finally {
|
|
200
230
|
// Release health: finalize the per-run session before the flush.
|
|
@@ -203,4 +233,4 @@ registerMcpCommand(program);
|
|
|
203
233
|
}
|
|
204
234
|
})();
|
|
205
235
|
//# sourceMappingURL=index.js.map
|
|
206
|
-
//# debugId=
|
|
236
|
+
//# debugId=82c44c14-a953-5644-9bf2-dd2c6d487d63
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime Node.js version guard for the hq CLI.
|
|
3
|
+
*
|
|
4
|
+
* HQ tooling requires Node.js 20 or newer. On older runtimes (notably Node 18)
|
|
5
|
+
* the CLI dies with cryptic failures long before reaching any of its own code:
|
|
6
|
+
* a native-module ABI mismatch from a prebuilt dependency, and a missing
|
|
7
|
+
* `util.styleText` (added in Node 20). Those errors give the user no hint that
|
|
8
|
+
* the real problem is just an old Node.
|
|
9
|
+
*
|
|
10
|
+
* This module exists to fail fast with an actionable message instead. It is
|
|
11
|
+
* imported FIRST by every CLI entry point (`index.ts`, `bin/hq-auth-refresh.ts`)
|
|
12
|
+
* so the check runs before commander, Sentry, or any dependency that needs a
|
|
13
|
+
* Node 20+ API or a newer native ABI is evaluated. ES modules evaluate their
|
|
14
|
+
* imports in source order, so as long as this is the first import in the entry
|
|
15
|
+
* module, the guard short-circuits an unsupported runtime cleanly.
|
|
16
|
+
*
|
|
17
|
+
* Keep this file dependency-free — it must not import anything that could itself
|
|
18
|
+
* fail to load on the very runtime it is trying to detect.
|
|
19
|
+
*/
|
|
20
|
+
export declare const MIN_NODE_MAJOR = 20;
|
|
21
|
+
export interface NodeVersionCheck {
|
|
22
|
+
ok: boolean;
|
|
23
|
+
major: number;
|
|
24
|
+
message?: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Pure check: is the given Node version string (e.g. "18.19.0") supported?
|
|
28
|
+
* Defaults to the running runtime's version. An unparseable version is treated
|
|
29
|
+
* as supported so we never block a user on a version string we can't read.
|
|
30
|
+
*/
|
|
31
|
+
export declare function checkNodeVersion(versionString?: string): NodeVersionCheck;
|
|
32
|
+
/**
|
|
33
|
+
* Side-effecting guard run on import: prints the upgrade message to stderr and
|
|
34
|
+
* exits 1 on an unsupported runtime. A no-op on Node 20+. Set
|
|
35
|
+
* `HQ_SKIP_NODE_PREFLIGHT=1` to bypass (used by the test runner, which already
|
|
36
|
+
* runs on a supported Node).
|
|
37
|
+
*/
|
|
38
|
+
export declare function enforceNodeVersion(): void;
|
|
39
|
+
//# sourceMappingURL=node-preflight.d.ts.map
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime Node.js version guard for the hq CLI.
|
|
3
|
+
*
|
|
4
|
+
* HQ tooling requires Node.js 20 or newer. On older runtimes (notably Node 18)
|
|
5
|
+
* the CLI dies with cryptic failures long before reaching any of its own code:
|
|
6
|
+
* a native-module ABI mismatch from a prebuilt dependency, and a missing
|
|
7
|
+
* `util.styleText` (added in Node 20). Those errors give the user no hint that
|
|
8
|
+
* the real problem is just an old Node.
|
|
9
|
+
*
|
|
10
|
+
* This module exists to fail fast with an actionable message instead. It is
|
|
11
|
+
* imported FIRST by every CLI entry point (`index.ts`, `bin/hq-auth-refresh.ts`)
|
|
12
|
+
* so the check runs before commander, Sentry, or any dependency that needs a
|
|
13
|
+
* Node 20+ API or a newer native ABI is evaluated. ES modules evaluate their
|
|
14
|
+
* imports in source order, so as long as this is the first import in the entry
|
|
15
|
+
* module, the guard short-circuits an unsupported runtime cleanly.
|
|
16
|
+
*
|
|
17
|
+
* Keep this file dependency-free — it must not import anything that could itself
|
|
18
|
+
* fail to load on the very runtime it is trying to detect.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="7d63229d-ff96-5511-9601-53c556487763")}catch(e){}}();
|
|
22
|
+
export const MIN_NODE_MAJOR = 20;
|
|
23
|
+
/**
|
|
24
|
+
* Pure check: is the given Node version string (e.g. "18.19.0") supported?
|
|
25
|
+
* Defaults to the running runtime's version. An unparseable version is treated
|
|
26
|
+
* as supported so we never block a user on a version string we can't read.
|
|
27
|
+
*/
|
|
28
|
+
export function checkNodeVersion(versionString = process.versions.node) {
|
|
29
|
+
const major = Number.parseInt(String(versionString).split(".")[0] ?? "", 10);
|
|
30
|
+
if (!Number.isFinite(major) || major >= MIN_NODE_MAJOR) {
|
|
31
|
+
return { ok: true, major };
|
|
32
|
+
}
|
|
33
|
+
const message = `hq requires Node.js ${MIN_NODE_MAJOR} or newer — you are running Node ${versionString}.\n` +
|
|
34
|
+
`Older versions fail with native-module ABI mismatches and missing APIs.\n` +
|
|
35
|
+
`Please upgrade to Node ${MIN_NODE_MAJOR}+ (https://nodejs.org/) and run hq again.`;
|
|
36
|
+
return { ok: false, major, message };
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Side-effecting guard run on import: prints the upgrade message to stderr and
|
|
40
|
+
* exits 1 on an unsupported runtime. A no-op on Node 20+. Set
|
|
41
|
+
* `HQ_SKIP_NODE_PREFLIGHT=1` to bypass (used by the test runner, which already
|
|
42
|
+
* runs on a supported Node).
|
|
43
|
+
*/
|
|
44
|
+
export function enforceNodeVersion() {
|
|
45
|
+
if (process.env.HQ_SKIP_NODE_PREFLIGHT)
|
|
46
|
+
return;
|
|
47
|
+
const result = checkNodeVersion();
|
|
48
|
+
if (!result.ok && result.message) {
|
|
49
|
+
process.stderr.write(`${result.message}\n`);
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
enforceNodeVersion();
|
|
54
|
+
//# sourceMappingURL=node-preflight.js.map
|
|
55
|
+
//# debugId=7d63229d-ff96-5511-9601-53c556487763
|
package/dist/sentry.d.ts
CHANGED
|
@@ -1,4 +1,16 @@
|
|
|
1
1
|
import * as Sentry from "@sentry/node";
|
|
2
|
+
import type { ErrorEvent, EventHint } from "@sentry/node";
|
|
3
|
+
/**
|
|
4
|
+
* Drop broken-pipe (EPIPE) crashes before scrubbing/send. A closed downstream
|
|
5
|
+
* reader (`hq … | head`, `source <(hq …)`, a parent that exited) is normal
|
|
6
|
+
* Unix behavior with no user-facing degradation — never a defect to report.
|
|
7
|
+
* This is the path-independent catch-all for HQ-6B: whichever way an EPIPE
|
|
8
|
+
* surfaces (a synchronous throw into the command catch, an async stream
|
|
9
|
+
* 'error', or a write outside the top-level try), it never ships a fatal. The
|
|
10
|
+
* clean-exit handlers in index.ts still keep exit code 0 for the common paths.
|
|
11
|
+
* Consistent with the swallow-EPIPE posture established in #138.
|
|
12
|
+
*/
|
|
13
|
+
export declare function epipeAwareBeforeSend(event: ErrorEvent, hint: EventHint): ErrorEvent | null;
|
|
2
14
|
export declare function initSentry(): void;
|
|
3
15
|
export { Sentry };
|
|
4
16
|
//# sourceMappingURL=sentry.d.ts.map
|
package/dist/sentry.js
CHANGED
|
@@ -1,11 +1,27 @@
|
|
|
1
1
|
|
|
2
|
-
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="
|
|
2
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="a2ba6d5a-af8a-5512-9ce8-ced925ad5505")}catch(e){}}();
|
|
3
3
|
import * as Sentry from "@sentry/node";
|
|
4
4
|
import { BUNDLED_DSN } from "./sentry-dsn.generated.js";
|
|
5
5
|
import { beforeSend } from "./sentry-before-send.js";
|
|
6
6
|
import { beforeBreadcrumb } from "./utils/breadcrumb-buffer.js";
|
|
7
7
|
import { CLI_VERSION } from "./cli-version.js";
|
|
8
8
|
import { getCachedSentryUser } from "./utils/sentry-identity.js";
|
|
9
|
+
import { isEpipe } from "./utils/epipe.js";
|
|
10
|
+
/**
|
|
11
|
+
* Drop broken-pipe (EPIPE) crashes before scrubbing/send. A closed downstream
|
|
12
|
+
* reader (`hq … | head`, `source <(hq …)`, a parent that exited) is normal
|
|
13
|
+
* Unix behavior with no user-facing degradation — never a defect to report.
|
|
14
|
+
* This is the path-independent catch-all for HQ-6B: whichever way an EPIPE
|
|
15
|
+
* surfaces (a synchronous throw into the command catch, an async stream
|
|
16
|
+
* 'error', or a write outside the top-level try), it never ships a fatal. The
|
|
17
|
+
* clean-exit handlers in index.ts still keep exit code 0 for the common paths.
|
|
18
|
+
* Consistent with the swallow-EPIPE posture established in #138.
|
|
19
|
+
*/
|
|
20
|
+
export function epipeAwareBeforeSend(event, hint) {
|
|
21
|
+
if (isEpipe(hint?.originalException))
|
|
22
|
+
return null;
|
|
23
|
+
return beforeSend(event, hint);
|
|
24
|
+
}
|
|
9
25
|
export function initSentry() {
|
|
10
26
|
const dsn = BUNDLED_DSN || process.env.SENTRY_DSN;
|
|
11
27
|
if (!dsn)
|
|
@@ -20,7 +36,7 @@ export function initSentry() {
|
|
|
20
36
|
initialScope: {
|
|
21
37
|
tags: { repo: "hq-cli" },
|
|
22
38
|
},
|
|
23
|
-
beforeSend,
|
|
39
|
+
beforeSend: epipeAwareBeforeSend,
|
|
24
40
|
beforeBreadcrumb,
|
|
25
41
|
});
|
|
26
42
|
// Attribute events to the logged-in HQ identity (best-effort; null when not
|
|
@@ -35,4 +51,4 @@ export function initSentry() {
|
|
|
35
51
|
}
|
|
36
52
|
export { Sentry };
|
|
37
53
|
//# sourceMappingURL=sentry.js.map
|
|
38
|
-
//# debugId=
|
|
54
|
+
//# debugId=a2ba6d5a-af8a-5512-9ce8-ced925ad5505
|
package/dist/types.d.ts
CHANGED
|
@@ -75,13 +75,31 @@ export interface PackAuthor {
|
|
|
75
75
|
handle: string;
|
|
76
76
|
displayName: string;
|
|
77
77
|
}
|
|
78
|
+
/**
|
|
79
|
+
* A pack-to-pack dependency (M0). OPTIONAL and backwards-compatible — packs
|
|
80
|
+
* published before this field omit `requires.packs` and install unchanged. When
|
|
81
|
+
* present, each entry names another content pack that MUST already be installed
|
|
82
|
+
* before this one (enforced at install time by `assertPackDependencies`, which
|
|
83
|
+
* tracks installed packs by FILESYSTEM PRESENCE — not `modules.yaml`). `version`
|
|
84
|
+
* is an optional semver RANGE the installed dependency must satisfy.
|
|
85
|
+
*/
|
|
86
|
+
export interface PackDependency {
|
|
87
|
+
name: string;
|
|
88
|
+
version?: string;
|
|
89
|
+
}
|
|
78
90
|
export interface PackManifest {
|
|
79
91
|
name: string;
|
|
80
92
|
version: string;
|
|
81
93
|
publisher: string;
|
|
82
94
|
access: 'public' | 'private';
|
|
95
|
+
/**
|
|
96
|
+
* Host + pack prerequisites. `hqCore` is a required semver RANGE the host HQ
|
|
97
|
+
* must satisfy. `packs` (M0) is an OPTIONAL list of other content packs that
|
|
98
|
+
* must be installed first — see PackDependency.
|
|
99
|
+
*/
|
|
83
100
|
requires: {
|
|
84
101
|
hqCore: string;
|
|
102
|
+
packs?: PackDependency[];
|
|
85
103
|
};
|
|
86
104
|
contributes: Partial<Record<PackContributeKey, string[]>>;
|
|
87
105
|
description?: string;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* True when `err` is a Node EPIPE error — the pipe `hq` was writing to was
|
|
3
|
+
* closed by its reader. Callers should treat this as a clean, expected exit
|
|
4
|
+
* (code 0) and SKIP Sentry capture: there is no defect and no user-facing
|
|
5
|
+
* degradation to fix.
|
|
6
|
+
*/
|
|
7
|
+
export declare function isEpipe(err: unknown): boolean;
|
|
8
|
+
//# sourceMappingURL=epipe.d.ts.map
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// src/utils/epipe.ts
|
|
2
|
+
//
|
|
3
|
+
// Classify the "broken pipe" (EPIPE) error: a downstream reader closed the
|
|
4
|
+
// pipe before the CLI finished writing to stdout/stderr. This is normal,
|
|
5
|
+
// expected Unix behavior — `hq … | head`, `source <(hq …)`, or a parent
|
|
6
|
+
// process that exits while `hq` is still printing — NOT an HQ code defect.
|
|
7
|
+
//
|
|
8
|
+
// HQ-6B: a synchronous `write EPIPE` thrown from `console.log` (the Sentry
|
|
9
|
+
// console-instrumentation wraps it) propagated out of an awaited command into
|
|
10
|
+
// the CLI's top-level `catch`, which captured it via `Sentry.captureException`
|
|
11
|
+
// and shipped a fatal — even though the only thing that happened is the
|
|
12
|
+
// consumer of `hq`'s output went away. The async path (an emitted stream
|
|
13
|
+
// 'error' event) was already handled by the stdout/stderr listeners in
|
|
14
|
+
// index.ts; this predicate closes the synchronous-throw path too, mirroring
|
|
15
|
+
// the environmental-error carve-out (HQ-CLI-2).
|
|
16
|
+
/**
|
|
17
|
+
* True when `err` is a Node EPIPE error — the pipe `hq` was writing to was
|
|
18
|
+
* closed by its reader. Callers should treat this as a clean, expected exit
|
|
19
|
+
* (code 0) and SKIP Sentry capture: there is no defect and no user-facing
|
|
20
|
+
* degradation to fix.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="3305e402-0237-55ac-a6d4-11143bc69b83")}catch(e){}}();
|
|
24
|
+
export function isEpipe(err) {
|
|
25
|
+
return (typeof err === "object" &&
|
|
26
|
+
err !== null &&
|
|
27
|
+
err.code === "EPIPE");
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=epipe.js.map
|
|
30
|
+
//# debugId=3305e402-0237-55ac-a6d4-11143bc69b83
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* True when `err` is a fuzz/audit harness's intercepted-`process.exit` marker
|
|
3
|
+
* rather than a genuine fault. Callers should SKIP Sentry capture (no defect, no
|
|
4
|
+
* user-facing degradation) while preserving the intended exit code.
|
|
5
|
+
*/
|
|
6
|
+
export declare function isInterceptedProcessExit(err: unknown): boolean;
|
|
7
|
+
//# sourceMappingURL=intercepted-process-exit.d.ts.map
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// src/utils/intercepted-process-exit.ts
|
|
2
|
+
//
|
|
3
|
+
// Classify the "intercepted process.exit" synthetic error (HQ-CLI-3).
|
|
4
|
+
//
|
|
5
|
+
// Security/audit FUZZ harnesses (e.g. `/opt/audit/fuzz-exports.js`) monkey-patch
|
|
6
|
+
// `process.exit` to THROW a marker instead of actually terminating, so the
|
|
7
|
+
// fuzzer can keep exercising a binary's entrypoints. When such a harness drives
|
|
8
|
+
// `hq` (e.g. `hq <unknown-command>`), commander legitimately calls
|
|
9
|
+
// `process.exit(1)` — and the patched exit turns that normal CLI control-flow
|
|
10
|
+
// into a thrown `Error: fuzz-intercepted-process-exit` that propagates out of
|
|
11
|
+
// `program.parseAsync()` into the CLI's top-level catch, which then shipped it to
|
|
12
|
+
// Sentry as a fatal.
|
|
13
|
+
//
|
|
14
|
+
// This is a test-harness artifact, NOT an hq-cli defect: a real user's
|
|
15
|
+
// `process.exit` exits the process and nothing is ever thrown or captured.
|
|
16
|
+
// There is no user-facing degradation to fix, so the top-level catch skips
|
|
17
|
+
// Sentry capture for it — the genuine "expected, non-actionable" carve-out,
|
|
18
|
+
// mirroring the EPIPE (HQ-6B) and environmental-FS (HQ-CLI-2) carve-outs.
|
|
19
|
+
/** The marker a fuzz/audit harness throws in place of a real `process.exit`. */
|
|
20
|
+
|
|
21
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="55c7e3b5-a8bc-50fd-835b-127a19172aa1")}catch(e){}}();
|
|
22
|
+
const INTERCEPTED_PROCESS_EXIT_MARKER = "fuzz-intercepted-process-exit";
|
|
23
|
+
/**
|
|
24
|
+
* True when `err` is a fuzz/audit harness's intercepted-`process.exit` marker
|
|
25
|
+
* rather than a genuine fault. Callers should SKIP Sentry capture (no defect, no
|
|
26
|
+
* user-facing degradation) while preserving the intended exit code.
|
|
27
|
+
*/
|
|
28
|
+
export function isInterceptedProcessExit(err) {
|
|
29
|
+
if (typeof err === "object" && err !== null) {
|
|
30
|
+
const message = err.message;
|
|
31
|
+
if (typeof message === "string" && message.includes(INTERCEPTED_PROCESS_EXIT_MARKER)) {
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return typeof err === "string" && err.includes(INTERCEPTED_PROCESS_EXIT_MARKER);
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=intercepted-process-exit.js.map
|
|
38
|
+
//# debugId=55c7e3b5-a8bc-50fd-835b-127a19172aa1
|
|
@@ -100,6 +100,13 @@ export declare function readPackManifest(packDir: string): {
|
|
|
100
100
|
* of truth for installed content packs.
|
|
101
101
|
*/
|
|
102
102
|
export declare function listInstalledPacks(hqRoot: string): InstalledPack[];
|
|
103
|
+
/**
|
|
104
|
+
* Installed packs that declare `name` in their `requires.packs` (M0). Pure over
|
|
105
|
+
* the supplied list — the uninstall dependents guard calls this with
|
|
106
|
+
* `listInstalledPacks(hqRoot)`. Excludes the pack named `name` itself, so a
|
|
107
|
+
* self-reference never counts as its own dependent.
|
|
108
|
+
*/
|
|
109
|
+
export declare function findDependentPacks(installed: InstalledPack[], name: string): InstalledPack[];
|
|
103
110
|
export interface UnwireResult {
|
|
104
111
|
unlinked: Array<{
|
|
105
112
|
key: PackContributeKey;
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
* `merge` rows -- they never produce a symlink.
|
|
38
38
|
*/
|
|
39
39
|
|
|
40
|
-
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="
|
|
40
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="63e778df-65dd-51a4-8a43-3a7d2aa9a451")}catch(e){}}();
|
|
41
41
|
import * as fs from 'fs';
|
|
42
42
|
import * as path from 'path';
|
|
43
43
|
import * as yaml from 'js-yaml';
|
|
@@ -213,6 +213,16 @@ export function listInstalledPacks(hqRoot) {
|
|
|
213
213
|
}
|
|
214
214
|
return out;
|
|
215
215
|
}
|
|
216
|
+
/**
|
|
217
|
+
* Installed packs that declare `name` in their `requires.packs` (M0). Pure over
|
|
218
|
+
* the supplied list — the uninstall dependents guard calls this with
|
|
219
|
+
* `listInstalledPacks(hqRoot)`. Excludes the pack named `name` itself, so a
|
|
220
|
+
* self-reference never counts as its own dependent.
|
|
221
|
+
*/
|
|
222
|
+
export function findDependentPacks(installed, name) {
|
|
223
|
+
return installed.filter((p) => p.name !== name &&
|
|
224
|
+
(p.manifest?.requires?.packs ?? []).some((d) => d.name === name));
|
|
225
|
+
}
|
|
216
226
|
/**
|
|
217
227
|
* Remove only the host symlinks that resolve into THIS pack's directory
|
|
218
228
|
* (status `live` or `broken`). Foreign links and real files are left in place
|
|
@@ -318,4 +328,4 @@ export function readRecommendedPackages(hqRoot) {
|
|
|
318
328
|
}
|
|
319
329
|
}
|
|
320
330
|
//# sourceMappingURL=pack-contributions.js.map
|
|
321
|
-
//# debugId=
|
|
331
|
+
//# debugId=63e778df-65dd-51a4-8a43-3a7d2aa9a451
|
|
@@ -27,16 +27,49 @@
|
|
|
27
27
|
* Opt-out: `HQ_NO_UPDATE_CHECK=1` (same env as `version-check.ts` — one knob
|
|
28
28
|
* to silence both check + gate).
|
|
29
29
|
*/
|
|
30
|
+
interface VersionCheckResponse {
|
|
31
|
+
clientId: string;
|
|
32
|
+
currentVersion: string;
|
|
33
|
+
minVersion: string;
|
|
34
|
+
latestVersion: string;
|
|
35
|
+
updateRequired: boolean;
|
|
36
|
+
updateRecommended: boolean;
|
|
37
|
+
updateCommand?: string;
|
|
38
|
+
downloadUrl?: string;
|
|
39
|
+
message?: string;
|
|
40
|
+
}
|
|
41
|
+
export declare function npmPrefixFromPackageDir(pkgDir: string): string | null;
|
|
42
|
+
export declare function resolveRunningPrefix(): string | null;
|
|
43
|
+
export declare function buildPrefixedInstallArgv(prefix: string): string[];
|
|
30
44
|
/**
|
|
31
45
|
* Run the upgrade command in a blocking subprocess. Inherits stdio so the
|
|
32
46
|
* user sees the npm progress. We do NOT auto-rerun the CLI on completion —
|
|
33
47
|
* forcing a re-invocation would run twice on the same process and feel
|
|
34
48
|
* janky; instead we print a clear "rerun your command" message and exit.
|
|
35
49
|
*/
|
|
36
|
-
|
|
50
|
+
type UpdateResult = {
|
|
37
51
|
ok: boolean;
|
|
38
52
|
detail?: string;
|
|
39
53
|
};
|
|
54
|
+
type UpdateRunner = (cmd: string, args: string[]) => UpdateResult;
|
|
55
|
+
declare function runUpdateCommand(cmd: string, args: string[]): UpdateResult;
|
|
56
|
+
declare function performUpdateCommand(cmd: string, args: string[], runner?: UpdateRunner): UpdateResult;
|
|
57
|
+
declare function performUpdate(command: string, runner?: UpdateRunner): UpdateResult;
|
|
58
|
+
/**
|
|
59
|
+
* Hard enforcement when the server says we're below `minVersion`. Print a
|
|
60
|
+
* red banner, attempt the update, then exit so the user reruns against the
|
|
61
|
+
* fresh binary. Sequence chosen so a user with a broken `npm` global prefix
|
|
62
|
+
* still gets a clear error rather than an opaque silent failure.
|
|
63
|
+
*
|
|
64
|
+
* Exit codes:
|
|
65
|
+
* 0 — update succeeded; user must rerun their command
|
|
66
|
+
* 75 — update failed (EX_TEMPFAIL; common for sudo/EACCES on system npm)
|
|
67
|
+
*/
|
|
68
|
+
declare function enforceUpdateRequired(decision: VersionCheckResponse, deps?: {
|
|
69
|
+
performUpdateString?: (command: string) => UpdateResult;
|
|
70
|
+
resolvePrefix?: () => string | null;
|
|
71
|
+
runner?: UpdateRunner;
|
|
72
|
+
}): never;
|
|
40
73
|
/**
|
|
41
74
|
* Public entry point. Call before commander parses argv. Blocks the CLI on
|
|
42
75
|
* network IO for up to FETCH_TIMEOUT_MS — acceptable because the alternative
|
|
@@ -58,7 +91,13 @@ export declare const __test__: {
|
|
|
58
91
|
CLIENT_ID: string;
|
|
59
92
|
ENDPOINT_PATH: string;
|
|
60
93
|
FETCH_TIMEOUT_MS: number;
|
|
94
|
+
buildPrefixedInstallArgv: typeof buildPrefixedInstallArgv;
|
|
95
|
+
enforceUpdateRequired: typeof enforceUpdateRequired;
|
|
96
|
+
npmPrefixFromPackageDir: typeof npmPrefixFromPackageDir;
|
|
61
97
|
performUpdate: typeof performUpdate;
|
|
98
|
+
performUpdateCommand: typeof performUpdateCommand;
|
|
99
|
+
runUpdateCommand: typeof runUpdateCommand;
|
|
100
|
+
resolveRunningPrefix: typeof resolveRunningPrefix;
|
|
62
101
|
};
|
|
63
102
|
export {};
|
|
64
103
|
//# sourceMappingURL=version-gate.d.ts.map
|