@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/diff.js
CHANGED
|
@@ -12,52 +12,18 @@
|
|
|
12
12
|
import { readFile } from "fs/promises";
|
|
13
13
|
import { existsSync } from "fs";
|
|
14
14
|
import { ExitCode } from "../utils/exit-codes.js";
|
|
15
|
+
import { createClient } from "../utils/client.js";
|
|
15
16
|
import { loadBaseline, DEFAULT_BASELINE_PATH } from "../baseline/manager.js";
|
|
16
17
|
import { computeDiff, formatDiffHuman, formatDiffJson } from "../baseline/diff.js";
|
|
17
18
|
import { writeJsonOutput, writeOutput } from "../output/envelope.js";
|
|
18
19
|
import { resolveCommandFormat } from "../output/formats.js";
|
|
19
|
-
|
|
20
|
+
import { strings } from "../ui/strings.js";
|
|
21
|
+
import { runSteps, createRenderer, renderWarning, renderError, isCI, isTTY } from "@vertaaux/tui";
|
|
20
22
|
/**
|
|
21
|
-
*
|
|
22
|
-
*/
|
|
23
|
-
function getApiBase() {
|
|
24
|
-
return (process.env.VERTAAUX_API_BASE || DEFAULT_API_BASE).replace(/\/$/, "");
|
|
25
|
-
}
|
|
26
|
-
/**
|
|
27
|
-
* Get API key from environment.
|
|
28
|
-
*/
|
|
29
|
-
function getApiKey() {
|
|
30
|
-
const key = process.env.VERTAAUX_API_KEY;
|
|
31
|
-
if (!key) {
|
|
32
|
-
throw new Error("VERTAAUX_API_KEY is required");
|
|
33
|
-
}
|
|
34
|
-
return key;
|
|
35
|
-
}
|
|
36
|
-
/**
|
|
37
|
-
* Fetch audit results from API.
|
|
23
|
+
* Fetch audit results via SDK client.
|
|
38
24
|
*/
|
|
39
25
|
async function fetchAudit(jobId) {
|
|
40
|
-
|
|
41
|
-
const url = `${base}/audit/${jobId}`;
|
|
42
|
-
const res = await fetch(url, {
|
|
43
|
-
method: "GET",
|
|
44
|
-
headers: {
|
|
45
|
-
"Content-Type": "application/json",
|
|
46
|
-
"X-API-Key": getApiKey(),
|
|
47
|
-
},
|
|
48
|
-
});
|
|
49
|
-
if (!res.ok) {
|
|
50
|
-
let detail = res.statusText;
|
|
51
|
-
try {
|
|
52
|
-
const data = (await res.json());
|
|
53
|
-
detail = data.error || data.message || detail;
|
|
54
|
-
}
|
|
55
|
-
catch {
|
|
56
|
-
// ignore
|
|
57
|
-
}
|
|
58
|
-
throw new Error(`HTTP ${res.status}: ${detail}`);
|
|
59
|
-
}
|
|
60
|
-
return (await res.json());
|
|
26
|
+
return createClient().audits.retrieve(jobId);
|
|
61
27
|
}
|
|
62
28
|
/**
|
|
63
29
|
* Normalize issues from various API response formats.
|
|
@@ -71,6 +37,157 @@ function normalizeIssues(issues) {
|
|
|
71
37
|
}
|
|
72
38
|
return [];
|
|
73
39
|
}
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
// Handler — exported for CommandRunnerView
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
export async function handleDiff(opts) {
|
|
44
|
+
const baselinePath = opts.baseline || DEFAULT_BASELINE_PATH;
|
|
45
|
+
const format = resolveCommandFormat("diff", opts.format, opts.machine || false);
|
|
46
|
+
const verbose = opts.verbose || false;
|
|
47
|
+
const jobIdArg = opts.jobIdArg;
|
|
48
|
+
// Resolve fail-fast mode: --strict > --continue-on-error > auto-detect
|
|
49
|
+
let failFast;
|
|
50
|
+
if (opts.strict && opts.continueOnError) {
|
|
51
|
+
writeOutput(renderWarning({ message: "--strict and --continue-on-error both set — --strict takes precedence" }));
|
|
52
|
+
failFast = true;
|
|
53
|
+
}
|
|
54
|
+
else if (opts.strict) {
|
|
55
|
+
failFast = true;
|
|
56
|
+
}
|
|
57
|
+
else if (opts.continueOnError) {
|
|
58
|
+
failFast = false;
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
failFast = isCI() || !isTTY();
|
|
62
|
+
}
|
|
63
|
+
const ctx = { diffOutput: null };
|
|
64
|
+
const renderer = createRenderer("auto");
|
|
65
|
+
const baseState = {
|
|
66
|
+
phase: "diff",
|
|
67
|
+
phaseIndex: 1,
|
|
68
|
+
phaseTotal: 1,
|
|
69
|
+
url: "",
|
|
70
|
+
mode: "diff",
|
|
71
|
+
progress: {},
|
|
72
|
+
totals: {},
|
|
73
|
+
issueCount: 0,
|
|
74
|
+
scorePreview: null,
|
|
75
|
+
verbose: false,
|
|
76
|
+
elapsed: 0,
|
|
77
|
+
};
|
|
78
|
+
const steps = [
|
|
79
|
+
{
|
|
80
|
+
id: "load",
|
|
81
|
+
actionText: strings.diff.run.action,
|
|
82
|
+
summaryText: strings.diff.run.done(0, 0),
|
|
83
|
+
run: async () => {
|
|
84
|
+
// Get current audit issues
|
|
85
|
+
let currentIssues;
|
|
86
|
+
let currentUrl;
|
|
87
|
+
if (opts.fromFile) {
|
|
88
|
+
const filePath = opts.fromFile;
|
|
89
|
+
if (!existsSync(filePath)) {
|
|
90
|
+
throw new Error(`File not found: ${filePath}`);
|
|
91
|
+
}
|
|
92
|
+
const content = await readFile(filePath, "utf-8");
|
|
93
|
+
const data = JSON.parse(content);
|
|
94
|
+
currentIssues = normalizeIssues(data.issues);
|
|
95
|
+
currentUrl = data.url || "unknown";
|
|
96
|
+
}
|
|
97
|
+
else if (jobIdArg) {
|
|
98
|
+
const audit = await fetchAudit(jobIdArg);
|
|
99
|
+
if (audit.status !== "completed") {
|
|
100
|
+
throw new Error(strings.fixAll.auditIncomplete(jobIdArg || "unknown", audit.status || "unknown"));
|
|
101
|
+
}
|
|
102
|
+
currentIssues = normalizeIssues(audit.issues);
|
|
103
|
+
currentUrl = audit.url || "unknown";
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
throw new Error("Provide job ID or use --from-file");
|
|
107
|
+
}
|
|
108
|
+
// Handle --compare: compare two audits
|
|
109
|
+
if (opts.compare) {
|
|
110
|
+
const compareAudit = await fetchAudit(opts.compare);
|
|
111
|
+
if (compareAudit.status !== "completed") {
|
|
112
|
+
throw new Error(strings.fixAll.auditIncomplete(opts.compare || "unknown", compareAudit.status || "unknown"));
|
|
113
|
+
}
|
|
114
|
+
const compareIssues = normalizeIssues(compareAudit.issues);
|
|
115
|
+
const tempBaseline = {
|
|
116
|
+
version: 1,
|
|
117
|
+
created: new Date().toISOString(),
|
|
118
|
+
updated: new Date().toISOString(),
|
|
119
|
+
url: compareAudit.url || "unknown",
|
|
120
|
+
issues: compareIssues.map((issue) => ({
|
|
121
|
+
fingerprint: "",
|
|
122
|
+
ruleId: issue.ruleId || issue.rule_id || issue.id || "unknown",
|
|
123
|
+
severity: issue.severity || "warning",
|
|
124
|
+
category: issue.category || "general",
|
|
125
|
+
description: issue.description || "",
|
|
126
|
+
selector: issue.selector,
|
|
127
|
+
baselinedAt: new Date().toISOString(),
|
|
128
|
+
})),
|
|
129
|
+
};
|
|
130
|
+
const { generateFingerprint } = await import("../baseline/hash.js");
|
|
131
|
+
for (let i = 0; i < tempBaseline.issues.length; i++) {
|
|
132
|
+
tempBaseline.issues[i].fingerprint = generateFingerprint(compareIssues[i]);
|
|
133
|
+
}
|
|
134
|
+
const diff = computeDiff(currentIssues, tempBaseline);
|
|
135
|
+
const diffData = format === "json" ? JSON.parse(formatDiffJson(diff)) : null;
|
|
136
|
+
ctx.diffOutput = { diffData, newCount: diff.summary.newCount, currentUrl };
|
|
137
|
+
if (format !== "json") {
|
|
138
|
+
writeOutput(`Comparing ${currentUrl} (current) vs ${tempBaseline.url} (previous)\n`);
|
|
139
|
+
writeOutput(formatDiffHuman(diff, verbose));
|
|
140
|
+
}
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
// Load baseline
|
|
144
|
+
const baseline = await loadBaseline(baselinePath);
|
|
145
|
+
if (!baseline) {
|
|
146
|
+
throw new Error(strings.diff.errors.noBaseline);
|
|
147
|
+
}
|
|
148
|
+
const diff = computeDiff(currentIssues, baseline);
|
|
149
|
+
const diffData = format === "json" ? JSON.parse(formatDiffJson(diff)) : null;
|
|
150
|
+
ctx.diffOutput = { diffData, newCount: diff.summary.newCount, currentUrl };
|
|
151
|
+
if (format !== "json") {
|
|
152
|
+
writeOutput(`Comparing ${currentUrl} against baseline\n`);
|
|
153
|
+
writeOutput(formatDiffHuman(diff, verbose));
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
];
|
|
158
|
+
const { success, states } = await runSteps(steps, {
|
|
159
|
+
failFast,
|
|
160
|
+
onStateChange: (stepStates) => {
|
|
161
|
+
renderer.update({ ...baseState, stepStates });
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
renderer.finish({
|
|
165
|
+
url: "",
|
|
166
|
+
mode: "diff",
|
|
167
|
+
overallScore: success ? 100 : 0,
|
|
168
|
+
scores: {},
|
|
169
|
+
issueCount: 0,
|
|
170
|
+
passed: success,
|
|
171
|
+
elapsed: 0,
|
|
172
|
+
});
|
|
173
|
+
if (!success) {
|
|
174
|
+
const failedStep = states.find((s) => s.status === "failed");
|
|
175
|
+
process.stderr.write(renderError({
|
|
176
|
+
message: strings.diff.errors.noBaseline,
|
|
177
|
+
context: failedStep?.failReason,
|
|
178
|
+
suggestion: "vertaa audit <url>",
|
|
179
|
+
}) + "\n");
|
|
180
|
+
process.exit(ExitCode.ERROR);
|
|
181
|
+
}
|
|
182
|
+
// Output JSON if requested
|
|
183
|
+
if (format === "json" && ctx.diffOutput) {
|
|
184
|
+
writeJsonOutput(ctx.diffOutput.diffData, "diff");
|
|
185
|
+
}
|
|
186
|
+
// Exit code: 1 if new issues
|
|
187
|
+
if (ctx.diffOutput && ctx.diffOutput.newCount > 0) {
|
|
188
|
+
process.exit(1);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
74
191
|
/**
|
|
75
192
|
* Register the diff command with the Commander program.
|
|
76
193
|
*/
|
|
@@ -83,113 +200,23 @@ export function registerDiffCommand(program) {
|
|
|
83
200
|
.option("--format <format>", "Output format: json | human (default: auto)")
|
|
84
201
|
.option("--verbose", "Show all present issues")
|
|
85
202
|
.option("--from-file <file>", "Read current audit from JSON file")
|
|
203
|
+
.option("--strict", "Fail immediately on first step error")
|
|
204
|
+
.option("--continue-on-error", "Continue on step errors even in CI")
|
|
86
205
|
.action(async (jobIdArg, cmdOptions, cmd) => {
|
|
87
206
|
try {
|
|
88
|
-
const baselinePath = cmdOptions.baseline || DEFAULT_BASELINE_PATH;
|
|
89
207
|
const machineMode = cmd.optsWithGlobals?.().machine || false;
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
if (cmdOptions.fromFile) {
|
|
96
|
-
// Load from file
|
|
97
|
-
const filePath = cmdOptions.fromFile;
|
|
98
|
-
if (!existsSync(filePath)) {
|
|
99
|
-
console.error(`File not found: ${filePath}`);
|
|
100
|
-
process.exit(ExitCode.ERROR);
|
|
101
|
-
}
|
|
102
|
-
const content = await readFile(filePath, "utf-8");
|
|
103
|
-
const data = JSON.parse(content);
|
|
104
|
-
currentIssues = normalizeIssues(data.issues);
|
|
105
|
-
currentUrl = data.url || "unknown";
|
|
106
|
-
}
|
|
107
|
-
else if (jobIdArg) {
|
|
108
|
-
// Fetch from API
|
|
109
|
-
const audit = await fetchAudit(jobIdArg);
|
|
110
|
-
if (audit.status !== "completed") {
|
|
111
|
-
console.error(`Audit ${jobIdArg} is not completed (status: ${audit.status})`);
|
|
112
|
-
process.exit(ExitCode.ERROR);
|
|
113
|
-
}
|
|
114
|
-
currentIssues = normalizeIssues(audit.issues);
|
|
115
|
-
currentUrl = audit.url || "unknown";
|
|
116
|
-
}
|
|
117
|
-
else {
|
|
118
|
-
console.error("Provide job ID or use --from-file");
|
|
119
|
-
console.error("Usage: vertaa diff <job-id>");
|
|
120
|
-
console.error(" vertaa diff --from-file <audit.json>");
|
|
121
|
-
process.exit(ExitCode.ERROR);
|
|
122
|
-
}
|
|
123
|
-
// Handle --compare: compare two audits
|
|
124
|
-
if (cmdOptions.compare) {
|
|
125
|
-
const compareAudit = await fetchAudit(cmdOptions.compare);
|
|
126
|
-
if (compareAudit.status !== "completed") {
|
|
127
|
-
console.error(`Audit ${cmdOptions.compare} is not completed (status: ${compareAudit.status})`);
|
|
128
|
-
process.exit(ExitCode.ERROR);
|
|
129
|
-
}
|
|
130
|
-
// Create temporary baseline from compare job
|
|
131
|
-
const compareIssues = normalizeIssues(compareAudit.issues);
|
|
132
|
-
const tempBaseline = {
|
|
133
|
-
version: 1,
|
|
134
|
-
created: new Date().toISOString(),
|
|
135
|
-
updated: new Date().toISOString(),
|
|
136
|
-
url: compareAudit.url || "unknown",
|
|
137
|
-
issues: compareIssues.map((issue) => ({
|
|
138
|
-
fingerprint: "",
|
|
139
|
-
ruleId: issue.ruleId || issue.rule_id || issue.id || "unknown",
|
|
140
|
-
severity: issue.severity || "warning",
|
|
141
|
-
category: issue.category || "general",
|
|
142
|
-
description: issue.description || "",
|
|
143
|
-
selector: issue.selector,
|
|
144
|
-
baselinedAt: new Date().toISOString(),
|
|
145
|
-
})),
|
|
146
|
-
};
|
|
147
|
-
// Re-generate fingerprints using the proper function
|
|
148
|
-
const { generateFingerprint } = await import("../baseline/hash.js");
|
|
149
|
-
for (let i = 0; i < tempBaseline.issues.length; i++) {
|
|
150
|
-
tempBaseline.issues[i].fingerprint = generateFingerprint(compareIssues[i]);
|
|
151
|
-
}
|
|
152
|
-
const diff = computeDiff(currentIssues, tempBaseline);
|
|
153
|
-
// Output
|
|
154
|
-
if (format === "json") {
|
|
155
|
-
const diffData = JSON.parse(formatDiffJson(diff));
|
|
156
|
-
writeJsonOutput(diffData, "diff");
|
|
157
|
-
}
|
|
158
|
-
else {
|
|
159
|
-
console.error(`Comparing ${currentUrl} (current) vs ${tempBaseline.url} (previous)\n`);
|
|
160
|
-
writeOutput(formatDiffHuman(diff, verbose));
|
|
161
|
-
}
|
|
162
|
-
// Exit code: 1 if new issues
|
|
163
|
-
if (diff.summary.newCount > 0) {
|
|
164
|
-
process.exit(1);
|
|
165
|
-
}
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
// Load baseline
|
|
169
|
-
const baseline = await loadBaseline(baselinePath);
|
|
170
|
-
if (!baseline) {
|
|
171
|
-
console.error(`No baseline found at: ${baselinePath}`);
|
|
172
|
-
console.error("Create one with: vertaa baseline <job-id>");
|
|
173
|
-
process.exit(ExitCode.ERROR);
|
|
174
|
-
}
|
|
175
|
-
// Compute diff
|
|
176
|
-
const diff = computeDiff(currentIssues, baseline);
|
|
177
|
-
// Output
|
|
178
|
-
if (format === "json") {
|
|
179
|
-
const diffData = JSON.parse(formatDiffJson(diff));
|
|
180
|
-
writeJsonOutput(diffData, "diff");
|
|
181
|
-
}
|
|
182
|
-
else {
|
|
183
|
-
console.error(`Comparing ${currentUrl} against baseline\n`);
|
|
184
|
-
writeOutput(formatDiffHuman(diff, verbose));
|
|
185
|
-
}
|
|
186
|
-
// Exit code: 1 if new issues
|
|
187
|
-
if (diff.summary.newCount > 0) {
|
|
188
|
-
process.exit(1);
|
|
189
|
-
}
|
|
208
|
+
await handleDiff({
|
|
209
|
+
...cmdOptions,
|
|
210
|
+
machine: machineMode,
|
|
211
|
+
jobIdArg,
|
|
212
|
+
});
|
|
190
213
|
}
|
|
191
214
|
catch (error) {
|
|
192
|
-
|
|
215
|
+
process.stderr.write(renderError({
|
|
216
|
+
message: error instanceof Error ? error.message : String(error),
|
|
217
|
+
suggestion: "vertaa audit <url>",
|
|
218
|
+
exitCode: ExitCode.ERROR,
|
|
219
|
+
}) + "\n");
|
|
193
220
|
process.exit(ExitCode.ERROR);
|
|
194
221
|
}
|
|
195
222
|
});
|
package/dist/commands/doc.d.ts
CHANGED
|
@@ -14,5 +14,15 @@
|
|
|
14
14
|
* vertaa doc --file audit.json --team "Frontend Team"
|
|
15
15
|
*/
|
|
16
16
|
import { Command } from "commander";
|
|
17
|
+
export interface DocCommandOptions {
|
|
18
|
+
job?: string;
|
|
19
|
+
file?: string;
|
|
20
|
+
team?: string;
|
|
21
|
+
format?: string;
|
|
22
|
+
base?: string;
|
|
23
|
+
machine?: boolean;
|
|
24
|
+
apiKey?: string;
|
|
25
|
+
}
|
|
26
|
+
export declare function handleDoc(opts: DocCommandOptions): Promise<void>;
|
|
17
27
|
export declare function registerDocCommand(program: Command): void;
|
|
18
28
|
//# sourceMappingURL=doc.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"doc.d.ts","sourceRoot":"","sources":["../../src/commands/doc.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAqFpC,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"doc.d.ts","sourceRoot":"","sources":["../../src/commands/doc.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAqFpC,MAAM,WAAW,iBAAiB;IAChC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAsB,SAAS,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAyItE;AAMD,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA2CzD"}
|
package/dist/commands/doc.js
CHANGED
|
@@ -13,15 +13,15 @@
|
|
|
13
13
|
* vertaa doc --job abc123
|
|
14
14
|
* vertaa doc --file audit.json --team "Frontend Team"
|
|
15
15
|
*/
|
|
16
|
-
import
|
|
16
|
+
import { bold, dim, renderError, createRenderer, runSteps } from "@vertaaux/tui";
|
|
17
17
|
import { ExitCode } from "../utils/exit-codes.js";
|
|
18
|
-
import { resolveApiBase, getApiKey, apiRequest } from "../utils/client.js";
|
|
18
|
+
import { resolveApiBase, getApiKey, apiRequest, createClient } from "../utils/client.js";
|
|
19
19
|
import { resolveConfig } from "../config/loader.js";
|
|
20
20
|
import { writeJsonOutput, writeOutput } from "../output/envelope.js";
|
|
21
21
|
import { resolveCommandFormat } from "../output/formats.js";
|
|
22
|
-
import { createSpinner, succeedSpinner } from "../ui/spinner.js";
|
|
23
22
|
import { readJsonInput } from "../utils/stdin.js";
|
|
24
|
-
import {
|
|
23
|
+
import { AI_TIMEOUT_MS } from "../utils/ai-error.js";
|
|
24
|
+
import { strings } from "../ui/strings.js";
|
|
25
25
|
// ---------------------------------------------------------------------------
|
|
26
26
|
// Helpers
|
|
27
27
|
// ---------------------------------------------------------------------------
|
|
@@ -55,13 +55,126 @@ function normalizeIssues(issues) {
|
|
|
55
55
|
// ---------------------------------------------------------------------------
|
|
56
56
|
function formatDocHuman(data) {
|
|
57
57
|
const lines = [];
|
|
58
|
-
lines.push(
|
|
59
|
-
lines.push(
|
|
60
|
-
lines.push(
|
|
58
|
+
lines.push(bold(data.title));
|
|
59
|
+
lines.push(dim("─".repeat(40)));
|
|
60
|
+
lines.push(dim(`Sections: ${data.sections.join(", ")}`));
|
|
61
61
|
lines.push("");
|
|
62
62
|
lines.push(data.content);
|
|
63
63
|
return lines.join("\n");
|
|
64
64
|
}
|
|
65
|
+
export async function handleDoc(opts) {
|
|
66
|
+
const config = { apiKey: opts.apiKey };
|
|
67
|
+
const format = resolveCommandFormat("doc", opts.format, opts.machine || false);
|
|
68
|
+
const renderer = createRenderer("auto");
|
|
69
|
+
const baseState = {
|
|
70
|
+
phase: "doc",
|
|
71
|
+
phaseIndex: 1,
|
|
72
|
+
phaseTotal: 2,
|
|
73
|
+
url: "",
|
|
74
|
+
mode: "doc",
|
|
75
|
+
progress: {},
|
|
76
|
+
totals: {},
|
|
77
|
+
issueCount: 0,
|
|
78
|
+
scorePreview: null,
|
|
79
|
+
verbose: false,
|
|
80
|
+
elapsed: 0,
|
|
81
|
+
};
|
|
82
|
+
const startTime = Date.now();
|
|
83
|
+
let responseData = null;
|
|
84
|
+
const steps = [
|
|
85
|
+
{
|
|
86
|
+
id: "analyze",
|
|
87
|
+
actionText: strings.doc.generate.action,
|
|
88
|
+
summaryText: "Project analyzed",
|
|
89
|
+
run: async () => {
|
|
90
|
+
// Resolve audit data
|
|
91
|
+
let auditPayload;
|
|
92
|
+
if (opts.job) {
|
|
93
|
+
const sdkClient = createClient({ base: opts.base, apiKey: config.apiKey });
|
|
94
|
+
const result = await sdkClient.audits.retrieve(opts.job);
|
|
95
|
+
const issues = normalizeIssues(result.issues);
|
|
96
|
+
auditPayload = {
|
|
97
|
+
job_id: result.job_id || opts.job,
|
|
98
|
+
url: result.url || null,
|
|
99
|
+
scores: result.scores || null,
|
|
100
|
+
issues,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
const input = await readJsonInput(opts.file);
|
|
105
|
+
if (!input) {
|
|
106
|
+
throw new Error("No audit data provided.\nUsage:\n vertaa audit https://example.com --json | vertaa doc\n vertaa doc --job <job-id>\n vertaa doc --file audit.json");
|
|
107
|
+
}
|
|
108
|
+
const data = input;
|
|
109
|
+
const innerData = (data.data && typeof data.data === "object" ? data.data : data);
|
|
110
|
+
const issues = normalizeIssues(innerData.issues);
|
|
111
|
+
auditPayload = {
|
|
112
|
+
job_id: innerData.job_id || null,
|
|
113
|
+
url: innerData.url || null,
|
|
114
|
+
scores: innerData.scores || null,
|
|
115
|
+
issues,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
if (!Array.isArray(auditPayload.issues) ||
|
|
119
|
+
auditPayload.issues.length === 0) {
|
|
120
|
+
throw new Error("No issues found in audit data.");
|
|
121
|
+
}
|
|
122
|
+
const base = resolveApiBase(opts.base);
|
|
123
|
+
const apiKey = getApiKey(config.apiKey);
|
|
124
|
+
const response = await Promise.race([
|
|
125
|
+
apiRequest(base, "/cli/ai/doc", {
|
|
126
|
+
method: "POST",
|
|
127
|
+
body: {
|
|
128
|
+
audit: auditPayload,
|
|
129
|
+
teamName: opts.team || null,
|
|
130
|
+
},
|
|
131
|
+
}, apiKey),
|
|
132
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error("LLM request timed out")), AI_TIMEOUT_MS)),
|
|
133
|
+
]);
|
|
134
|
+
responseData = response.data;
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
id: "generate",
|
|
139
|
+
actionText: "Generating documentation...",
|
|
140
|
+
summaryText: responseData ? strings.doc.generate.done(responseData.sections.length) : "Documentation generated",
|
|
141
|
+
run: async () => {
|
|
142
|
+
// Document generation happened in the analyze step via API call
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
];
|
|
146
|
+
const { success, states } = await runSteps(steps, {
|
|
147
|
+
onStateChange: (stepStates) => {
|
|
148
|
+
renderer.update({ ...baseState, stepStates, elapsed: Date.now() - startTime });
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
renderer.finish({
|
|
152
|
+
url: "",
|
|
153
|
+
mode: "doc",
|
|
154
|
+
overallScore: success ? 100 : 0,
|
|
155
|
+
scores: {},
|
|
156
|
+
issueCount: 0,
|
|
157
|
+
passed: success,
|
|
158
|
+
elapsed: Date.now() - startTime,
|
|
159
|
+
});
|
|
160
|
+
if (!success) {
|
|
161
|
+
const failed = states.find(s => s.status === "failed");
|
|
162
|
+
process.stderr.write(renderError({
|
|
163
|
+
message: failed?.failReason || "Command failed",
|
|
164
|
+
suggestion: "vertaa doctor",
|
|
165
|
+
}) + "\n");
|
|
166
|
+
process.exitCode = ExitCode.ERROR;
|
|
167
|
+
}
|
|
168
|
+
if (success && responseData) {
|
|
169
|
+
const data = responseData;
|
|
170
|
+
if (format === "json") {
|
|
171
|
+
writeJsonOutput(data, "doc");
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
writeOutput(format === "markdown" ? data.content : formatDocHuman(data));
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
65
178
|
// ---------------------------------------------------------------------------
|
|
66
179
|
// Command Registration
|
|
67
180
|
// ---------------------------------------------------------------------------
|
|
@@ -84,77 +197,22 @@ Examples:
|
|
|
84
197
|
const globalOpts = command.optsWithGlobals();
|
|
85
198
|
const config = await resolveConfig(globalOpts.config);
|
|
86
199
|
const machineMode = globalOpts.machine || false;
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
job_id: result.job_id || options.job,
|
|
97
|
-
url: result.url || null,
|
|
98
|
-
scores: result.scores || null,
|
|
99
|
-
issues,
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
else {
|
|
103
|
-
const input = await readJsonInput(options.file);
|
|
104
|
-
if (!input) {
|
|
105
|
-
console.error("Error: No audit data provided.");
|
|
106
|
-
console.error("Usage:");
|
|
107
|
-
console.error(" vertaa audit https://example.com --json | vertaa doc");
|
|
108
|
-
console.error(" vertaa doc --job <job-id>");
|
|
109
|
-
console.error(" vertaa doc --file audit.json");
|
|
110
|
-
process.exit(ExitCode.ERROR);
|
|
111
|
-
}
|
|
112
|
-
const data = input;
|
|
113
|
-
const innerData = (data.data && typeof data.data === "object" ? data.data : data);
|
|
114
|
-
const issues = normalizeIssues(innerData.issues);
|
|
115
|
-
auditPayload = {
|
|
116
|
-
job_id: innerData.job_id || null,
|
|
117
|
-
url: innerData.url || null,
|
|
118
|
-
scores: innerData.scores || null,
|
|
119
|
-
issues,
|
|
120
|
-
};
|
|
121
|
-
}
|
|
122
|
-
if (!Array.isArray(auditPayload.issues) ||
|
|
123
|
-
auditPayload.issues.length === 0) {
|
|
124
|
-
console.error("Error: No issues found in audit data.");
|
|
125
|
-
process.exit(ExitCode.ERROR);
|
|
126
|
-
}
|
|
127
|
-
// Auth check
|
|
128
|
-
const base = resolveApiBase(globalOpts.base);
|
|
129
|
-
const apiKey = getApiKey(config.apiKey);
|
|
130
|
-
// Call LLM doc API
|
|
131
|
-
const spinner = createSpinner("Generating team playbook...");
|
|
132
|
-
try {
|
|
133
|
-
const response = await Promise.race([
|
|
134
|
-
apiRequest(base, "/cli/ai/doc", {
|
|
135
|
-
method: "POST",
|
|
136
|
-
body: {
|
|
137
|
-
audit: auditPayload,
|
|
138
|
-
teamName: options.team || null,
|
|
139
|
-
},
|
|
140
|
-
}, apiKey),
|
|
141
|
-
new Promise((_, reject) => setTimeout(() => reject(new Error("LLM request timed out")), AI_TIMEOUT_MS)),
|
|
142
|
-
]);
|
|
143
|
-
succeedSpinner(spinner, "Playbook ready");
|
|
144
|
-
if (format === "json") {
|
|
145
|
-
writeJsonOutput(response.data, "doc");
|
|
146
|
-
}
|
|
147
|
-
else {
|
|
148
|
-
// Default markdown output — just emit the content directly
|
|
149
|
-
writeOutput(format === "markdown" ? response.data.content : formatDocHuman(response.data));
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
catch (error) {
|
|
153
|
-
handleAiCommandError(error, "doc", spinner);
|
|
154
|
-
}
|
|
200
|
+
await handleDoc({
|
|
201
|
+
job: options.job,
|
|
202
|
+
file: options.file,
|
|
203
|
+
team: options.team,
|
|
204
|
+
format: options.format,
|
|
205
|
+
base: globalOpts.base,
|
|
206
|
+
machine: machineMode,
|
|
207
|
+
apiKey: config.apiKey,
|
|
208
|
+
});
|
|
155
209
|
}
|
|
156
210
|
catch (error) {
|
|
157
|
-
|
|
211
|
+
process.stderr.write(renderError({
|
|
212
|
+
message: error instanceof Error ? error.message : String(error),
|
|
213
|
+
suggestion: "vertaa audit <url>",
|
|
214
|
+
exitCode: ExitCode.ERROR,
|
|
215
|
+
}) + "\n");
|
|
158
216
|
process.exit(ExitCode.ERROR);
|
|
159
217
|
}
|
|
160
218
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAoBzC,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;IAC1C,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;CACrE;AAMD;;;GAGG;AACH,wBAAsB,WAAW,IAAI,OAAO,CAAC,WAAW,CAAC,CAkCxD;AAED;;;;GAIG;AACH,wBAAsB,SAAS,CAAC,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,WAAW,CAAC,CA4FrE;AAED;;;;GAIG;AACH,wBAAsB,YAAY,IAAI,OAAO,CAAC,WAAW,CAAC,CAsBzD;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,OAAO,GAAG,WAAW,CAuD/D;AAMD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAqC9D;AA2CD,wBAAsB,YAAY,CAAC,OAAO,EAAE;IAC1C,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B,GAAG,OAAO,CAAC,IAAI,CAAC,CA+IhB;AAMD;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA6B5D"}
|