@indigoai-us/hq-cli 5.77.14 → 5.78.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/CHANGELOG.md +23 -0
- package/assets/scaffold/core/scripts/archive-old-threads.sh +86 -0
- package/assets/scaffold/core/scripts/backfill-company-skill-mirrors.sh +73 -0
- package/assets/scaffold/core/scripts/backfill-workspace-mirror.sh +51 -0
- package/assets/scaffold/core/scripts/codex-skill-bridge.sh +550 -0
- package/assets/scaffold/core/scripts/detect-stale-core-policy-mirror.sh +185 -0
- package/assets/scaffold/core/scripts/hq-status-summary.sh +187 -0
- package/assets/scaffold/core/scripts/ontology-readme-drift.sh +264 -0
- package/assets/scaffold/core/scripts/qmd-reindex-after-sync.sh +93 -0
- package/assets/scaffold/core/scripts/rebuild-all-indexes.sh +69 -0
- package/assets/scaffold/core/scripts/rebuild-companies-index.sh +206 -0
- package/assets/scaffold/core/scripts/rebuild-company-knowledge-index.sh +127 -0
- package/assets/scaffold/core/scripts/rebuild-orchestrator-index.sh +86 -0
- package/assets/scaffold/core/scripts/rebuild-projects-index.sh +105 -0
- package/assets/scaffold/core/scripts/rebuild-public-knowledge-index.sh +83 -0
- package/assets/scaffold/core/scripts/rebuild-reports-index.sh +114 -0
- package/assets/scaffold/core/scripts/rebuild-social-drafts-index.sh +135 -0
- package/assets/scaffold/core/scripts/rebuild-threads-index.sh +82 -0
- package/assets/scaffold/core/scripts/rebuild-workers-index.sh +97 -0
- package/assets/scaffold/core/scripts/resize-screenshot.sh +47 -0
- package/assets/scaffold/core/scripts/token-usage-report.sh +254 -0
- package/assets/scaffold/core/scripts/worktree.sh +136 -0
- package/dist/commands/agents.js +28 -12
- package/dist/commands/api-keys.js +100 -20
- package/dist/commands/core.d.ts +90 -0
- package/dist/commands/core.js +279 -0
- package/dist/commands/files-browse.d.ts +21 -0
- package/dist/commands/files-browse.js +145 -24
- package/dist/commands/files.js +33 -7
- package/dist/commands/members.js +16 -5
- package/dist/commands/pack-install.d.ts +14 -0
- package/dist/commands/pack-install.js +42 -2
- package/dist/commands/pkg-install.js +39 -2
- package/dist/commands/run.js +6 -2
- package/dist/commands/secrets.js +120 -65
- package/dist/main.js +6 -0
- package/dist/utils/api-key-command-gate.d.ts +45 -0
- package/dist/utils/api-key-command-gate.js +51 -0
- package/dist/utils/hq-roots.d.ts +91 -0
- package/dist/utils/hq-roots.js +212 -0
- package/dist/utils/resolve-vault-credential.d.ts +63 -3
- package/dist/utils/resolve-vault-credential.js +152 -6
- package/dist/utils/run-bundled-script.d.ts +66 -0
- package/dist/utils/run-bundled-script.js +80 -0
- package/dist/utils/vault-api.d.ts +21 -0
- package/dist/utils/vault-api.js +74 -3
- package/package.json +7 -1
- package/.claude/policies/hq-cli-interactive-reads-use-sts-vend.md +0 -49
- package/.github/workflows/ci.yml +0 -46
- package/.github/workflows/publish.yml +0 -36
- package/.github/workflows/scripts/smoke-test-pkg.sh +0 -97
- package/e2e/cli.test.ts +0 -128
- package/eslint.config.js +0 -37
- package/pnpm-workspace.yaml +0 -4
- package/scripts/chmod-bins.mjs +0 -49
- package/scripts/generate-dsn.mjs +0 -26
- package/scripts/generate-scan-packages-table.mjs +0 -113
- package/scripts/smoke-sources-signals.sh +0 -103
- package/src/bin/hq-auth-refresh.ts +0 -43
- package/src/cli-version.ts +0 -13
- package/src/commands/__fixtures__/make-tar.ts +0 -126
- package/src/commands/_patterns.ts +0 -10
- package/src/commands/add.ts +0 -75
- package/src/commands/agents.test.ts +0 -527
- package/src/commands/agents.ts +0 -923
- package/src/commands/api-keys.test.ts +0 -291
- package/src/commands/api-keys.ts +0 -382
- package/src/commands/artifact-verify.test.ts +0 -177
- package/src/commands/auth.test.ts +0 -82
- package/src/commands/auth.ts +0 -213
- package/src/commands/billing.test.ts +0 -158
- package/src/commands/billing.ts +0 -146
- package/src/commands/channels.test.ts +0 -74
- package/src/commands/channels.ts +0 -91
- package/src/commands/cloud-demote.test.ts +0 -401
- package/src/commands/cloud-demote.ts +0 -278
- package/src/commands/cloud-provision.test.ts +0 -1117
- package/src/commands/cloud-provision.ts +0 -942
- package/src/commands/cloud.pull-all.test.ts +0 -711
- package/src/commands/cloud.pull-per-company.test.ts +0 -188
- package/src/commands/cloud.push-all.test.ts +0 -485
- package/src/commands/cloud.scope-excluded-warning.test.ts +0 -22
- package/src/commands/cloud.selectors.test.ts +0 -208
- package/src/commands/cloud.status.test.ts +0 -160
- package/src/commands/cloud.test.ts +0 -41
- package/src/commands/cloud.ts +0 -2130
- package/src/commands/company.test.ts +0 -202
- package/src/commands/company.ts +0 -148
- package/src/commands/creators.test.ts +0 -60
- package/src/commands/creators.ts +0 -117
- package/src/commands/crm.test.ts +0 -179
- package/src/commands/crm.ts +0 -236
- package/src/commands/db-migrate.ts +0 -55
- package/src/commands/db-provision.ts +0 -114
- package/src/commands/db-sql.ts +0 -124
- package/src/commands/db-status.ts +0 -108
- package/src/commands/db.ts +0 -26
- package/src/commands/dm.test.ts +0 -748
- package/src/commands/dm.ts +0 -1226
- package/src/commands/feedback.test.ts +0 -413
- package/src/commands/feedback.ts +0 -169
- package/src/commands/files-browse.test.ts +0 -1249
- package/src/commands/files-browse.ts +0 -1506
- package/src/commands/files-delete.test.ts +0 -484
- package/src/commands/files-recovery.test.ts +0 -370
- package/src/commands/files.test.ts +0 -722
- package/src/commands/files.ts +0 -1446
- package/src/commands/group-grants.test.ts +0 -330
- package/src/commands/group-grants.ts +0 -455
- package/src/commands/groups.test.ts +0 -44
- package/src/commands/groups.ts +0 -368
- package/src/commands/integrations.test.ts +0 -633
- package/src/commands/integrations.ts +0 -507
- package/src/commands/list.ts +0 -85
- package/src/commands/login.ts +0 -52
- package/src/commands/logout.ts +0 -30
- package/src/commands/marketplace-install.test.ts +0 -543
- package/src/commands/marketplace-security.test.ts +0 -646
- package/src/commands/mcp-registration.test.ts +0 -2787
- package/src/commands/mcp-registration.ts +0 -2612
- package/src/commands/mcp-status.test.ts +0 -483
- package/src/commands/mcp-status.ts +0 -575
- package/src/commands/mcp-status.us011.test.ts +0 -243
- package/src/commands/meetings.test.ts +0 -522
- package/src/commands/meetings.ts +0 -789
- package/src/commands/members.test.ts +0 -1474
- package/src/commands/members.ts +0 -1181
- package/src/commands/onboard-identity-guard.test.ts +0 -99
- package/src/commands/onboard-identity-guard.ts +0 -103
- package/src/commands/onboard-join.test.ts +0 -96
- package/src/commands/onboard-join.ts +0 -101
- package/src/commands/onboard-warning.test.ts +0 -26
- package/src/commands/onboard-warning.ts +0 -12
- package/src/commands/onboard.ts +0 -267
- package/src/commands/outposts-heartbeat.test.ts +0 -299
- package/src/commands/outposts-heartbeat.ts +0 -310
- package/src/commands/outposts-replica-sync.test.ts +0 -272
- package/src/commands/outposts-self-deploy.test.ts +0 -243
- package/src/commands/outposts.test.ts +0 -1039
- package/src/commands/outposts.ts +0 -1935
- package/src/commands/pack-install-secret-authorization.test.ts +0 -115
- package/src/commands/pack-install.test.ts +0 -1469
- package/src/commands/pack-install.ts +0 -2256
- package/src/commands/pack-update-cache.test.ts +0 -149
- package/src/commands/packs-update-api-key.test.ts +0 -105
- package/src/commands/packs.test.ts +0 -88
- package/src/commands/packs.ts +0 -622
- package/src/commands/people.test.ts +0 -807
- package/src/commands/people.ts +0 -422
- package/src/commands/pkg-install.dispatch.test.ts +0 -103
- package/src/commands/pkg-install.ts +0 -221
- package/src/commands/pkg-list.test.ts +0 -79
- package/src/commands/pkg-list.ts +0 -13
- package/src/commands/pkg-remove.ts +0 -71
- package/src/commands/pkg-update.ts +0 -189
- package/src/commands/publish.test.ts +0 -650
- package/src/commands/publish.ts +0 -580
- package/src/commands/reindex.test.ts +0 -259
- package/src/commands/reindex.ts +0 -274
- package/src/commands/rescue.test.ts +0 -39
- package/src/commands/rescue.ts +0 -210
- package/src/commands/run.env-local.test.ts +0 -88
- package/src/commands/run.test.ts +0 -45
- package/src/commands/run.ts +0 -190
- package/src/commands/safe-extract.test.ts +0 -459
- package/src/commands/safe-extract.ts +0 -444
- package/src/commands/secrets-scope.test.ts +0 -56
- package/src/commands/secrets-scope.ts +0 -32
- package/src/commands/secrets.parse-destination.test.ts +0 -38
- package/src/commands/secrets.test.ts +0 -2050
- package/src/commands/secrets.ts +0 -2249
- package/src/commands/signals.ts +0 -351
- package/src/commands/skill.test.ts +0 -770
- package/src/commands/skill.ts +0 -796
- package/src/commands/sources.ts +0 -362
- package/src/commands/sync-mode.test.ts +0 -366
- package/src/commands/sync-mode.ts +0 -387
- package/src/commands/sync-narrow.test.ts +0 -633
- package/src/commands/sync-narrow.ts +0 -555
- package/src/commands/sync.ts +0 -165
- package/src/commands/team-sync.ts +0 -628
- package/src/commands/update.ts +0 -71
- package/src/commands/whoami.ts +0 -83
- package/src/commands/workers.test.ts +0 -158
- package/src/commands/workers.ts +0 -301
- package/src/index.test.ts +0 -32
- package/src/index.ts +0 -20
- package/src/lib/db/control-plane.test.ts +0 -59
- package/src/lib/db/control-plane.ts +0 -113
- package/src/lib/db/local.test.ts +0 -81
- package/src/lib/db/local.ts +0 -148
- package/src/lib/db/migrate.test.ts +0 -133
- package/src/lib/db/migrate.ts +0 -137
- package/src/lib/db/paths.test.ts +0 -112
- package/src/lib/db/paths.ts +0 -128
- package/src/lib/db/remote-engine.test.ts +0 -44
- package/src/lib/db/remote-engine.ts +0 -148
- package/src/lib/db/remote-sql.test.ts +0 -32
- package/src/lib/db/remote-sql.ts +0 -62
- package/src/lib/db/sql.test.ts +0 -106
- package/src/lib/db/sql.ts +0 -192
- package/src/lib/hq-cloud-dep.smoke.test.ts +0 -75
- package/src/lib/local-tree-diff.test.ts +0 -301
- package/src/lib/local-tree-diff.ts +0 -362
- package/src/lib/narrow-hint-banner.test.ts +0 -235
- package/src/lib/narrow-hint-banner.ts +0 -212
- package/src/lib/plan-limit-nag.test.ts +0 -317
- package/src/lib/plan-limit-nag.ts +0 -264
- package/src/main.ts +0 -371
- package/src/node-preflight.test.ts +0 -60
- package/src/node-preflight.ts +0 -67
- package/src/outpost/session-heartbeat-bounds.test.ts +0 -195
- package/src/outpost/session-heartbeat-guard.test.ts +0 -105
- package/src/outpost/session-heartbeat-publisher.test.ts +0 -178
- package/src/outpost/session-heartbeat-publisher.ts +0 -186
- package/src/outpost/session-heartbeat-retain-guard.test.ts +0 -126
- package/src/outpost/session-heartbeat.test.ts +0 -459
- package/src/outpost/session-heartbeat.ts +0 -877
- package/src/packaging.test.ts +0 -109
- package/src/run/__fixtures__/discover-schemas/example.env.schema +0 -4
- package/src/run/discover-schemas.test.ts +0 -153
- package/src/run/discover-schemas.ts +0 -79
- package/src/run/hq-plugin.test.ts +0 -371
- package/src/run/hq-plugin.ts +0 -277
- package/src/run/varlock-shape.test.ts +0 -57
- package/src/schemas/hq-package.schema.json +0 -155
- package/src/sentry-before-send.test.ts +0 -359
- package/src/sentry-before-send.ts +0 -221
- package/src/sentry-epipe.test.ts +0 -37
- package/src/sentry-release.test.ts +0 -54
- package/src/sentry.test.ts +0 -24
- package/src/sentry.ts +0 -54
- package/src/strategies/link.ts +0 -62
- package/src/strategies/merge.ts +0 -142
- package/src/types.ts +0 -146
- package/src/utils/__fixtures__/scan-packages.generated-block.sh +0 -23
- package/src/utils/auth-error.test.ts +0 -40
- package/src/utils/auth-error.ts +0 -42
- package/src/utils/billing-gate.test.ts +0 -95
- package/src/utils/billing-gate.ts +0 -228
- package/src/utils/breadcrumb-buffer.ts +0 -18
- package/src/utils/cli-telemetry.test.ts +0 -153
- package/src/utils/cli-telemetry.ts +0 -61
- package/src/utils/cognito-session.machine.test.ts +0 -109
- package/src/utils/cognito-session.refresh.test.ts +0 -84
- package/src/utils/cognito-session.test.ts +0 -328
- package/src/utils/cognito-session.ts +0 -448
- package/src/utils/company-selection-error.test.ts +0 -42
- package/src/utils/company-selection-error.ts +0 -45
- package/src/utils/contribution-table.ts +0 -83
- package/src/utils/environmental-error.test.ts +0 -45
- package/src/utils/environmental-error.ts +0 -39
- package/src/utils/epipe.test.ts +0 -28
- package/src/utils/epipe.ts +0 -29
- package/src/utils/expected-cli-error.test.ts +0 -28
- package/src/utils/expected-cli-error.ts +0 -39
- package/src/utils/feedback-diagnostics.test.ts +0 -183
- package/src/utils/feedback-diagnostics.ts +0 -123
- package/src/utils/feedback-screenshots.test.ts +0 -134
- package/src/utils/feedback-screenshots.ts +0 -124
- package/src/utils/feedback-versions.test.ts +0 -98
- package/src/utils/feedback-versions.ts +0 -68
- package/src/utils/git.ts +0 -74
- package/src/utils/integrity.ts +0 -59
- package/src/utils/intercepted-process-exit.test.ts +0 -37
- package/src/utils/intercepted-process-exit.ts +0 -36
- package/src/utils/login-provider.test.ts +0 -42
- package/src/utils/login-provider.ts +0 -30
- package/src/utils/manifest.test.ts +0 -167
- package/src/utils/manifest.ts +0 -131
- package/src/utils/pack-contributions.test.ts +0 -603
- package/src/utils/pack-contributions.ts +0 -450
- package/src/utils/people.ts +0 -215
- package/src/utils/registry-client.ts +0 -204
- package/src/utils/registry.ts +0 -90
- package/src/utils/resolve-vault-credential.test.ts +0 -69
- package/src/utils/resolve-vault-credential.ts +0 -60
- package/src/utils/sandbox-runner-client.test.ts +0 -279
- package/src/utils/sandbox-runner-client.ts +0 -260
- package/src/utils/secrets-cache.ts +0 -183
- package/src/utils/sentry-identity.ts +0 -45
- package/src/utils/settle-with-timeout.test.ts +0 -21
- package/src/utils/settle-with-timeout.ts +0 -22
- package/src/utils/user-yaml-error.test.ts +0 -24
- package/src/utils/user-yaml-error.ts +0 -30
- package/src/utils/vault-api.test.ts +0 -536
- package/src/utils/vault-api.ts +0 -382
- package/src/utils/version-check.test.ts +0 -176
- package/src/utils/version-check.ts +0 -155
- package/src/utils/version-gate.test.ts +0 -1024
- package/src/utils/version-gate.ts +0 -702
- package/test/commands/db-tenant-isolation.test.ts +0 -94
- package/test/commands/db.test.ts +0 -85
- package/test/commands/signals.test.ts +0 -207
- package/test/commands/sources.test.ts +0 -231
- package/test/e2e/smoke-install-mcp.sh +0 -113
- package/test/fixtures/hq-pack-smoke-mcp/mcp/smoke-http.json +0 -1
- package/test/fixtures/hq-pack-smoke-mcp/package.yaml +0 -11
- package/test/fixtures/signals/action_item/sample.md +0 -16
- package/test/fixtures/signals/summary/sample.md +0 -12
- package/test/fixtures/sources/meetings/sample.md +0 -25
- package/test/helpers/cli-runner.ts +0 -150
- package/test/helpers/s3-list-mock.ts +0 -79
- package/test/helpers/vault-service-mock.ts +0 -295
- package/test/sources-signals/smoke.test.ts +0 -228
- package/tsconfig.json +0 -20
- package/vitest.config.ts +0 -11
- package/vitest.e2e.config.ts +0 -7
|
@@ -1,260 +0,0 @@
|
|
|
1
|
-
export type SandboxRunnerState = "queued" | "running" | "succeeded" | "failed";
|
|
2
|
-
|
|
3
|
-
export interface SandboxRunnerStartRequest {
|
|
4
|
-
companyUid: string;
|
|
5
|
-
secretNames: string[];
|
|
6
|
-
command: string;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export interface SandboxRunnerStartResponse {
|
|
10
|
-
jobId: string;
|
|
11
|
-
status: SandboxRunnerState;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export interface SandboxRunnerJob {
|
|
15
|
-
jobId: string;
|
|
16
|
-
status: SandboxRunnerState;
|
|
17
|
-
output?: string;
|
|
18
|
-
error?: string;
|
|
19
|
-
exitCode?: number;
|
|
20
|
-
success?: boolean;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export interface SandboxRunnerClientOptions {
|
|
24
|
-
baseUrl?: string;
|
|
25
|
-
fetchImpl?: typeof fetch;
|
|
26
|
-
retry?: SandboxRunnerRetryOptions;
|
|
27
|
-
sleep?: (ms: number) => Promise<void>;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export interface SandboxRunnerPollOptions {
|
|
31
|
-
intervalMs?: number;
|
|
32
|
-
maxPolls?: number;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export interface SandboxRunnerRetryOptions {
|
|
36
|
-
maxAttempts?: number;
|
|
37
|
-
maxElapsedMs?: number;
|
|
38
|
-
baseDelayMs?: number;
|
|
39
|
-
maxDelayMs?: number;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const DEFAULT_SANDBOX_RUNNER_URL = "https://hqapi.hq.computer/sandbox";
|
|
43
|
-
const RETRYABLE_STATUS_CODES = new Set([408, 425, 429, 500, 502, 503, 504]);
|
|
44
|
-
const DEFAULT_RETRY_OPTIONS: Required<SandboxRunnerRetryOptions> = {
|
|
45
|
-
maxAttempts: 8,
|
|
46
|
-
maxElapsedMs: 90_000,
|
|
47
|
-
baseDelayMs: 500,
|
|
48
|
-
maxDelayMs: 5_000,
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
function normalizeBaseUrl(baseUrl: string): string {
|
|
52
|
-
return baseUrl.replace(/\/+$/, "");
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function getSandboxRunnerBaseUrl(): string {
|
|
56
|
-
return normalizeBaseUrl(
|
|
57
|
-
process.env.HQ_SANDBOX_RUNNER_URL ?? DEFAULT_SANDBOX_RUNNER_URL,
|
|
58
|
-
);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function isSandboxRunnerState(value: unknown): value is SandboxRunnerState {
|
|
62
|
-
return value === "queued" || value === "running" || value === "succeeded" || value === "failed";
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
async function parseJsonResponse(res: Response): Promise<Record<string, unknown>> {
|
|
66
|
-
return (await res.json().catch(() => ({}))) as Record<string, unknown>;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function requireString(body: Record<string, unknown>, key: string): string {
|
|
70
|
-
const value = body[key];
|
|
71
|
-
if (typeof value !== "string" || value.length === 0) {
|
|
72
|
-
throw new Error(`Sandbox Runner returned an invalid '${key}'.`);
|
|
73
|
-
}
|
|
74
|
-
return value;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function normalizeJob(
|
|
78
|
-
body: Record<string, unknown>,
|
|
79
|
-
jobIdFallback?: string,
|
|
80
|
-
): SandboxRunnerJob {
|
|
81
|
-
const status = body.status;
|
|
82
|
-
if (!isSandboxRunnerState(status)) {
|
|
83
|
-
throw new Error("Sandbox Runner returned an invalid job status.");
|
|
84
|
-
}
|
|
85
|
-
return {
|
|
86
|
-
jobId:
|
|
87
|
-
typeof body.jobId === "string" && body.jobId.length > 0
|
|
88
|
-
? body.jobId
|
|
89
|
-
: jobIdFallback ?? requireString(body, "jobId"),
|
|
90
|
-
status,
|
|
91
|
-
output: typeof body.output === "string" ? body.output : undefined,
|
|
92
|
-
error: typeof body.error === "string" ? body.error : undefined,
|
|
93
|
-
exitCode: typeof body.exitCode === "number" ? body.exitCode : undefined,
|
|
94
|
-
success: typeof body.success === "boolean" ? body.success : undefined,
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function delay(ms: number): Promise<void> {
|
|
99
|
-
if (ms <= 0) return Promise.resolve();
|
|
100
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function getErrorMessage(error: unknown): string {
|
|
104
|
-
if (error instanceof Error) {
|
|
105
|
-
return error.message;
|
|
106
|
-
}
|
|
107
|
-
if (typeof error === "string") {
|
|
108
|
-
return error;
|
|
109
|
-
}
|
|
110
|
-
return String(error);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function parseRetryAfterMs(value: string | null): number | undefined {
|
|
114
|
-
if (!value) return undefined;
|
|
115
|
-
const seconds = Number(value);
|
|
116
|
-
if (Number.isFinite(seconds) && seconds >= 0) {
|
|
117
|
-
return seconds * 1000;
|
|
118
|
-
}
|
|
119
|
-
const dateMs = Date.parse(value);
|
|
120
|
-
if (!Number.isNaN(dateMs)) {
|
|
121
|
-
return Math.max(0, dateMs - Date.now());
|
|
122
|
-
}
|
|
123
|
-
return undefined;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
export class SandboxRunnerClient {
|
|
127
|
-
private readonly baseUrl: string;
|
|
128
|
-
private readonly fetchImpl: typeof fetch;
|
|
129
|
-
private readonly retry: Required<SandboxRunnerRetryOptions>;
|
|
130
|
-
private readonly sleep: (ms: number) => Promise<void>;
|
|
131
|
-
|
|
132
|
-
constructor(options: SandboxRunnerClientOptions = {}) {
|
|
133
|
-
this.baseUrl = normalizeBaseUrl(options.baseUrl ?? getSandboxRunnerBaseUrl());
|
|
134
|
-
this.fetchImpl = options.fetchImpl ?? fetch;
|
|
135
|
-
this.retry = { ...DEFAULT_RETRY_OPTIONS, ...options.retry };
|
|
136
|
-
this.sleep = options.sleep ?? delay;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
private async fetchWithRetry(
|
|
140
|
-
url: string,
|
|
141
|
-
init: RequestInit,
|
|
142
|
-
): Promise<Response> {
|
|
143
|
-
const startedAt = Date.now();
|
|
144
|
-
let lastError = "unknown error";
|
|
145
|
-
let attempts = 0;
|
|
146
|
-
|
|
147
|
-
for (let attempt = 1; attempt <= this.retry.maxAttempts; attempt += 1) {
|
|
148
|
-
attempts = attempt;
|
|
149
|
-
try {
|
|
150
|
-
const res = await this.fetchImpl(url, init);
|
|
151
|
-
if (!RETRYABLE_STATUS_CODES.has(res.status)) {
|
|
152
|
-
return res;
|
|
153
|
-
}
|
|
154
|
-
lastError = `HTTP ${res.status} ${res.statusText}`.trim();
|
|
155
|
-
if (!this.shouldRetry(attempt, startedAt)) {
|
|
156
|
-
break;
|
|
157
|
-
}
|
|
158
|
-
await this.sleep(this.nextDelayMs(attempt, res));
|
|
159
|
-
} catch (error) {
|
|
160
|
-
lastError = getErrorMessage(error);
|
|
161
|
-
if (!this.shouldRetry(attempt, startedAt)) {
|
|
162
|
-
break;
|
|
163
|
-
}
|
|
164
|
-
await this.sleep(this.nextDelayMs(attempt));
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
throw new Error(
|
|
169
|
-
`Sandbox Runner did not respond after ${attempts} attempts ` +
|
|
170
|
-
`(cold start or transient network); last error: ${lastError}`,
|
|
171
|
-
);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
private shouldRetry(attempt: number, startedAt: number): boolean {
|
|
175
|
-
if (attempt >= this.retry.maxAttempts) {
|
|
176
|
-
return false;
|
|
177
|
-
}
|
|
178
|
-
return Date.now() - startedAt < this.retry.maxElapsedMs;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
private nextDelayMs(attempt: number, res?: Response): number {
|
|
182
|
-
const retryAfterMs = parseRetryAfterMs(res?.headers.get("Retry-After") ?? null);
|
|
183
|
-
if (retryAfterMs !== undefined) {
|
|
184
|
-
return Math.min(retryAfterMs, this.retry.maxDelayMs);
|
|
185
|
-
}
|
|
186
|
-
const exponential = this.retry.baseDelayMs * 2 ** (attempt - 1);
|
|
187
|
-
const capped = Math.min(exponential, this.retry.maxDelayMs);
|
|
188
|
-
const jitter = Math.floor(Math.random() * Math.max(1, capped * 0.25));
|
|
189
|
-
return Math.min(capped + jitter, this.retry.maxDelayMs);
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
async startJob(
|
|
193
|
-
token: string,
|
|
194
|
-
request: SandboxRunnerStartRequest,
|
|
195
|
-
): Promise<SandboxRunnerStartResponse> {
|
|
196
|
-
const res = await this.fetchWithRetry(`${this.baseUrl}/jobs`, {
|
|
197
|
-
method: "POST",
|
|
198
|
-
headers: {
|
|
199
|
-
Authorization: `Bearer ${token}`,
|
|
200
|
-
"Content-Type": "application/json",
|
|
201
|
-
},
|
|
202
|
-
body: JSON.stringify(request),
|
|
203
|
-
});
|
|
204
|
-
const body = await parseJsonResponse(res);
|
|
205
|
-
if (!res.ok) {
|
|
206
|
-
const message =
|
|
207
|
-
typeof body.message === "string"
|
|
208
|
-
? body.message
|
|
209
|
-
: typeof body.error === "string"
|
|
210
|
-
? body.error
|
|
211
|
-
: res.statusText;
|
|
212
|
-
throw new Error(`Sandbox Runner rejected job: ${message}`);
|
|
213
|
-
}
|
|
214
|
-
const status = body.status;
|
|
215
|
-
if (!isSandboxRunnerState(status)) {
|
|
216
|
-
throw new Error("Sandbox Runner returned an invalid start status.");
|
|
217
|
-
}
|
|
218
|
-
return {
|
|
219
|
-
jobId: requireString(body, "jobId"),
|
|
220
|
-
status,
|
|
221
|
-
};
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
async getJob(token: string, jobId: string): Promise<SandboxRunnerJob> {
|
|
225
|
-
const res = await this.fetchWithRetry(
|
|
226
|
-
`${this.baseUrl}/jobs/${encodeURIComponent(jobId)}`,
|
|
227
|
-
{
|
|
228
|
-
headers: { Authorization: `Bearer ${token}` },
|
|
229
|
-
},
|
|
230
|
-
);
|
|
231
|
-
const body = await parseJsonResponse(res);
|
|
232
|
-
if (!res.ok) {
|
|
233
|
-
const message =
|
|
234
|
-
typeof body.message === "string"
|
|
235
|
-
? body.message
|
|
236
|
-
: typeof body.error === "string"
|
|
237
|
-
? body.error
|
|
238
|
-
: res.statusText;
|
|
239
|
-
throw new Error(`Sandbox Runner job lookup failed: ${message}`);
|
|
240
|
-
}
|
|
241
|
-
return normalizeJob(body, jobId);
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
async pollJob(
|
|
245
|
-
token: string,
|
|
246
|
-
jobId: string,
|
|
247
|
-
options: SandboxRunnerPollOptions = {},
|
|
248
|
-
): Promise<SandboxRunnerJob> {
|
|
249
|
-
const intervalMs = options.intervalMs ?? 1000;
|
|
250
|
-
const maxPolls = options.maxPolls ?? 300;
|
|
251
|
-
for (let attempt = 0; attempt < maxPolls; attempt += 1) {
|
|
252
|
-
const job = await this.getJob(token, jobId);
|
|
253
|
-
if (job.status === "succeeded" || job.status === "failed") {
|
|
254
|
-
return job;
|
|
255
|
-
}
|
|
256
|
-
await this.sleep(intervalMs);
|
|
257
|
-
}
|
|
258
|
-
throw new Error(`Sandbox Runner job '${jobId}' did not finish before the poll limit.`);
|
|
259
|
-
}
|
|
260
|
-
}
|
|
@@ -1,183 +0,0 @@
|
|
|
1
|
-
import * as crypto from "node:crypto";
|
|
2
|
-
import * as fs from "node:fs";
|
|
3
|
-
import * as path from "node:path";
|
|
4
|
-
import * as os from "node:os";
|
|
5
|
-
|
|
6
|
-
const CACHE_DIR = path.join(os.homedir(), ".hq", "secrets-cache");
|
|
7
|
-
const KEY_PATH = path.join(CACHE_DIR, ".key");
|
|
8
|
-
const CACHE_FORMAT_MAGIC = Buffer.from("HQSC");
|
|
9
|
-
const CACHE_FORMAT_MAGIC_BYTES = CACHE_FORMAT_MAGIC.length;
|
|
10
|
-
const LEGACY_TIMESTAMP_BYTES = 8;
|
|
11
|
-
const TIMESTAMP_BYTES = 8;
|
|
12
|
-
const TTL_BYTES = 8;
|
|
13
|
-
const ALGORITHM = "aes-256-gcm";
|
|
14
|
-
const IV_BYTES = 12;
|
|
15
|
-
const AUTH_TAG_BYTES = 16;
|
|
16
|
-
export const DEFAULT_SECRETS_CACHE_TTL_MS = 5 * 60 * 1000;
|
|
17
|
-
|
|
18
|
-
function ensureCacheDir(companyUid: string): void {
|
|
19
|
-
const dir = path.join(CACHE_DIR, companyUid);
|
|
20
|
-
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function validateInputs(companyUid: string, name: string): boolean {
|
|
24
|
-
if (!companyUid || companyUid.includes("/") || companyUid.includes("..")) return false;
|
|
25
|
-
if (!/^[A-Z][A-Z0-9_]*(?:\/[A-Z][A-Z0-9_]+)*$/.test(name)) return false;
|
|
26
|
-
return true;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function getOrCreateKey(): Buffer {
|
|
30
|
-
try {
|
|
31
|
-
const key = fs.readFileSync(KEY_PATH);
|
|
32
|
-
if (key.length === 32) {
|
|
33
|
-
const mode = fs.statSync(KEY_PATH).mode & 0o777;
|
|
34
|
-
if (mode !== 0o600) {
|
|
35
|
-
process.stderr.write(`Warning: secrets cache key has permissions ${mode.toString(8)}, expected 600\n`);
|
|
36
|
-
}
|
|
37
|
-
return key;
|
|
38
|
-
}
|
|
39
|
-
} catch {
|
|
40
|
-
// Key doesn't exist or is unreadable — generate a new one
|
|
41
|
-
}
|
|
42
|
-
fs.mkdirSync(CACHE_DIR, { recursive: true, mode: 0o700 });
|
|
43
|
-
const key = crypto.randomBytes(32);
|
|
44
|
-
const tmpPath = `${KEY_PATH}.tmp.${process.pid}`;
|
|
45
|
-
fs.writeFileSync(tmpPath, key, { mode: 0o600 });
|
|
46
|
-
fs.renameSync(tmpPath, KEY_PATH);
|
|
47
|
-
return fs.readFileSync(KEY_PATH);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export function readCache(companyUid: string, name: string): string | null {
|
|
51
|
-
if (!validateInputs(companyUid, name)) return null;
|
|
52
|
-
const filePath = path.join(CACHE_DIR, companyUid, name);
|
|
53
|
-
let raw: Buffer;
|
|
54
|
-
try {
|
|
55
|
-
raw = fs.readFileSync(filePath);
|
|
56
|
-
} catch {
|
|
57
|
-
return null;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
let timestampMs: number;
|
|
61
|
-
let ttlMs: number;
|
|
62
|
-
let ivStart: number;
|
|
63
|
-
let authTagStart: number;
|
|
64
|
-
let ciphertextStart: number;
|
|
65
|
-
|
|
66
|
-
if (
|
|
67
|
-
raw.length >=
|
|
68
|
-
CACHE_FORMAT_MAGIC_BYTES + TIMESTAMP_BYTES + TTL_BYTES + IV_BYTES + AUTH_TAG_BYTES &&
|
|
69
|
-
raw.subarray(0, CACHE_FORMAT_MAGIC_BYTES).equals(CACHE_FORMAT_MAGIC)
|
|
70
|
-
) {
|
|
71
|
-
timestampMs = Number(raw.readBigInt64BE(CACHE_FORMAT_MAGIC_BYTES));
|
|
72
|
-
ttlMs = Number(raw.readBigInt64BE(CACHE_FORMAT_MAGIC_BYTES + TIMESTAMP_BYTES));
|
|
73
|
-
ivStart = CACHE_FORMAT_MAGIC_BYTES + TIMESTAMP_BYTES + TTL_BYTES;
|
|
74
|
-
authTagStart = ivStart + IV_BYTES;
|
|
75
|
-
ciphertextStart = authTagStart + AUTH_TAG_BYTES;
|
|
76
|
-
} else {
|
|
77
|
-
const headerLen = LEGACY_TIMESTAMP_BYTES + IV_BYTES + AUTH_TAG_BYTES;
|
|
78
|
-
if (raw.length < headerLen) return null;
|
|
79
|
-
timestampMs = Number(raw.readBigInt64BE(0));
|
|
80
|
-
ttlMs = DEFAULT_SECRETS_CACHE_TTL_MS;
|
|
81
|
-
ivStart = LEGACY_TIMESTAMP_BYTES;
|
|
82
|
-
authTagStart = ivStart + IV_BYTES;
|
|
83
|
-
ciphertextStart = authTagStart + AUTH_TAG_BYTES;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
if (ttlMs <= 0 || Date.now() - timestampMs > ttlMs) {
|
|
87
|
-
try { fs.unlinkSync(filePath); } catch { /* ok */ }
|
|
88
|
-
return null;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const iv = raw.subarray(ivStart, authTagStart);
|
|
92
|
-
const authTag = raw.subarray(authTagStart, ciphertextStart);
|
|
93
|
-
const ciphertext = raw.subarray(ciphertextStart);
|
|
94
|
-
|
|
95
|
-
let key: Buffer;
|
|
96
|
-
try {
|
|
97
|
-
key = getOrCreateKey();
|
|
98
|
-
} catch {
|
|
99
|
-
return null;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
try {
|
|
103
|
-
const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
|
|
104
|
-
decipher.setAuthTag(authTag);
|
|
105
|
-
const decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
|
106
|
-
return decrypted.toString("utf8");
|
|
107
|
-
} catch {
|
|
108
|
-
try { fs.unlinkSync(filePath); } catch { /* ok */ }
|
|
109
|
-
return null;
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
export function writeCache(
|
|
114
|
-
companyUid: string,
|
|
115
|
-
name: string,
|
|
116
|
-
value: string,
|
|
117
|
-
ttlMs = DEFAULT_SECRETS_CACHE_TTL_MS,
|
|
118
|
-
): void {
|
|
119
|
-
try {
|
|
120
|
-
if (!validateInputs(companyUid, name)) return;
|
|
121
|
-
if (ttlMs <= 0) return;
|
|
122
|
-
ensureCacheDir(companyUid);
|
|
123
|
-
const key = getOrCreateKey();
|
|
124
|
-
const iv = crypto.randomBytes(IV_BYTES);
|
|
125
|
-
const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
|
|
126
|
-
const encrypted = Buffer.concat([cipher.update(value, "utf8"), cipher.final()]);
|
|
127
|
-
const authTag = cipher.getAuthTag();
|
|
128
|
-
|
|
129
|
-
const timestamp = Buffer.alloc(8);
|
|
130
|
-
timestamp.writeBigInt64BE(BigInt(Date.now()));
|
|
131
|
-
const ttl = Buffer.alloc(8);
|
|
132
|
-
ttl.writeBigInt64BE(BigInt(ttlMs));
|
|
133
|
-
|
|
134
|
-
const out = Buffer.concat([CACHE_FORMAT_MAGIC, timestamp, ttl, iv, authTag, encrypted]);
|
|
135
|
-
const filePath = path.join(CACHE_DIR, companyUid, name);
|
|
136
|
-
fs.mkdirSync(path.dirname(filePath), { recursive: true, mode: 0o700 });
|
|
137
|
-
const tmpPath = `${filePath}.tmp.${process.pid}`;
|
|
138
|
-
fs.writeFileSync(tmpPath, out, { mode: 0o600 });
|
|
139
|
-
fs.renameSync(tmpPath, filePath);
|
|
140
|
-
} catch {
|
|
141
|
-
// Cache write failure is non-fatal
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* List the scope UIDs (`cmp_*` / `prs_*` subdirectories) that currently have a
|
|
147
|
-
* secrets-cache directory on disk. Install-time MCP registration may use an
|
|
148
|
-
* exactly-one result as a scope hint before reauthorizing every value online; it
|
|
149
|
-
* never reads cached plaintext through this helper. Returns `[]` when the cache
|
|
150
|
-
* root is absent or unreadable.
|
|
151
|
-
*/
|
|
152
|
-
export function listSecretCacheScopes(): string[] {
|
|
153
|
-
try {
|
|
154
|
-
return fs
|
|
155
|
-
.readdirSync(CACHE_DIR, { withFileTypes: true })
|
|
156
|
-
.filter((e) => e.isDirectory())
|
|
157
|
-
.map((e) => e.name)
|
|
158
|
-
.filter((name) => /^(?:cmp|prs)_[A-Za-z0-9_-]+$/.test(name));
|
|
159
|
-
} catch {
|
|
160
|
-
return [];
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
export function removeCacheEntry(companyUid: string, name: string): void {
|
|
165
|
-
if (!validateInputs(companyUid, name)) return;
|
|
166
|
-
const filePath = path.join(CACHE_DIR, companyUid, name);
|
|
167
|
-
try {
|
|
168
|
-
fs.unlinkSync(filePath);
|
|
169
|
-
} catch {
|
|
170
|
-
// Cache entry may not exist
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
export function clearAllCache(): { removed: number } {
|
|
175
|
-
let removed = 0;
|
|
176
|
-
try {
|
|
177
|
-
fs.rmSync(CACHE_DIR, { recursive: true, force: true });
|
|
178
|
-
removed = 1;
|
|
179
|
-
} catch {
|
|
180
|
-
// Cache dir may not exist
|
|
181
|
-
}
|
|
182
|
-
return { removed };
|
|
183
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
loadCachedTokens,
|
|
3
|
-
isMachineIdentity,
|
|
4
|
-
loadMachineCreds,
|
|
5
|
-
} from "@indigoai-us/hq-cloud";
|
|
6
|
-
|
|
7
|
-
/** Decode the (untrusted) id-token payload — for attribution/display only. */
|
|
8
|
-
function peekIdToken(idToken: string): { email?: string; sub?: string } {
|
|
9
|
-
try {
|
|
10
|
-
const payload = idToken.split(".")[1];
|
|
11
|
-
if (!payload) return {};
|
|
12
|
-
const pad =
|
|
13
|
-
payload.length % 4 === 0 ? "" : "=".repeat(4 - (payload.length % 4));
|
|
14
|
-
const normalized = payload.replace(/-/g, "+").replace(/_/g, "/") + pad;
|
|
15
|
-
const decoded = JSON.parse(
|
|
16
|
-
Buffer.from(normalized, "base64").toString("utf-8"),
|
|
17
|
-
) as { email?: string; sub?: string };
|
|
18
|
-
return { email: decoded.email, sub: decoded.sub };
|
|
19
|
-
} catch {
|
|
20
|
-
return {};
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Best-effort Sentry user from the cached HQ session, so CLI events are
|
|
26
|
-
* attributed to a user ("users affected" + per-user triage). Returns null when
|
|
27
|
-
* not logged in. A CLI process is single-user, so this is set once at init.
|
|
28
|
-
*/
|
|
29
|
-
export function getCachedSentryUser():
|
|
30
|
-
| { id?: string; email?: string; username?: string }
|
|
31
|
-
| null {
|
|
32
|
-
try {
|
|
33
|
-
if (isMachineIdentity()) {
|
|
34
|
-
const username = loadMachineCreds()?.username;
|
|
35
|
-
return username ? { id: username, username } : null;
|
|
36
|
-
}
|
|
37
|
-
const cached = loadCachedTokens();
|
|
38
|
-
if (!cached) return null;
|
|
39
|
-
const { email, sub } = peekIdToken(cached.idToken);
|
|
40
|
-
if (!sub && !email) return null;
|
|
41
|
-
return { id: sub, email };
|
|
42
|
-
} catch {
|
|
43
|
-
return null;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from "vitest";
|
|
2
|
-
|
|
3
|
-
import { settleWithin } from "./settle-with-timeout.js";
|
|
4
|
-
|
|
5
|
-
describe("settleWithin", () => {
|
|
6
|
-
it("settles normally when all finalizers finish", async () => {
|
|
7
|
-
await expect(settleWithin([Promise.resolve(), Promise.reject(new Error("ignored"))], 100))
|
|
8
|
-
.resolves.toBe("settled");
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
it("settles the caller when a best-effort finalizer never resolves", async () => {
|
|
12
|
-
vi.useFakeTimers();
|
|
13
|
-
try {
|
|
14
|
-
const result = settleWithin([new Promise<void>(() => {})], 1_000);
|
|
15
|
-
await vi.advanceTimersByTimeAsync(1_000);
|
|
16
|
-
await expect(result).resolves.toBe("timed_out");
|
|
17
|
-
} finally {
|
|
18
|
-
vi.useRealTimers();
|
|
19
|
-
}
|
|
20
|
-
});
|
|
21
|
-
});
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Await finalization work without allowing a best-effort promise to leave a
|
|
3
|
-
* CLI's top-level await pending forever. The timer intentionally stays
|
|
4
|
-
* referenced: Node must remain alive long enough to settle this race.
|
|
5
|
-
*/
|
|
6
|
-
export async function settleWithin(
|
|
7
|
-
promises: readonly Promise<unknown>[],
|
|
8
|
-
timeoutMs: number,
|
|
9
|
-
): Promise<"settled" | "timed_out"> {
|
|
10
|
-
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
11
|
-
const timedOut = new Promise<"timed_out">((resolve) => {
|
|
12
|
-
timer = setTimeout(() => resolve("timed_out"), timeoutMs);
|
|
13
|
-
});
|
|
14
|
-
try {
|
|
15
|
-
return await Promise.race([
|
|
16
|
-
Promise.allSettled(promises).then(() => "settled" as const),
|
|
17
|
-
timedOut,
|
|
18
|
-
]);
|
|
19
|
-
} finally {
|
|
20
|
-
if (timer !== undefined) clearTimeout(timer);
|
|
21
|
-
}
|
|
22
|
-
}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { isExpectedUserError } from "./expected-cli-error.js";
|
|
3
|
-
import { parseUserYaml } from "./user-yaml-error.js";
|
|
4
|
-
|
|
5
|
-
describe("parseUserYaml", () => {
|
|
6
|
-
it("marks malformed user YAML as expected and names its file and location", () => {
|
|
7
|
-
const filePath = "/tmp/HQ/modules/modules.yaml";
|
|
8
|
-
const error = (() => {
|
|
9
|
-
try {
|
|
10
|
-
parseUserYaml('apiKey: "secret-do-not-echo"\nmodule:\n name: hq\n bad: indentation\n', filePath);
|
|
11
|
-
} catch (err) {
|
|
12
|
-
return err;
|
|
13
|
-
}
|
|
14
|
-
throw new Error("expected malformed YAML to throw");
|
|
15
|
-
})();
|
|
16
|
-
|
|
17
|
-
expect(isExpectedUserError(error)).toBe(true);
|
|
18
|
-
expect((error as Error).message).toContain(
|
|
19
|
-
`Invalid YAML in ${filePath}:4:2: bad indentation of a mapping entry.`,
|
|
20
|
-
);
|
|
21
|
-
expect((error as Error).message).toContain("Fix the indentation or syntax and try again.");
|
|
22
|
-
expect((error as Error).message).not.toContain("secret-do-not-echo");
|
|
23
|
-
});
|
|
24
|
-
});
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import * as yaml from "js-yaml";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Parse YAML supplied from an on-disk HQ file.
|
|
5
|
-
*
|
|
6
|
-
* YAML syntax is user-correctable content, not an hq-cli defect. js-yaml's
|
|
7
|
-
* default error includes a source excerpt, which can inadvertently echo
|
|
8
|
-
* secrets, so replace it with a concise, actionable location instead.
|
|
9
|
-
*/
|
|
10
|
-
export function parseUserYaml<T>(content: string, filePath: string): T {
|
|
11
|
-
try {
|
|
12
|
-
return yaml.load(content, { filename: filePath }) as T;
|
|
13
|
-
} catch (err) {
|
|
14
|
-
if (!(err instanceof yaml.YAMLException)) throw err;
|
|
15
|
-
|
|
16
|
-
const line = err.mark ? err.mark.line + 1 : undefined;
|
|
17
|
-
const column = err.mark ? err.mark.column + 1 : undefined;
|
|
18
|
-
const location = line !== undefined && column !== undefined
|
|
19
|
-
? `:${line}:${column}`
|
|
20
|
-
: "";
|
|
21
|
-
|
|
22
|
-
throw Object.assign(
|
|
23
|
-
new Error(
|
|
24
|
-
`Invalid YAML in ${filePath}${location}: ${err.reason}. ` +
|
|
25
|
-
"Fix the indentation or syntax and try again.",
|
|
26
|
-
),
|
|
27
|
-
{ expected: true as const },
|
|
28
|
-
);
|
|
29
|
-
}
|
|
30
|
-
}
|