ccqa 0.12.0 → 0.13.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/ccqa.mjs +51 -7
- package/dist/hub-client/index.d.mts +6 -0
- package/dist/hub-client/index.mjs +1 -0
- package/dist/package.json +1 -1
- package/package.json +1 -1
package/dist/bin/ccqa.mjs
CHANGED
|
@@ -3199,6 +3199,44 @@ function resolveProjectOrThrow(project, cwd) {
|
|
|
3199
3199
|
*/
|
|
3200
3200
|
const hubUrlOption = ["--hub-url <url>", "ccqa hub base URL (or CCQA_HUB_URL)."];
|
|
3201
3201
|
const hubTokenOption = ["--hub-token <token>", "ccqa hub bearer token (or CCQA_HUB_TOKEN)."];
|
|
3202
|
+
const hubHeaderOption = [
|
|
3203
|
+
"--hub-header <header>",
|
|
3204
|
+
"Extra header sent with every hub request, as \"key:value\" (or CCQA_HUB_HEADER). Repeatable. For infra that gates the hub behind a header check (e.g. a load balancer bypass rule).",
|
|
3205
|
+
collectHubHeader,
|
|
3206
|
+
[]
|
|
3207
|
+
];
|
|
3208
|
+
/** commander `--hub-header` accumulator: collects repeated flags into an array. */
|
|
3209
|
+
function collectHubHeader(value, previous) {
|
|
3210
|
+
return [...previous, value];
|
|
3211
|
+
}
|
|
3212
|
+
/**
|
|
3213
|
+
* Parse `"key:value"` entries (from `--hub-header`, repeatable) into a
|
|
3214
|
+
* headers map. The value may itself contain `:` (e.g. a URL), so only the
|
|
3215
|
+
* first colon is treated as the separator. Throws on an entry with no colon.
|
|
3216
|
+
*/
|
|
3217
|
+
function parseHubHeaders(entries) {
|
|
3218
|
+
const headers = {};
|
|
3219
|
+
for (const entry of entries) {
|
|
3220
|
+
const i = entry.indexOf(":");
|
|
3221
|
+
if (i < 0) throw new Error(`invalid --hub-header (expected "key:value"): ${entry}`);
|
|
3222
|
+
const key = entry.slice(0, i).trim();
|
|
3223
|
+
const value = entry.slice(i + 1).trim();
|
|
3224
|
+
if (!key) throw new Error(`invalid --hub-header (expected "key:value"): ${entry}`);
|
|
3225
|
+
headers[key] = value;
|
|
3226
|
+
}
|
|
3227
|
+
return headers;
|
|
3228
|
+
}
|
|
3229
|
+
/**
|
|
3230
|
+
* Resolve custom hub headers from flags, falling back to `CCQA_HUB_HEADER`
|
|
3231
|
+
* (a single `"key:value"` entry) when no `--hub-header` flag was given.
|
|
3232
|
+
* CI setups typically pass exactly one gateway header via env; `--hub-header`
|
|
3233
|
+
* is repeatable for the (rarer) local/multi-header case.
|
|
3234
|
+
*/
|
|
3235
|
+
function resolveHubHeaders(hubHeader) {
|
|
3236
|
+
if (hubHeader && hubHeader.length > 0) return parseHubHeaders(hubHeader);
|
|
3237
|
+
const envHeader = process.env.CCQA_HUB_HEADER;
|
|
3238
|
+
if (envHeader) return parseHubHeaders([envHeader]);
|
|
3239
|
+
}
|
|
3202
3240
|
/** Thrown by `requireHubClient` when the URL and/or token can't be resolved from flags/env. */
|
|
3203
3241
|
var HubConnectionError = class extends Error {
|
|
3204
3242
|
constructor(message) {
|
|
@@ -3215,9 +3253,11 @@ function resolveHubClient(opts) {
|
|
|
3215
3253
|
const baseUrl = opts.hubUrl ?? process.env.CCQA_HUB_URL;
|
|
3216
3254
|
const token = opts.hubToken ?? process.env.CCQA_HUB_TOKEN;
|
|
3217
3255
|
if (!baseUrl || !token) return null;
|
|
3256
|
+
const headers = resolveHubHeaders(opts.hubHeader);
|
|
3218
3257
|
return createHubClient({
|
|
3219
3258
|
baseUrl: baseUrl.replace(/\/+$/, ""),
|
|
3220
|
-
token
|
|
3259
|
+
token,
|
|
3260
|
+
...headers ? { headers } : {}
|
|
3221
3261
|
});
|
|
3222
3262
|
}
|
|
3223
3263
|
/** Same as `resolveHubClient`, but throws `HubConnectionError` instead of returning `null`. */
|
|
@@ -3286,7 +3326,7 @@ function addProfileOption(command) {
|
|
|
3286
3326
|
* options so help text and behaviour don't drift.
|
|
3287
3327
|
*/
|
|
3288
3328
|
function addHubOptions(command) {
|
|
3289
|
-
return command.option(...hubUrlOption).option(...hubTokenOption);
|
|
3329
|
+
return command.option(...hubUrlOption).option(...hubTokenOption).option(...hubHeaderOption);
|
|
3290
3330
|
}
|
|
3291
3331
|
/**
|
|
3292
3332
|
* CLI wrapper around `resolveProfileEnv`: on failure, print the error and
|
|
@@ -5218,7 +5258,8 @@ async function executeRun(targets, opts) {
|
|
|
5218
5258
|
project: projectForProfile,
|
|
5219
5259
|
cwd,
|
|
5220
5260
|
hubUrl: opts.hubUrl,
|
|
5221
|
-
hubToken: opts.hubToken
|
|
5261
|
+
hubToken: opts.hubToken,
|
|
5262
|
+
hubHeader: opts.hubHeader
|
|
5222
5263
|
});
|
|
5223
5264
|
} else await resolveProfileEnv({
|
|
5224
5265
|
profile: void 0,
|
|
@@ -5236,6 +5277,7 @@ async function executeRun(targets, opts) {
|
|
|
5236
5277
|
hubCtx = resolveHubContext({
|
|
5237
5278
|
hubUrl: opts.hubUrl,
|
|
5238
5279
|
hubToken: opts.hubToken,
|
|
5280
|
+
hubHeader: opts.hubHeader,
|
|
5239
5281
|
project: opts.project,
|
|
5240
5282
|
cwd
|
|
5241
5283
|
});
|
|
@@ -8597,7 +8639,8 @@ const recordCommand = addHubOptions(addProfileOption(addLanguageOption(new Comma
|
|
|
8597
8639
|
project,
|
|
8598
8640
|
cwd: cwdForProfile,
|
|
8599
8641
|
hubUrl: opts.hubUrl,
|
|
8600
|
-
hubToken: opts.hubToken
|
|
8642
|
+
hubToken: opts.hubToken,
|
|
8643
|
+
hubHeader: opts.hubHeader
|
|
8601
8644
|
});
|
|
8602
8645
|
else await applyProfileFromOption({
|
|
8603
8646
|
profile: void 0,
|
|
@@ -8606,7 +8649,8 @@ const recordCommand = addHubOptions(addProfileOption(addLanguageOption(new Comma
|
|
|
8606
8649
|
});
|
|
8607
8650
|
const hubClientForTrace = resolveHubClient({
|
|
8608
8651
|
hubUrl: opts.hubUrl,
|
|
8609
|
-
hubToken: opts.hubToken
|
|
8652
|
+
hubToken: opts.hubToken,
|
|
8653
|
+
hubHeader: opts.hubHeader
|
|
8610
8654
|
});
|
|
8611
8655
|
const hubContext = hubClientForTrace && project ? {
|
|
8612
8656
|
hub: hubClientForTrace,
|
|
@@ -9325,7 +9369,7 @@ Return the spec keys that might be affected by any of the new files. Conservativ
|
|
|
9325
9369
|
//#endregion
|
|
9326
9370
|
//#region src/cli/drift.ts
|
|
9327
9371
|
const DEFAULT_CONCURRENCY = 3;
|
|
9328
|
-
const driftCommand = addLanguageOption(new Command("drift").argument("[feature/spec]", "Optional spec id. If omitted, every spec under .ccqa/features/ is checked.").description("Standalone spec ↔ codebase static audit. Use for PR checks where the browser isn't run. For run-time audit with a structured report, see `ccqa run --report`.").option("--format <fmt>", "Output format: text | json | github", "text").option("--severity <level>", "Exit non-zero on this severity or higher: warn | error", "error").option("--concurrency <n>", `Parallel spec checks (default: ${DEFAULT_CONCURRENCY})`).option("-m, --model <name>", "Claude model alias ('sonnet'|'opus'|'haiku') or full ID. Overrides CCQA_MODEL.").option("--cwd <path>", "Working directory used as both the .ccqa root and the codebase Claude reads. Useful for monorepos. Defaults to process.cwd().").option("--changed", "Restrict drift checks to specs whose relatedPaths intersect the git diff against --base (or, in CI, $GITHUB_BASE_REF, else origin/main). New files are routed to specs via a single lightweight Claude call.").option("--base <ref>", "Base ref to diff against when --changed is set. Defaults to $GITHUB_BASE_REF (CI) or origin/main.").option("--push", "Push the drift result to a ccqa hub as a run (kind: drift).").option("--project <name>", "Logical project name for the pushed run. Defaults to the current directory's name.").option(...hubUrlOption).option(...hubTokenOption)).action(async (specPath, opts) => {
|
|
9372
|
+
const driftCommand = addLanguageOption(new Command("drift").argument("[feature/spec]", "Optional spec id. If omitted, every spec under .ccqa/features/ is checked.").description("Standalone spec ↔ codebase static audit. Use for PR checks where the browser isn't run. For run-time audit with a structured report, see `ccqa run --report`.").option("--format <fmt>", "Output format: text | json | github", "text").option("--severity <level>", "Exit non-zero on this severity or higher: warn | error", "error").option("--concurrency <n>", `Parallel spec checks (default: ${DEFAULT_CONCURRENCY})`).option("-m, --model <name>", "Claude model alias ('sonnet'|'opus'|'haiku') or full ID. Overrides CCQA_MODEL.").option("--cwd <path>", "Working directory used as both the .ccqa root and the codebase Claude reads. Useful for monorepos. Defaults to process.cwd().").option("--changed", "Restrict drift checks to specs whose relatedPaths intersect the git diff against --base (or, in CI, $GITHUB_BASE_REF, else origin/main). New files are routed to specs via a single lightweight Claude call.").option("--base <ref>", "Base ref to diff against when --changed is set. Defaults to $GITHUB_BASE_REF (CI) or origin/main.").option("--push", "Push the drift result to a ccqa hub as a run (kind: drift).").option("--project <name>", "Logical project name for the pushed run. Defaults to the current directory's name.").option(...hubUrlOption).option(...hubTokenOption).option(...hubHeaderOption)).action(async (specPath, opts) => {
|
|
9329
9373
|
const format = parseFormat(opts.format);
|
|
9330
9374
|
const threshold = parseSeverity(opts.severity);
|
|
9331
9375
|
const concurrency = parseConcurrency(opts.concurrency);
|
|
@@ -11597,7 +11641,7 @@ const CSS = `
|
|
|
11597
11641
|
order decides). */
|
|
11598
11642
|
.chip.kind-chip { color: var(--violet); background: var(--violet-bg); border-color: var(--violet-border); font-family: var(--font); margin-left: 6px; }
|
|
11599
11643
|
.chip.drift-count-chip { color: var(--amber); background: var(--amber-bg); border-color: var(--amber-border); margin-left: 6px; }
|
|
11600
|
-
.chip.drift-errors-chip { color: var(--fail); background: var(--fail-bg); border-color: var(--fail-border); }
|
|
11644
|
+
.chip.drift-errors-chip { color: var(--fail); background: var(--fail-bg); border-color: var(--fail-border); margin-left: 6px; }
|
|
11601
11645
|
.drift-meta-box { display: flex; flex-direction: column; gap: 4px; }
|
|
11602
11646
|
.drift-meta-chips { display: flex; gap: 6px; }
|
|
11603
11647
|
.specs { display: inline-flex; align-items: center; gap: 9px; }
|
|
@@ -181,6 +181,12 @@ type LabelsExport = z.infer<typeof LabelsExportSchema>;
|
|
|
181
181
|
interface HubClientOptions {
|
|
182
182
|
baseUrl: string;
|
|
183
183
|
token: string;
|
|
184
|
+
/**
|
|
185
|
+
* Extra headers sent on every request, ahead of any per-call headers and
|
|
186
|
+
* the `Authorization` header (e.g. an infra gateway/ALB bypass header in
|
|
187
|
+
* CI — never overrides `Authorization`).
|
|
188
|
+
*/
|
|
189
|
+
headers?: Record<string, string>;
|
|
184
190
|
/** Override for testing; defaults to the global `fetch`. */
|
|
185
191
|
fetchImpl?: typeof fetch;
|
|
186
192
|
}
|
package/dist/package.json
CHANGED