@vertaaux/cli 0.3.3 → 0.5.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 +97 -0
- package/MIGRATION.md +239 -0
- package/README.md +34 -16
- package/dist/app/interactive-app.d.ts +101 -0
- package/dist/app/interactive-app.d.ts.map +1 -0
- package/dist/app/interactive-app.js +309 -0
- package/dist/app/layout/canvas.d.ts +23 -0
- package/dist/app/layout/canvas.d.ts.map +1 -0
- package/dist/app/layout/canvas.js +36 -0
- package/dist/app/layout/footer.d.ts +31 -0
- package/dist/app/layout/footer.d.ts.map +1 -0
- package/dist/app/layout/footer.js +41 -0
- package/dist/app/layout/header.d.ts +20 -0
- package/dist/app/layout/header.d.ts.map +1 -0
- package/dist/app/layout/header.js +27 -0
- package/dist/app/menu/categories.d.ts +20 -0
- package/dist/app/menu/categories.d.ts.map +1 -0
- package/dist/app/menu/categories.js +181 -0
- package/dist/app/menu/filter.d.ts +17 -0
- package/dist/app/menu/filter.d.ts.map +1 -0
- package/dist/app/menu/filter.js +33 -0
- package/dist/app/menu/menu-view.d.ts +35 -0
- package/dist/app/menu/menu-view.d.ts.map +1 -0
- package/dist/app/menu/menu-view.js +230 -0
- package/dist/app/menu/recent.d.ts +24 -0
- package/dist/app/menu/recent.d.ts.map +1 -0
- package/dist/app/menu/recent.js +49 -0
- package/dist/app/types.d.ts +43 -0
- package/dist/app/types.d.ts.map +1 -0
- package/dist/app/types.js +7 -0
- package/dist/app/views/command-runner.d.ts +36 -0
- package/dist/app/views/command-runner.d.ts.map +1 -0
- package/dist/app/views/command-runner.js +372 -0
- package/dist/app/views/help-overlay.d.ts +21 -0
- package/dist/app/views/help-overlay.d.ts.map +1 -0
- package/dist/app/views/help-overlay.js +45 -0
- package/dist/auth/ci-token.d.ts +14 -2
- package/dist/auth/ci-token.d.ts.map +1 -1
- package/dist/auth/ci-token.js +15 -30
- package/dist/auth/device-flow.d.ts +2 -1
- package/dist/auth/device-flow.d.ts.map +1 -1
- package/dist/auth/device-flow.js +13 -10
- package/dist/auth/token-store.d.ts.map +1 -1
- package/dist/auth/token-store.js +12 -2
- package/dist/baseline/diff.d.ts +2 -2
- package/dist/baseline/diff.d.ts.map +1 -1
- package/dist/baseline/diff.js +15 -34
- package/dist/commands/a11y.d.ts +9 -0
- package/dist/commands/a11y.d.ts.map +1 -0
- package/dist/commands/a11y.js +76 -0
- package/dist/commands/audit/artifacts.d.ts +27 -0
- package/dist/commands/audit/artifacts.d.ts.map +1 -0
- package/dist/commands/audit/artifacts.js +158 -0
- package/dist/commands/audit/ci-detection.d.ts +18 -0
- package/dist/commands/audit/ci-detection.d.ts.map +1 -0
- package/dist/commands/audit/ci-detection.js +71 -0
- package/dist/commands/audit/explain.d.ts +11 -0
- package/dist/commands/audit/explain.d.ts.map +1 -0
- package/dist/commands/audit/explain.js +45 -0
- package/dist/commands/audit/filters.d.ts +17 -0
- package/dist/commands/audit/filters.d.ts.map +1 -0
- package/dist/commands/audit/filters.js +40 -0
- package/dist/commands/audit/index.d.ts +18 -0
- package/dist/commands/audit/index.d.ts.map +1 -0
- package/dist/commands/audit/index.js +564 -0
- package/dist/commands/audit/output.d.ts +32 -0
- package/dist/commands/audit/output.d.ts.map +1 -0
- package/dist/commands/audit/output.js +130 -0
- package/dist/commands/audit/policy.d.ts +19 -0
- package/dist/commands/audit/policy.d.ts.map +1 -0
- package/dist/commands/audit/policy.js +102 -0
- package/dist/commands/audit/scoring.d.ts +23 -0
- package/dist/commands/audit/scoring.d.ts.map +1 -0
- package/dist/commands/audit/scoring.js +70 -0
- package/dist/commands/audit/types.d.ts +88 -0
- package/dist/commands/audit/types.d.ts.map +1 -0
- package/dist/commands/audit/types.js +8 -0
- package/dist/commands/audit.d.ts +2 -60
- package/dist/commands/audit.d.ts.map +1 -1
- package/dist/commands/audit.js +2 -1038
- package/dist/commands/baseline.d.ts +1 -0
- package/dist/commands/baseline.d.ts.map +1 -1
- package/dist/commands/baseline.js +205 -121
- package/dist/commands/comment.d.ts +22 -0
- package/dist/commands/comment.d.ts.map +1 -1
- package/dist/commands/comment.js +122 -58
- package/dist/commands/compare.d.ts +17 -0
- package/dist/commands/compare.d.ts.map +1 -1
- package/dist/commands/compare.js +287 -180
- package/dist/commands/diff.d.ts +5 -0
- package/dist/commands/diff.d.ts.map +1 -1
- package/dist/commands/diff.js +168 -141
- package/dist/commands/doc.d.ts +10 -0
- package/dist/commands/doc.d.ts.map +1 -1
- package/dist/commands/doc.js +134 -76
- package/dist/commands/doctor.d.ts +2 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +164 -17
- package/dist/commands/download.d.ts +10 -0
- package/dist/commands/download.d.ts.map +1 -1
- package/dist/commands/download.js +169 -112
- package/dist/commands/explain.d.ts +5 -0
- package/dist/commands/explain.d.ts.map +1 -1
- package/dist/commands/explain.js +241 -155
- package/dist/commands/fix-all.d.ts +25 -0
- package/dist/commands/fix-all.d.ts.map +1 -0
- package/dist/commands/fix-all.js +206 -0
- package/dist/commands/fix-plan.d.ts +9 -0
- package/dist/commands/fix-plan.d.ts.map +1 -1
- package/dist/commands/fix-plan.js +152 -89
- package/dist/commands/fix.d.ts +17 -0
- package/dist/commands/fix.d.ts.map +1 -0
- package/dist/commands/fix.js +111 -0
- package/dist/commands/init.d.ts +11 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +94 -42
- package/dist/commands/login.d.ts +18 -0
- package/dist/commands/login.d.ts.map +1 -1
- package/dist/commands/login.js +268 -95
- package/dist/commands/patch-review.d.ts +11 -0
- package/dist/commands/patch-review.d.ts.map +1 -1
- package/dist/commands/patch-review.js +159 -97
- package/dist/commands/policy.d.ts +31 -0
- package/dist/commands/policy.d.ts.map +1 -1
- package/dist/commands/policy.js +269 -124
- package/dist/commands/release-notes.d.ts +10 -0
- package/dist/commands/release-notes.d.ts.map +1 -1
- package/dist/commands/release-notes.js +127 -73
- package/dist/commands/scan.d.ts +13 -0
- package/dist/commands/scan.d.ts.map +1 -0
- package/dist/commands/scan.js +133 -0
- package/dist/commands/status.d.ts +9 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +81 -0
- package/dist/commands/suggest.d.ts +10 -0
- package/dist/commands/suggest.d.ts.map +1 -1
- package/dist/commands/suggest.js +153 -82
- package/dist/commands/triage.d.ts +35 -0
- package/dist/commands/triage.d.ts.map +1 -1
- package/dist/commands/triage.js +206 -81
- package/dist/commands/upload.d.ts +9 -0
- package/dist/commands/upload.d.ts.map +1 -1
- package/dist/commands/upload.js +140 -101
- package/dist/commands/verify.d.ts +13 -0
- package/dist/commands/verify.d.ts.map +1 -0
- package/dist/commands/verify.js +118 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +125 -990
- package/dist/interactive/fix-wizard.d.ts +3 -0
- package/dist/interactive/fix-wizard.d.ts.map +1 -1
- package/dist/interactive/fix-wizard.js +130 -112
- package/dist/interactive/init-wizard.d.ts +3 -1
- package/dist/interactive/init-wizard.d.ts.map +1 -1
- package/dist/interactive/init-wizard.js +207 -138
- package/dist/interactive/prompts.d.ts +7 -3
- package/dist/interactive/prompts.d.ts.map +1 -1
- package/dist/interactive/prompts.js +44 -23
- package/dist/output/envelope.d.ts +2 -0
- package/dist/output/envelope.d.ts.map +1 -1
- package/dist/output/envelope.js +18 -2
- package/dist/output/factory.d.ts +9 -1
- package/dist/output/factory.d.ts.map +1 -1
- package/dist/output/html.d.ts +2 -1
- package/dist/output/html.d.ts.map +1 -1
- package/dist/output/html.js +3 -2
- package/dist/output/human.d.ts +9 -1
- package/dist/output/human.d.ts.map +1 -1
- package/dist/output/human.js +17 -2
- package/dist/output/json.d.ts +2 -1
- package/dist/output/json.d.ts.map +1 -1
- package/dist/output/junit.d.ts +2 -1
- package/dist/output/junit.d.ts.map +1 -1
- package/dist/output/sarif.d.ts +2 -1
- package/dist/output/sarif.d.ts.map +1 -1
- package/dist/types.d.ts +74 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/ui/banner.d.ts +34 -0
- package/dist/ui/banner.d.ts.map +1 -1
- package/dist/ui/banner.js +97 -5
- package/dist/ui/diagnostics.d.ts +9 -4
- package/dist/ui/diagnostics.d.ts.map +1 -1
- package/dist/ui/diagnostics.js +32 -82
- package/dist/ui/strings.d.ts +373 -0
- package/dist/ui/strings.d.ts.map +1 -0
- package/dist/ui/strings.js +499 -0
- package/dist/ui/table.d.ts +0 -2
- package/dist/ui/table.d.ts.map +1 -1
- package/dist/ui/table.js +3 -4
- package/dist/utils/api-client.d.ts +46 -0
- package/dist/utils/api-client.d.ts.map +1 -0
- package/dist/utils/api-client.js +170 -0
- package/dist/utils/client.d.ts +29 -18
- package/dist/utils/client.d.ts.map +1 -1
- package/dist/utils/client.js +102 -12
- package/dist/utils/formatters.d.ts +38 -0
- package/dist/utils/formatters.d.ts.map +1 -0
- package/dist/utils/formatters.js +277 -0
- package/dist/utils/local-capture.d.ts +25 -0
- package/dist/utils/local-capture.d.ts.map +1 -0
- package/dist/utils/local-capture.js +57 -0
- package/dist/utils/url-classify.d.ts +18 -0
- package/dist/utils/url-classify.d.ts.map +1 -0
- package/dist/utils/url-classify.js +106 -0
- package/node_modules/@vertaaux/tui/dist/index.cjs +713 -20
- package/node_modules/@vertaaux/tui/dist/index.cjs.map +1 -1
- package/node_modules/@vertaaux/tui/dist/index.d.cts +361 -4
- package/node_modules/@vertaaux/tui/dist/index.d.ts +361 -4
- package/node_modules/@vertaaux/tui/dist/index.js +689 -21
- package/node_modules/@vertaaux/tui/dist/index.js.map +1 -1
- package/package.json +13 -5
- package/dist/commands/client.d.ts +0 -14
- package/dist/commands/client.d.ts.map +0 -1
- package/dist/commands/client.js +0 -362
- package/dist/commands/drift.d.ts +0 -15
- package/dist/commands/drift.d.ts.map +0 -1
- package/dist/commands/drift.js +0 -309
- package/dist/commands/protect.d.ts +0 -16
- package/dist/commands/protect.d.ts.map +0 -1
- package/dist/commands/protect.js +0 -323
- package/dist/commands/report.d.ts +0 -15
- package/dist/commands/report.d.ts.map +0 -1
- package/dist/commands/report.js +0 -214
- package/dist/policy/sync.d.ts +0 -67
- package/dist/policy/sync.d.ts.map +0 -1
- package/dist/policy/sync.js +0 -147
package/dist/commands/doctor.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Reports pass/fail with actionable remediation instructions.
|
|
6
6
|
* Never crashes -- all checks are wrapped in try/catch.
|
|
7
7
|
*/
|
|
8
|
-
import
|
|
8
|
+
import { bold, dim, colorize, brand, severity as severityPalette, runSteps, createRenderer, renderWarning, isCI, isTTY } from "@vertaaux/tui";
|
|
9
9
|
import { createRequire } from "module";
|
|
10
10
|
import { resolveConfig } from "../config/loader.js";
|
|
11
11
|
import { ConfigValidationError, ConfigLoadError } from "../config/loader.js";
|
|
@@ -16,6 +16,7 @@ import { ExitCode } from "../utils/exit-codes.js";
|
|
|
16
16
|
import { writeJsonOutput, writeOutput } from "../output/envelope.js";
|
|
17
17
|
import { COMMAND_FORMATS } from "../output/formats.js";
|
|
18
18
|
import { getVersion } from "../ui/banner.js";
|
|
19
|
+
import { strings } from "../ui/strings.js";
|
|
19
20
|
const require = createRequire(import.meta.url);
|
|
20
21
|
// ---------------------------------------------------------------------------
|
|
21
22
|
// Individual health checks
|
|
@@ -249,51 +250,194 @@ export function checkOutputContracts(deep) {
|
|
|
249
250
|
// ---------------------------------------------------------------------------
|
|
250
251
|
export function formatDoctorHuman(result) {
|
|
251
252
|
const statusIcons = {
|
|
252
|
-
pass:
|
|
253
|
-
fail:
|
|
254
|
-
warn:
|
|
255
|
-
skip:
|
|
253
|
+
pass: colorize("PASS", brand.lime),
|
|
254
|
+
fail: colorize("FAIL", severityPalette.error),
|
|
255
|
+
warn: colorize("WARN", severityPalette.warning),
|
|
256
|
+
skip: dim("SKIP"),
|
|
256
257
|
};
|
|
257
258
|
const lines = [];
|
|
258
259
|
const version = getVersion();
|
|
259
|
-
lines.push(
|
|
260
|
+
lines.push(bold(strings.doctor.title(version)));
|
|
260
261
|
lines.push("");
|
|
261
262
|
for (const check of result.checks) {
|
|
262
263
|
lines.push(` ${statusIcons[check.status]} ${check.name}: ${check.message}`);
|
|
263
264
|
if (check.remediation) {
|
|
264
|
-
lines.push(` ${
|
|
265
|
+
lines.push(` ${colorize(strings.doctor.checks.fix, brand.cyan)} ${check.remediation}`);
|
|
265
266
|
}
|
|
266
267
|
if (check.detail) {
|
|
267
|
-
lines.push(` ${
|
|
268
|
+
lines.push(` ${dim(check.detail)}`);
|
|
268
269
|
}
|
|
269
270
|
}
|
|
270
271
|
lines.push("");
|
|
271
272
|
const { pass, fail, warn } = result.summary;
|
|
272
273
|
if (fail > 0) {
|
|
273
|
-
lines.push(
|
|
274
|
+
lines.push(colorize(strings.doctor.results.withFailures(fail, pass + fail + warn), severityPalette.error) +
|
|
274
275
|
" Run the suggested fix commands above.");
|
|
275
276
|
}
|
|
276
277
|
else if (warn > 0) {
|
|
277
|
-
lines.push(
|
|
278
|
+
lines.push(colorize(strings.doctor.results.withWarnings(warn), severityPalette.warning));
|
|
278
279
|
}
|
|
279
280
|
else {
|
|
280
|
-
lines.push(
|
|
281
|
+
lines.push(colorize(strings.doctor.results.allPassed(pass), brand.lime));
|
|
281
282
|
}
|
|
282
283
|
return lines.join("\n");
|
|
283
284
|
}
|
|
284
285
|
// ---------------------------------------------------------------------------
|
|
286
|
+
// Step failure mode diagnostic
|
|
287
|
+
// ---------------------------------------------------------------------------
|
|
288
|
+
/**
|
|
289
|
+
* Build a DoctorCheck describing the current step failure mode and how to override it.
|
|
290
|
+
* This is always informational (status: "pass").
|
|
291
|
+
*/
|
|
292
|
+
function buildStepFailureModeDiagnostic(strict, continueOnError) {
|
|
293
|
+
let detectedMode;
|
|
294
|
+
let overrideHint;
|
|
295
|
+
if (strict) {
|
|
296
|
+
detectedMode = "fail-fast (--strict flag)";
|
|
297
|
+
overrideHint = "Use --continue-on-error to continue on step errors";
|
|
298
|
+
}
|
|
299
|
+
else if (continueOnError) {
|
|
300
|
+
detectedMode = "continue (--continue-on-error flag)";
|
|
301
|
+
overrideHint = "Use --strict to fail immediately on first step error";
|
|
302
|
+
}
|
|
303
|
+
else if (isCI()) {
|
|
304
|
+
detectedMode = "fail-fast (CI detected)";
|
|
305
|
+
overrideHint = "Use --continue-on-error to get partial results in CI";
|
|
306
|
+
}
|
|
307
|
+
else if (isTTY()) {
|
|
308
|
+
detectedMode = "continue (interactive TTY)";
|
|
309
|
+
overrideHint = "Use --strict to fail-fast in interactive mode";
|
|
310
|
+
}
|
|
311
|
+
else {
|
|
312
|
+
detectedMode = "fail-fast (non-TTY)";
|
|
313
|
+
overrideHint = "Use --continue-on-error to get partial results";
|
|
314
|
+
}
|
|
315
|
+
return {
|
|
316
|
+
name: "Step failure mode",
|
|
317
|
+
status: "pass",
|
|
318
|
+
message: detectedMode,
|
|
319
|
+
detail: `Override: ${overrideHint}. Use --strict to fail-fast in interactive mode, --continue-on-error for partial results in CI`,
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
// ---------------------------------------------------------------------------
|
|
285
323
|
// Command handler
|
|
286
324
|
// ---------------------------------------------------------------------------
|
|
287
325
|
export async function handleDoctor(options) {
|
|
288
326
|
const format = options.format || "human";
|
|
289
327
|
const online = options.online || false;
|
|
290
328
|
const deep = options.deep || false;
|
|
329
|
+
// Resolve fail-fast mode: --strict > --continue-on-error > auto-detect
|
|
330
|
+
let failFast;
|
|
331
|
+
if (options.strict && options.continueOnError) {
|
|
332
|
+
writeOutput(renderWarning({ message: "--strict and --continue-on-error both set — --strict takes precedence" }));
|
|
333
|
+
failFast = true;
|
|
334
|
+
}
|
|
335
|
+
else if (options.strict) {
|
|
336
|
+
failFast = true;
|
|
337
|
+
}
|
|
338
|
+
else if (options.continueOnError) {
|
|
339
|
+
failFast = false;
|
|
340
|
+
}
|
|
341
|
+
else {
|
|
342
|
+
failFast = isCI() || !isTTY();
|
|
343
|
+
}
|
|
291
344
|
const checks = [];
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
345
|
+
const checkResults = [];
|
|
346
|
+
const renderer = createRenderer("auto");
|
|
347
|
+
const baseState = {
|
|
348
|
+
phase: "doctor",
|
|
349
|
+
phaseIndex: 1,
|
|
350
|
+
phaseTotal: 4,
|
|
351
|
+
url: "",
|
|
352
|
+
mode: "doctor",
|
|
353
|
+
progress: {},
|
|
354
|
+
totals: {},
|
|
355
|
+
issueCount: 0,
|
|
356
|
+
scorePreview: null,
|
|
357
|
+
verbose: false,
|
|
358
|
+
elapsed: 0,
|
|
359
|
+
};
|
|
360
|
+
// Define doctor check steps with 1:1 mapping to check functions
|
|
361
|
+
const steps = [
|
|
362
|
+
{
|
|
363
|
+
id: "config",
|
|
364
|
+
actionText: "Checking configuration...",
|
|
365
|
+
summaryText: "Configuration checked",
|
|
366
|
+
run: async () => {
|
|
367
|
+
const result = await checkConfig();
|
|
368
|
+
checkResults.push(result);
|
|
369
|
+
if (result.status === "fail") {
|
|
370
|
+
throw new Error(result.message);
|
|
371
|
+
}
|
|
372
|
+
},
|
|
373
|
+
},
|
|
374
|
+
{
|
|
375
|
+
id: "auth",
|
|
376
|
+
actionText: "Checking authentication...",
|
|
377
|
+
summaryText: "Authentication checked",
|
|
378
|
+
run: async () => {
|
|
379
|
+
const result = await checkAuth(online);
|
|
380
|
+
checkResults.push(result);
|
|
381
|
+
if (result.status === "fail") {
|
|
382
|
+
throw new Error(result.message);
|
|
383
|
+
}
|
|
384
|
+
},
|
|
385
|
+
},
|
|
386
|
+
{
|
|
387
|
+
id: "network",
|
|
388
|
+
actionText: "Checking network connectivity...",
|
|
389
|
+
summaryText: "Network checked",
|
|
390
|
+
run: async () => {
|
|
391
|
+
const result = await checkNetwork();
|
|
392
|
+
checkResults.push(result);
|
|
393
|
+
if (result.status === "fail") {
|
|
394
|
+
throw new Error(result.message);
|
|
395
|
+
}
|
|
396
|
+
},
|
|
397
|
+
},
|
|
398
|
+
{
|
|
399
|
+
id: "contracts",
|
|
400
|
+
actionText: "Checking output contracts...",
|
|
401
|
+
summaryText: "Output contracts checked",
|
|
402
|
+
run: async () => {
|
|
403
|
+
const result = checkOutputContracts(deep);
|
|
404
|
+
checkResults.push(result);
|
|
405
|
+
if (result.status === "fail") {
|
|
406
|
+
throw new Error(result.message);
|
|
407
|
+
}
|
|
408
|
+
},
|
|
409
|
+
},
|
|
410
|
+
];
|
|
411
|
+
const startTime = Date.now();
|
|
412
|
+
await runSteps(steps, {
|
|
413
|
+
failFast,
|
|
414
|
+
onStateChange: (stepStates) => {
|
|
415
|
+
renderer.update({ ...baseState, stepStates, elapsed: Date.now() - startTime });
|
|
416
|
+
},
|
|
417
|
+
});
|
|
418
|
+
renderer.finish({
|
|
419
|
+
url: "",
|
|
420
|
+
mode: "doctor",
|
|
421
|
+
overallScore: 0,
|
|
422
|
+
scores: {},
|
|
423
|
+
issueCount: 0,
|
|
424
|
+
passed: true,
|
|
425
|
+
elapsed: Date.now() - startTime,
|
|
426
|
+
});
|
|
427
|
+
// Collect checks: use results captured during runSteps for ran steps.
|
|
428
|
+
// For steps skipped by fail-fast, create a skip placeholder.
|
|
429
|
+
const ranNames = new Set(checkResults.map((c) => c.name));
|
|
430
|
+
const checkNames = ["Configuration", "Authentication", "Network", "Output Contracts"];
|
|
431
|
+
for (const name of checkNames) {
|
|
432
|
+
if (ranNames.has(name)) {
|
|
433
|
+
checks.push(checkResults.find((c) => c.name === name));
|
|
434
|
+
}
|
|
435
|
+
else {
|
|
436
|
+
checks.push({ name, status: "skip", message: "Skipped due to earlier failure" });
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
// Add step failure mode diagnostic (always informational, always passes)
|
|
440
|
+
checks.push(buildStepFailureModeDiagnostic(options.strict, options.continueOnError));
|
|
297
441
|
// Build summary
|
|
298
442
|
const summary = { pass: 0, fail: 0, warn: 0, skip: 0 };
|
|
299
443
|
for (const check of checks) {
|
|
@@ -325,12 +469,15 @@ export function registerDoctorCommand(program) {
|
|
|
325
469
|
.option("--online", "Verify auth credentials against API (default: offline check)")
|
|
326
470
|
.option("--deep", "Run output contract self-tests")
|
|
327
471
|
.option("--format <format>", "Output format: human, json", "human")
|
|
472
|
+
.option("--strict", "Fail immediately on first check failure")
|
|
473
|
+
.option("--continue-on-error", "Continue running checks even after failures")
|
|
328
474
|
.action(async (options) => {
|
|
329
475
|
try {
|
|
330
476
|
await handleDoctor(options);
|
|
331
477
|
}
|
|
332
478
|
catch (error) {
|
|
333
|
-
// Doctor must NEVER crash
|
|
479
|
+
// Doctor must NEVER crash — raw fallback to avoid renderError() recursion
|
|
480
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
334
481
|
process.stderr.write(`Error: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
335
482
|
process.exit(ExitCode.ERROR);
|
|
336
483
|
}
|
|
@@ -5,6 +5,16 @@
|
|
|
5
5
|
* Enables pulling results from CI runs or shared audits.
|
|
6
6
|
*/
|
|
7
7
|
import { Command } from "commander";
|
|
8
|
+
/**
|
|
9
|
+
* Handle the download command.
|
|
10
|
+
*/
|
|
11
|
+
export declare function handleDownload(jobId: string, options: {
|
|
12
|
+
baseline?: boolean;
|
|
13
|
+
output?: string;
|
|
14
|
+
base?: string;
|
|
15
|
+
force?: boolean;
|
|
16
|
+
configPath?: string;
|
|
17
|
+
}): Promise<void>;
|
|
8
18
|
/**
|
|
9
19
|
* Register the download command with the Commander program.
|
|
10
20
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"download.d.ts","sourceRoot":"","sources":["../../src/commands/download.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"download.d.ts","sourceRoot":"","sources":["../../src/commands/download.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAqDpC;;GAEG;AACH,wBAAsB,cAAc,CAClC,KAAK,EAAE,MAAM,EACb,OAAO,EAAE;IACP,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,GACA,OAAO,CAAC,IAAI,CAAC,CAgNf;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA6B9D"}
|
|
@@ -6,16 +6,17 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import fs from "fs";
|
|
8
8
|
import path from "path";
|
|
9
|
-
import
|
|
10
|
-
import { createSpinner, succeedSpinner, failSpinner, warnSpinner, infoSpinner } from "../ui/spinner.js";
|
|
9
|
+
import { renderError, runSteps, createRenderer } from "@vertaaux/tui";
|
|
11
10
|
import { loadToken } from "../auth/token-store.js";
|
|
12
11
|
import { getCIToken } from "../auth/ci-token.js";
|
|
13
|
-
import { resolveApiBase } from "../utils/client.js";
|
|
12
|
+
import { apiRequest, resolveApiBase } from "../utils/client.js";
|
|
14
13
|
import { resolveConfig } from "../config/loader.js";
|
|
15
14
|
import { saveBaseline } from "../baseline/manager.js";
|
|
16
15
|
import { isInteractive, confirmAction } from "../interactive/prompts.js";
|
|
17
16
|
import { ExitCode } from "../utils/exit-codes.js";
|
|
18
17
|
import { assertPathContainment } from "../utils/sanitize.js";
|
|
18
|
+
import { writeOutput } from "../output/envelope.js";
|
|
19
|
+
import { strings } from "../ui/strings.js";
|
|
19
20
|
/**
|
|
20
21
|
* Default output directory.
|
|
21
22
|
*/
|
|
@@ -35,135 +36,181 @@ async function getAuthToken() {
|
|
|
35
36
|
/**
|
|
36
37
|
* Handle the download command.
|
|
37
38
|
*/
|
|
38
|
-
async function handleDownload(jobId, options) {
|
|
39
|
+
export async function handleDownload(jobId, options) {
|
|
39
40
|
// Get auth token
|
|
40
41
|
const token = await getAuthToken();
|
|
41
42
|
if (!token) {
|
|
42
|
-
|
|
43
|
-
|
|
43
|
+
process.stderr.write(renderError({
|
|
44
|
+
message: strings.download.errors.notAuthenticated,
|
|
45
|
+
suggestion: "vertaa login",
|
|
46
|
+
exitCode: ExitCode.ERROR,
|
|
47
|
+
}) + "\n");
|
|
44
48
|
process.exit(ExitCode.ERROR);
|
|
45
49
|
}
|
|
46
50
|
// Load config for API base (supports --config global option)
|
|
47
|
-
|
|
51
|
+
await resolveConfig(options.configPath);
|
|
48
52
|
const apiBase = resolveApiBase(options.base);
|
|
49
53
|
const outputDir = options.output || DEFAULT_OUTPUT_DIR;
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
54
|
+
const renderer = createRenderer("auto");
|
|
55
|
+
const baseState = {
|
|
56
|
+
phase: "download",
|
|
57
|
+
phaseIndex: 1,
|
|
58
|
+
phaseTotal: 1,
|
|
59
|
+
url: "",
|
|
60
|
+
mode: "download",
|
|
61
|
+
progress: {},
|
|
62
|
+
totals: {},
|
|
63
|
+
issueCount: 0,
|
|
64
|
+
scorePreview: null,
|
|
65
|
+
verbose: false,
|
|
66
|
+
elapsed: 0,
|
|
67
|
+
};
|
|
68
|
+
const startTime = Date.now();
|
|
69
|
+
// Use a container so TypeScript can track the value across step closures
|
|
70
|
+
const ctx = {
|
|
71
|
+
downloadResult: null,
|
|
72
|
+
resolvedOutputDir: "",
|
|
73
|
+
skippedFiles: [],
|
|
74
|
+
};
|
|
75
|
+
const steps = [
|
|
76
|
+
{
|
|
77
|
+
id: "fetch",
|
|
78
|
+
actionText: strings.download.run.action,
|
|
79
|
+
summaryText: "Audit data retrieved",
|
|
80
|
+
run: async () => {
|
|
81
|
+
// Make API request via apiRequest() — auth header constructed in client.ts only
|
|
82
|
+
const downloadPath = `/sync/download/${jobId}${options.baseline ? "?include_baseline=true" : ""}`;
|
|
83
|
+
const result = await apiRequest(apiBase, downloadPath, { method: "GET" }, token);
|
|
84
|
+
if (!result.success) {
|
|
85
|
+
throw new Error(result.error?.message || "Download failed");
|
|
86
|
+
}
|
|
87
|
+
ctx.downloadResult = result;
|
|
64
88
|
},
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
id: "save",
|
|
92
|
+
actionText: "Writing to file...",
|
|
93
|
+
summaryText: "File saved",
|
|
94
|
+
run: async () => {
|
|
95
|
+
const dlResult = ctx.downloadResult;
|
|
96
|
+
if (!dlResult)
|
|
97
|
+
throw new Error("No download result");
|
|
98
|
+
// Create output directory
|
|
99
|
+
ctx.resolvedOutputDir = path.resolve(process.cwd(), outputDir);
|
|
100
|
+
if (!fs.existsSync(ctx.resolvedOutputDir)) {
|
|
101
|
+
fs.mkdirSync(ctx.resolvedOutputDir, { recursive: true });
|
|
102
|
+
}
|
|
103
|
+
// Save audit results
|
|
104
|
+
if (dlResult.audit) {
|
|
105
|
+
const auditPath = path.join(ctx.resolvedOutputDir, `audit-${jobId}.json`);
|
|
106
|
+
// Check for existing file
|
|
107
|
+
if (fs.existsSync(auditPath) && !options.force) {
|
|
108
|
+
if (isInteractive()) {
|
|
109
|
+
const overwrite = await confirmAction(`File ${auditPath} already exists. Overwrite?`, false);
|
|
110
|
+
if (!overwrite) {
|
|
111
|
+
ctx.skippedFiles.push("audit results");
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
fs.writeFileSync(auditPath, JSON.stringify(dlResult.audit, null, 2), "utf-8");
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
ctx.skippedFiles.push("audit results (non-interactive)");
|
|
119
|
+
}
|
|
88
120
|
}
|
|
89
121
|
else {
|
|
90
|
-
fs.writeFileSync(auditPath, JSON.stringify(
|
|
122
|
+
fs.writeFileSync(auditPath, JSON.stringify(dlResult.audit, null, 2), "utf-8");
|
|
91
123
|
}
|
|
92
124
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
125
|
+
// Save baseline if included
|
|
126
|
+
if (options.baseline && dlResult.baseline) {
|
|
127
|
+
const baselinePath = path.join(ctx.resolvedOutputDir, "baseline.json");
|
|
128
|
+
// Check for existing baseline
|
|
129
|
+
if (fs.existsSync(baselinePath) && !options.force) {
|
|
130
|
+
if (isInteractive()) {
|
|
131
|
+
const overwrite = await confirmAction(`Baseline file already exists. Overwrite?`, false);
|
|
132
|
+
if (!overwrite) {
|
|
133
|
+
ctx.skippedFiles.push("baseline");
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
await saveBaseline(dlResult.baseline, baselinePath);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
ctx.skippedFiles.push("baseline (non-interactive)");
|
|
141
|
+
}
|
|
110
142
|
}
|
|
111
143
|
else {
|
|
112
|
-
await saveBaseline(
|
|
144
|
+
await saveBaseline(dlResult.baseline, baselinePath);
|
|
113
145
|
}
|
|
114
146
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
const isBinary = [".png", ".jpg", ".jpeg", ".gif", ".zip"].includes(ext);
|
|
140
|
-
if (isBinary) {
|
|
141
|
-
fs.writeFileSync(filePath, Buffer.from(content, "base64"));
|
|
142
|
-
}
|
|
143
|
-
else {
|
|
144
|
-
fs.writeFileSync(filePath, content, "utf-8");
|
|
147
|
+
// Save artifacts if included
|
|
148
|
+
if (dlResult.artifacts) {
|
|
149
|
+
const artifactsDir = path.join(ctx.resolvedOutputDir, "artifacts", jobId);
|
|
150
|
+
if (!fs.existsSync(artifactsDir)) {
|
|
151
|
+
fs.mkdirSync(artifactsDir, { recursive: true });
|
|
152
|
+
}
|
|
153
|
+
for (const [filename, content] of Object.entries(dlResult.artifacts)) {
|
|
154
|
+
let filePath;
|
|
155
|
+
try {
|
|
156
|
+
filePath = assertPathContainment(filename, artifactsDir);
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
ctx.skippedFiles.push(`${filename} (path traversal)`);
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
const ext = path.extname(filename).toLowerCase();
|
|
163
|
+
const isBinary = [".png", ".jpg", ".jpeg", ".gif", ".zip"].includes(ext);
|
|
164
|
+
if (isBinary) {
|
|
165
|
+
fs.writeFileSync(filePath, Buffer.from(content, "base64"));
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
fs.writeFileSync(filePath, content, "utf-8");
|
|
169
|
+
}
|
|
170
|
+
}
|
|
145
171
|
}
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
];
|
|
175
|
+
const { success, states } = await runSteps(steps, {
|
|
176
|
+
failFast: true,
|
|
177
|
+
onStateChange: (stepStates) => {
|
|
178
|
+
renderer.update({ ...baseState, stepStates, elapsed: Date.now() - startTime });
|
|
179
|
+
},
|
|
180
|
+
});
|
|
181
|
+
renderer.finish({ url: "", mode: "download", overallScore: 0, scores: {}, issueCount: 0, passed: success, elapsed: Date.now() - startTime });
|
|
182
|
+
if (!success) {
|
|
183
|
+
const failed = states.find(s => s.status === "failed");
|
|
184
|
+
process.stderr.write(renderError({
|
|
185
|
+
message: failed?.failReason || "Command failed",
|
|
186
|
+
suggestion: "vertaa doctor",
|
|
187
|
+
}) + "\n");
|
|
188
|
+
process.exitCode = ExitCode.ERROR;
|
|
189
|
+
}
|
|
190
|
+
const { downloadResult, resolvedOutputDir, skippedFiles } = ctx;
|
|
191
|
+
if (downloadResult) {
|
|
192
|
+
const artifactCount = downloadResult.artifacts ? Object.keys(downloadResult.artifacts).length : (downloadResult.audit ? 1 : 0);
|
|
193
|
+
const lines = [
|
|
194
|
+
strings.download.run.done(artifactCount),
|
|
195
|
+
"",
|
|
196
|
+
` Job ID: ${downloadResult.job_id}`,
|
|
197
|
+
` Output: ${resolvedOutputDir}`,
|
|
198
|
+
];
|
|
199
|
+
if (downloadResult.audit) {
|
|
200
|
+
lines.push(` Status: ${downloadResult.audit.status}`);
|
|
201
|
+
if (downloadResult.audit.url) {
|
|
202
|
+
lines.push(` URL: ${downloadResult.audit.url}`);
|
|
146
203
|
}
|
|
147
204
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
console.error(` Job ID: ${result.job_id}`);
|
|
151
|
-
console.error(` Output: ${resolvedOutputDir}`);
|
|
152
|
-
if (result.audit) {
|
|
153
|
-
console.error(` Status: ${result.audit.status}`);
|
|
154
|
-
if (result.audit.url) {
|
|
155
|
-
console.error(` URL: ${result.audit.url}`);
|
|
156
|
-
}
|
|
205
|
+
if (options.baseline && downloadResult.baseline) {
|
|
206
|
+
lines.push(` Baseline: ${downloadResult.baseline.issues.length} issues`);
|
|
157
207
|
}
|
|
158
|
-
if (
|
|
159
|
-
|
|
208
|
+
if (skippedFiles.length > 0) {
|
|
209
|
+
lines.push("");
|
|
210
|
+
lines.push(` Skipped: ${skippedFiles.join(", ")}`);
|
|
160
211
|
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
catch (error) {
|
|
164
|
-
failSpinner(spinner, "Download failed");
|
|
165
|
-
console.error(chalk.red("Error:"), error instanceof Error ? error.message : String(error));
|
|
166
|
-
process.exit(ExitCode.ERROR);
|
|
212
|
+
lines.push("");
|
|
213
|
+
writeOutput(lines.join("\n"));
|
|
167
214
|
}
|
|
168
215
|
}
|
|
169
216
|
/**
|
|
@@ -178,7 +225,17 @@ export function registerDownloadCommand(program) {
|
|
|
178
225
|
.option("-b, --base <url>", "API base URL")
|
|
179
226
|
.option("-f, --force", "Overwrite existing files without prompting")
|
|
180
227
|
.action(async (jobId, options, command) => {
|
|
181
|
-
|
|
182
|
-
|
|
228
|
+
try {
|
|
229
|
+
const globalOpts = command.optsWithGlobals();
|
|
230
|
+
await handleDownload(jobId, { ...options, configPath: globalOpts.config });
|
|
231
|
+
}
|
|
232
|
+
catch (error) {
|
|
233
|
+
process.stderr.write(renderError({
|
|
234
|
+
message: error instanceof Error ? error.message : String(error),
|
|
235
|
+
suggestion: "vertaa doctor",
|
|
236
|
+
exitCode: ExitCode.ERROR,
|
|
237
|
+
}) + "\n");
|
|
238
|
+
process.exit(ExitCode.ERROR);
|
|
239
|
+
}
|
|
183
240
|
});
|
|
184
241
|
}
|
|
@@ -34,10 +34,15 @@ export declare function formatEvidenceJson(issue: EvidenceIssue): string;
|
|
|
34
34
|
/** Exported for reuse in fix-wizard. */
|
|
35
35
|
export declare function explainIssue(issue: EvidenceIssue): string;
|
|
36
36
|
export interface ExplainCommandOptions {
|
|
37
|
+
findingId?: string;
|
|
37
38
|
job?: string;
|
|
38
39
|
file?: string;
|
|
39
40
|
format?: string;
|
|
40
41
|
base?: string;
|
|
42
|
+
verbose?: boolean;
|
|
43
|
+
machine?: boolean;
|
|
44
|
+
apiKey?: string;
|
|
41
45
|
}
|
|
46
|
+
export declare function handleExplain(opts: ExplainCommandOptions): Promise<void>;
|
|
42
47
|
export declare function registerExplainCommand(program: Command): void;
|
|
43
48
|
//# sourceMappingURL=explain.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"explain.d.ts","sourceRoot":"","sources":["../../src/commands/explain.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"explain.d.ts","sourceRoot":"","sources":["../../src/commands/explain.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAWpC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAOjD;;GAEG;AACH,MAAM,WAAW,aAAc,SAAQ,KAAK;IAC1C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAiFD;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,aAAa,GAAG,MAAM,CAyCjE;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,aAAa,GAAG,MAAM,CAmB/D;AAED,wCAAwC;AACxC,wBAAgB,YAAY,CAAC,KAAK,EAAE,aAAa,GAAG,MAAM,CAEzD;AAmID,MAAM,WAAW,qBAAqB;IACpC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAsB,aAAa,CAAC,IAAI,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAsO9E;AAMD,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAoD7D"}
|