@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
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Output formatting utilities for the VertaaUX CLI legacy commands.
|
|
3
|
+
* Provides markdown and JSON formatting functions for audit/fix/verify results.
|
|
4
|
+
*/
|
|
5
|
+
import { scoreColor, boldColor } from "@vertaaux/tui";
|
|
6
|
+
import { writeOutput } from "../output/envelope.js";
|
|
7
|
+
// ============================================================================
|
|
8
|
+
// Data normalization
|
|
9
|
+
// ============================================================================
|
|
10
|
+
export function normalizeIssues(issues) {
|
|
11
|
+
if (Array.isArray(issues))
|
|
12
|
+
return issues;
|
|
13
|
+
if (issues && typeof issues === "object") {
|
|
14
|
+
const values = Object.values(issues);
|
|
15
|
+
return values.flatMap((value) => (Array.isArray(value) ? value : []));
|
|
16
|
+
}
|
|
17
|
+
return [];
|
|
18
|
+
}
|
|
19
|
+
export function toNumber(value) {
|
|
20
|
+
if (typeof value === "number" && Number.isFinite(value))
|
|
21
|
+
return value;
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
export function getOverallScore(scores) {
|
|
25
|
+
if (!scores)
|
|
26
|
+
return null;
|
|
27
|
+
const s = scores;
|
|
28
|
+
const direct = toNumber(s.overall ?? s.ux ?? s.total);
|
|
29
|
+
if (direct !== null)
|
|
30
|
+
return direct;
|
|
31
|
+
const numeric = Object.values(s)
|
|
32
|
+
.map((value) => toNumber(value))
|
|
33
|
+
.filter((value) => value !== null);
|
|
34
|
+
if (numeric.length === 0)
|
|
35
|
+
return null;
|
|
36
|
+
const avg = numeric.reduce((sum, value) => sum + value, 0) / numeric.length;
|
|
37
|
+
return Math.round(avg);
|
|
38
|
+
}
|
|
39
|
+
export function getCategoryScore(scores, key) {
|
|
40
|
+
if (!scores)
|
|
41
|
+
return null;
|
|
42
|
+
return toNumber(scores[key]);
|
|
43
|
+
}
|
|
44
|
+
// ============================================================================
|
|
45
|
+
// Markdown formatters
|
|
46
|
+
// ============================================================================
|
|
47
|
+
export function formatScoresTable(scores) {
|
|
48
|
+
if (!scores)
|
|
49
|
+
return "No scores available.";
|
|
50
|
+
const entries = Object.entries(scores).filter(([, value]) => typeof value === "number");
|
|
51
|
+
if (entries.length === 0)
|
|
52
|
+
return "No scores available.";
|
|
53
|
+
const lines = ["| Category | Score |", "| --- | --- |"];
|
|
54
|
+
for (const [key, value] of entries) {
|
|
55
|
+
lines.push(`| ${key} | ${value} |`);
|
|
56
|
+
}
|
|
57
|
+
return lines.join("\n");
|
|
58
|
+
}
|
|
59
|
+
export function formatIssuesList(issues, limit = 5) {
|
|
60
|
+
if (!issues.length)
|
|
61
|
+
return "No issues.";
|
|
62
|
+
const lines = issues.slice(0, limit).map((issue, index) => {
|
|
63
|
+
const label = issue.title || issue.id || "Issue";
|
|
64
|
+
const severity = issue.severity ? issue.severity.toUpperCase() : "INFO";
|
|
65
|
+
return `${index + 1}. [${severity}] ${label}`;
|
|
66
|
+
});
|
|
67
|
+
if (issues.length > limit) {
|
|
68
|
+
lines.push(`...and ${issues.length - limit} more`);
|
|
69
|
+
}
|
|
70
|
+
return lines.join("\n");
|
|
71
|
+
}
|
|
72
|
+
export function formatAuditMarkdown(result) {
|
|
73
|
+
const status = result.status || "unknown";
|
|
74
|
+
const lines = [`## Audit Results`, `- Status: ${status}`];
|
|
75
|
+
if (result.job_id)
|
|
76
|
+
lines.push(`- Job ID: ${result.job_id}`);
|
|
77
|
+
if (result.url)
|
|
78
|
+
lines.push(`- URL: ${result.url}`);
|
|
79
|
+
if (result.mode)
|
|
80
|
+
lines.push(`- Mode: ${result.mode}`);
|
|
81
|
+
if (typeof result.progress === "number" && status !== "completed") {
|
|
82
|
+
lines.push(`- Progress: ${result.progress}%`);
|
|
83
|
+
}
|
|
84
|
+
if (status !== "completed")
|
|
85
|
+
return lines.join("\n");
|
|
86
|
+
const overall = getOverallScore(result.scores);
|
|
87
|
+
if (overall !== null)
|
|
88
|
+
lines.push(`- Overall score: ${boldColor(String(overall), scoreColor(overall))}`);
|
|
89
|
+
lines.push("\n### Scores");
|
|
90
|
+
lines.push(formatScoresTable(result.scores));
|
|
91
|
+
const issues = normalizeIssues(result.issues);
|
|
92
|
+
lines.push("\n### Issues");
|
|
93
|
+
lines.push(`Total issues: ${issues.length}`);
|
|
94
|
+
lines.push(formatIssuesList(issues));
|
|
95
|
+
return lines.join("\n");
|
|
96
|
+
}
|
|
97
|
+
export function formatA11yMarkdown(result) {
|
|
98
|
+
const lines = [`## Accessibility Audit`, `- Status: ${result.status || "unknown"}`];
|
|
99
|
+
if (result.url)
|
|
100
|
+
lines.push(`- URL: ${result.url}`);
|
|
101
|
+
if (result.mode)
|
|
102
|
+
lines.push(`- Mode: ${result.mode}`);
|
|
103
|
+
if (result.status !== "completed")
|
|
104
|
+
return lines.join("\n");
|
|
105
|
+
const a11yScore = getCategoryScore(result.scores, "accessibility");
|
|
106
|
+
if (a11yScore !== null)
|
|
107
|
+
lines.push(`- Accessibility score: ${boldColor(String(a11yScore), scoreColor(a11yScore))}`);
|
|
108
|
+
const issues = normalizeIssues(result.issues).filter((issue) => {
|
|
109
|
+
const category = (issue.category || "").toLowerCase();
|
|
110
|
+
return (category.includes("accessibility") ||
|
|
111
|
+
category.includes("a11y") ||
|
|
112
|
+
category.includes("wcag"));
|
|
113
|
+
});
|
|
114
|
+
lines.push("\n### Accessibility Issues");
|
|
115
|
+
lines.push(`Total accessibility issues: ${issues.length}`);
|
|
116
|
+
lines.push(formatIssuesList(issues));
|
|
117
|
+
return lines.join("\n");
|
|
118
|
+
}
|
|
119
|
+
export function formatCompareMarkdown(compare) {
|
|
120
|
+
const lines = ["## Audit Comparison"];
|
|
121
|
+
lines.push(`- URL A: ${compare.urlA}`);
|
|
122
|
+
lines.push(`- URL B: ${compare.urlB}`);
|
|
123
|
+
lines.push(`- Job A: ${compare.jobA}`);
|
|
124
|
+
lines.push(`- Job B: ${compare.jobB}`);
|
|
125
|
+
lines.push("\n### Overall Scores");
|
|
126
|
+
lines.push("| Site | Score | Delta |");
|
|
127
|
+
lines.push("| --- | --- | --- |");
|
|
128
|
+
lines.push(`| A | ${compare.overallA !== null ? boldColor(String(compare.overallA), scoreColor(compare.overallA)) : "n/a"} | ${compare.delta ?? "n/a"} |`);
|
|
129
|
+
lines.push(`| B | ${compare.overallB !== null ? boldColor(String(compare.overallB), scoreColor(compare.overallB)) : "n/a"} | ${compare.delta ?? "n/a"} |`);
|
|
130
|
+
lines.push("\n### Category Scores");
|
|
131
|
+
lines.push("| Category | A | B | Delta |");
|
|
132
|
+
lines.push("| --- | --- | --- | --- |");
|
|
133
|
+
for (const [key, values] of Object.entries(compare.categoryDeltas)) {
|
|
134
|
+
lines.push(`| ${key} | ${values.a ?? "n/a"} | ${values.b ?? "n/a"} | ${values.delta ?? "n/a"} |`);
|
|
135
|
+
}
|
|
136
|
+
lines.push("\n### Issue Counts");
|
|
137
|
+
lines.push(`- A: ${compare.issuesA}`);
|
|
138
|
+
lines.push(`- B: ${compare.issuesB}`);
|
|
139
|
+
return lines.join("\n");
|
|
140
|
+
}
|
|
141
|
+
export function formatExplainMarkdown(issue) {
|
|
142
|
+
const lines = ["## Issue Explanation"];
|
|
143
|
+
if (issue.id)
|
|
144
|
+
lines.push(`- ID: ${issue.id}`);
|
|
145
|
+
if (issue.severity)
|
|
146
|
+
lines.push(`- Severity: ${issue.severity}`);
|
|
147
|
+
if (issue.category)
|
|
148
|
+
lines.push(`- Category: ${issue.category}`);
|
|
149
|
+
if (issue.selector)
|
|
150
|
+
lines.push(`- Selector: ${issue.selector}`);
|
|
151
|
+
if (issue.description) {
|
|
152
|
+
lines.push("\n### Description");
|
|
153
|
+
lines.push(issue.description);
|
|
154
|
+
}
|
|
155
|
+
const recommendation = issue.recommendation || issue.recommended_fix;
|
|
156
|
+
if (recommendation) {
|
|
157
|
+
lines.push("\n### Recommendation");
|
|
158
|
+
lines.push(recommendation);
|
|
159
|
+
}
|
|
160
|
+
if (issue.wcag_reference) {
|
|
161
|
+
lines.push("\n### WCAG Reference");
|
|
162
|
+
lines.push(issue.wcag_reference);
|
|
163
|
+
}
|
|
164
|
+
return lines.join("\n");
|
|
165
|
+
}
|
|
166
|
+
export function formatPatchMarkdown(patch) {
|
|
167
|
+
if (!patch)
|
|
168
|
+
return "No patch generated.";
|
|
169
|
+
const lines = [
|
|
170
|
+
"## Patch Generated",
|
|
171
|
+
"",
|
|
172
|
+
`**Confidence:** ${patch.confidence.label} (${patch.confidence.percentage}%)`,
|
|
173
|
+
`**Classification:** ${patch.classification}`,
|
|
174
|
+
"",
|
|
175
|
+
"### Explanation",
|
|
176
|
+
patch.explanation,
|
|
177
|
+
"",
|
|
178
|
+
"### Search",
|
|
179
|
+
"```",
|
|
180
|
+
patch.search,
|
|
181
|
+
"```",
|
|
182
|
+
"",
|
|
183
|
+
"### Replace",
|
|
184
|
+
"```",
|
|
185
|
+
patch.replace,
|
|
186
|
+
"```",
|
|
187
|
+
];
|
|
188
|
+
return lines.join("\n");
|
|
189
|
+
}
|
|
190
|
+
export function formatVerifyMarkdown(result) {
|
|
191
|
+
if (!result.success || !result.verification) {
|
|
192
|
+
return `## Verification Failed\n\n${result.error?.message || "Unknown error"}`;
|
|
193
|
+
}
|
|
194
|
+
const v = result.verification;
|
|
195
|
+
const deltaSign = v.score_delta.delta >= 0 ? "+" : "";
|
|
196
|
+
const lines = [
|
|
197
|
+
"## Verification Result",
|
|
198
|
+
"",
|
|
199
|
+
`**Issue Fixed:** ${v.issue_fixed ? "Yes" : "No"}`,
|
|
200
|
+
`**Score Delta:** ${boldColor(String(v.score_delta.before), scoreColor(v.score_delta.before))} -> ${boldColor(String(v.score_delta.after), scoreColor(v.score_delta.after))} (${deltaSign}${v.score_delta.delta})`,
|
|
201
|
+
`**Component:** ${v.component_audited}`,
|
|
202
|
+
`**Duration:** ${v.duration_ms}ms`,
|
|
203
|
+
];
|
|
204
|
+
if (v.regressions.length > 0) {
|
|
205
|
+
lines.push("", "### Regressions");
|
|
206
|
+
for (const r of v.regressions) {
|
|
207
|
+
lines.push(`- [${r.impact.toUpperCase()}] ${r.rule_id}: ${r.description}`);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
lines.push("", "### Regressions", "None");
|
|
212
|
+
}
|
|
213
|
+
if (v.error) {
|
|
214
|
+
lines.push("", `**Warning:** ${v.error}`);
|
|
215
|
+
}
|
|
216
|
+
return lines.join("\n");
|
|
217
|
+
}
|
|
218
|
+
// Auto-fixable issue types (from lib/patch/classifier.ts)
|
|
219
|
+
const AUTO_FIXABLE_TYPES = new Set([
|
|
220
|
+
"label",
|
|
221
|
+
"input-missing-label",
|
|
222
|
+
"form-field-label",
|
|
223
|
+
"button-name",
|
|
224
|
+
"missing-button-name",
|
|
225
|
+
"link-name",
|
|
226
|
+
"html-has-lang",
|
|
227
|
+
"missing-lang-attribute",
|
|
228
|
+
"html-lang-valid",
|
|
229
|
+
"document-title",
|
|
230
|
+
"bypass",
|
|
231
|
+
"region",
|
|
232
|
+
"landmark-one-main",
|
|
233
|
+
"color-contrast",
|
|
234
|
+
"autocomplete-valid",
|
|
235
|
+
"aria-required-attr",
|
|
236
|
+
"aria-valid-attr",
|
|
237
|
+
"aria-valid-attr-value",
|
|
238
|
+
]);
|
|
239
|
+
export function isAutoFixable(issueType) {
|
|
240
|
+
const normalized = issueType.toLowerCase().replace(/^(axe|wcag)-/, "");
|
|
241
|
+
return AUTO_FIXABLE_TYPES.has(normalized);
|
|
242
|
+
}
|
|
243
|
+
export function formatBatchMarkdown(results) {
|
|
244
|
+
const lines = [
|
|
245
|
+
"## Batch Patch Results",
|
|
246
|
+
"",
|
|
247
|
+
`**Total Requested:** ${results.totalRequested}`,
|
|
248
|
+
`**Succeeded:** ${results.successes.length}`,
|
|
249
|
+
`**Failed:** ${results.failures.length}`,
|
|
250
|
+
`**Skipped:** ${results.totalSkipped}`,
|
|
251
|
+
];
|
|
252
|
+
if (results.successes.length > 0) {
|
|
253
|
+
lines.push("", "### Successful Patches");
|
|
254
|
+
for (const s of results.successes) {
|
|
255
|
+
const conf = s.patch?.confidence;
|
|
256
|
+
const cls = s.patch?.classification || "unknown";
|
|
257
|
+
const confStr = conf ? `${conf.label} (${conf.percentage}%)` : "n/a";
|
|
258
|
+
lines.push(`- ${s.issueId}: ${confStr} [${cls}]`);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
if (results.failures.length > 0) {
|
|
262
|
+
lines.push("", "### Failed");
|
|
263
|
+
for (const f of results.failures) {
|
|
264
|
+
const details = f.details ? ` - ${f.details}` : "";
|
|
265
|
+
lines.push(`- ${f.issueId}: ${f.reason}${details}`);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
return lines.join("\n");
|
|
269
|
+
}
|
|
270
|
+
export function printOutput(format, data, markdown) {
|
|
271
|
+
if (format === "json") {
|
|
272
|
+
process.stdout.write(JSON.stringify(data, null, 2) + "\n");
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
// Route through writeOutput so interactive mode can buffer the output
|
|
276
|
+
writeOutput(markdown || "");
|
|
277
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local page capture for localhost/private URL auditing.
|
|
3
|
+
*
|
|
4
|
+
* Fetches HTML content from a local URL using Node's native fetch,
|
|
5
|
+
* so the CLI can send pre-captured content to the cloud API for analysis.
|
|
6
|
+
*/
|
|
7
|
+
export interface CapturedPage {
|
|
8
|
+
html: string;
|
|
9
|
+
url: string;
|
|
10
|
+
statusCode: number;
|
|
11
|
+
headers: Record<string, string>;
|
|
12
|
+
fetchTimeMs: number;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Fetch a local/private URL and capture its rendered HTML.
|
|
16
|
+
*
|
|
17
|
+
* @param url - The local URL to capture (e.g., http://localhost:3000/)
|
|
18
|
+
* @param options - Optional configuration
|
|
19
|
+
* @returns Captured page content and metadata
|
|
20
|
+
* @throws Error with user-friendly message on connection failures
|
|
21
|
+
*/
|
|
22
|
+
export declare function captureLocalPage(url: string, options?: {
|
|
23
|
+
timeoutMs?: number;
|
|
24
|
+
}): Promise<CapturedPage>;
|
|
25
|
+
//# sourceMappingURL=local-capture.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local-capture.d.ts","sourceRoot":"","sources":["../../src/utils/local-capture.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;GAOG;AACH,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAC/B,OAAO,CAAC,YAAY,CAAC,CAmDvB"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local page capture for localhost/private URL auditing.
|
|
3
|
+
*
|
|
4
|
+
* Fetches HTML content from a local URL using Node's native fetch,
|
|
5
|
+
* so the CLI can send pre-captured content to the cloud API for analysis.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Fetch a local/private URL and capture its rendered HTML.
|
|
9
|
+
*
|
|
10
|
+
* @param url - The local URL to capture (e.g., http://localhost:3000/)
|
|
11
|
+
* @param options - Optional configuration
|
|
12
|
+
* @returns Captured page content and metadata
|
|
13
|
+
* @throws Error with user-friendly message on connection failures
|
|
14
|
+
*/
|
|
15
|
+
export async function captureLocalPage(url, options) {
|
|
16
|
+
const timeoutMs = options?.timeoutMs ?? 15_000;
|
|
17
|
+
const start = Date.now();
|
|
18
|
+
const controller = new AbortController();
|
|
19
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
20
|
+
try {
|
|
21
|
+
const response = await fetch(url, {
|
|
22
|
+
signal: controller.signal,
|
|
23
|
+
headers: {
|
|
24
|
+
"User-Agent": "VertaaUX-CLI (local-capture)",
|
|
25
|
+
Accept: "text/html,application/xhtml+xml,*/*",
|
|
26
|
+
},
|
|
27
|
+
redirect: "follow",
|
|
28
|
+
});
|
|
29
|
+
const html = await response.text();
|
|
30
|
+
const headers = {};
|
|
31
|
+
response.headers.forEach((value, key) => {
|
|
32
|
+
headers[key] = value;
|
|
33
|
+
});
|
|
34
|
+
return {
|
|
35
|
+
html,
|
|
36
|
+
url,
|
|
37
|
+
statusCode: response.status,
|
|
38
|
+
headers,
|
|
39
|
+
fetchTimeMs: Date.now() - start,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
if (err instanceof DOMException && err.name === "AbortError") {
|
|
44
|
+
throw new Error(`Timed out after ${timeoutMs}ms trying to reach ${url}. ` +
|
|
45
|
+
"Ensure your local server is running and responsive.");
|
|
46
|
+
}
|
|
47
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
48
|
+
if (message.includes("ECONNREFUSED")) {
|
|
49
|
+
throw new Error(`Connection refused at ${url}. ` +
|
|
50
|
+
"Ensure your local development server is running.");
|
|
51
|
+
}
|
|
52
|
+
throw new Error(`Failed to capture local page at ${url}: ${message}`);
|
|
53
|
+
}
|
|
54
|
+
finally {
|
|
55
|
+
clearTimeout(timer);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* URL classification for local vs. public URLs.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the SSRF guard logic from lib/ssrf.ts on the client side
|
|
5
|
+
* to determine routing strategy (cloud API vs local capture).
|
|
6
|
+
*/
|
|
7
|
+
export type UrlKind = "public" | "localhost" | "private";
|
|
8
|
+
/**
|
|
9
|
+
* Classify a URL as public, localhost, or private-network.
|
|
10
|
+
* Used to decide whether the CLI should capture the page locally.
|
|
11
|
+
*/
|
|
12
|
+
export declare function classifyUrl(urlString: string): UrlKind;
|
|
13
|
+
/**
|
|
14
|
+
* Check if a URL points to a local or private address
|
|
15
|
+
* (unreachable from the cloud API).
|
|
16
|
+
*/
|
|
17
|
+
export declare function isLocalUrl(urlString: string): boolean;
|
|
18
|
+
//# sourceMappingURL=url-classify.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"url-classify.d.ts","sourceRoot":"","sources":["../../src/utils/url-classify.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,MAAM,MAAM,OAAO,GAAG,QAAQ,GAAG,WAAW,GAAG,SAAS,CAAC;AAEzD;;;GAGG;AACH,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CA8BtD;AAgED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAGrD"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* URL classification for local vs. public URLs.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the SSRF guard logic from lib/ssrf.ts on the client side
|
|
5
|
+
* to determine routing strategy (cloud API vs local capture).
|
|
6
|
+
*/
|
|
7
|
+
import net from "net";
|
|
8
|
+
/**
|
|
9
|
+
* Classify a URL as public, localhost, or private-network.
|
|
10
|
+
* Used to decide whether the CLI should capture the page locally.
|
|
11
|
+
*/
|
|
12
|
+
export function classifyUrl(urlString) {
|
|
13
|
+
let parsed;
|
|
14
|
+
try {
|
|
15
|
+
parsed = new URL(urlString);
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return "public"; // Let the API decide for malformed URLs
|
|
19
|
+
}
|
|
20
|
+
const hostname = parsed.hostname.toLowerCase();
|
|
21
|
+
// Localhost variants (URL parser keeps brackets on IPv6)
|
|
22
|
+
if (hostname === "localhost" ||
|
|
23
|
+
hostname === "::1" ||
|
|
24
|
+
hostname === "[::1]" ||
|
|
25
|
+
hostname.endsWith(".localhost")) {
|
|
26
|
+
return "localhost";
|
|
27
|
+
}
|
|
28
|
+
// IP literal check — strip brackets from IPv6 literals
|
|
29
|
+
const ip = hostname.startsWith("[") && hostname.endsWith("]")
|
|
30
|
+
? hostname.slice(1, -1)
|
|
31
|
+
: hostname;
|
|
32
|
+
if (net.isIP(ip)) {
|
|
33
|
+
if (isPrivateIp(ip))
|
|
34
|
+
return "private";
|
|
35
|
+
return "public";
|
|
36
|
+
}
|
|
37
|
+
return "public";
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Check common private/reserved IPv4 ranges.
|
|
41
|
+
*/
|
|
42
|
+
function isPrivateIp(address) {
|
|
43
|
+
const version = net.isIP(address);
|
|
44
|
+
if (version === 6) {
|
|
45
|
+
const lower = address.toLowerCase();
|
|
46
|
+
// IPv4-mapped IPv6 — extract and check as IPv4
|
|
47
|
+
// Handles both dotted form (::ffff:127.0.0.1) and hex form (::ffff:7f00:1)
|
|
48
|
+
if (lower.startsWith("::ffff:")) {
|
|
49
|
+
const mapped = lower.slice(7); // strip "::ffff:"
|
|
50
|
+
if (net.isIPv4(mapped)) {
|
|
51
|
+
return isPrivateIp(mapped);
|
|
52
|
+
}
|
|
53
|
+
// Hex form: two 16-bit groups (e.g. "7f00:1" = 127.0.0.1)
|
|
54
|
+
const hexParts = mapped.split(":");
|
|
55
|
+
if (hexParts.length === 2) {
|
|
56
|
+
const hi = parseInt(hexParts[0], 16);
|
|
57
|
+
const lo = parseInt(hexParts[1], 16);
|
|
58
|
+
if (!isNaN(hi) && !isNaN(lo) && hi <= 0xffff && lo <= 0xffff) {
|
|
59
|
+
const ipv4 = `${(hi >> 8) & 0xff}.${hi & 0xff}.${(lo >> 8) & 0xff}.${lo & 0xff}`;
|
|
60
|
+
return isPrivateIp(ipv4);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return (lower === "::" ||
|
|
65
|
+
lower === "::1" ||
|
|
66
|
+
lower.startsWith("fc") ||
|
|
67
|
+
lower.startsWith("fd") ||
|
|
68
|
+
lower.startsWith("fe8") ||
|
|
69
|
+
lower.startsWith("fe9") ||
|
|
70
|
+
lower.startsWith("fea") ||
|
|
71
|
+
lower.startsWith("feb"));
|
|
72
|
+
}
|
|
73
|
+
if (version !== 4)
|
|
74
|
+
return false;
|
|
75
|
+
const parts = address.split(".").map(Number);
|
|
76
|
+
if (parts.length !== 4 || parts.some((n) => !Number.isFinite(n)))
|
|
77
|
+
return false;
|
|
78
|
+
const [a, b] = parts;
|
|
79
|
+
// 127.0.0.0/8 — loopback
|
|
80
|
+
if (a === 127)
|
|
81
|
+
return true;
|
|
82
|
+
// 10.0.0.0/8
|
|
83
|
+
if (a === 10)
|
|
84
|
+
return true;
|
|
85
|
+
// 172.16.0.0/12
|
|
86
|
+
if (a === 172 && b >= 16 && b <= 31)
|
|
87
|
+
return true;
|
|
88
|
+
// 192.168.0.0/16
|
|
89
|
+
if (a === 192 && b === 168)
|
|
90
|
+
return true;
|
|
91
|
+
// 169.254.0.0/16 — link-local
|
|
92
|
+
if (a === 169 && b === 254)
|
|
93
|
+
return true;
|
|
94
|
+
// 0.0.0.0/8
|
|
95
|
+
if (a === 0)
|
|
96
|
+
return true;
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Check if a URL points to a local or private address
|
|
101
|
+
* (unreachable from the cloud API).
|
|
102
|
+
*/
|
|
103
|
+
export function isLocalUrl(urlString) {
|
|
104
|
+
const kind = classifyUrl(urlString);
|
|
105
|
+
return kind === "localhost" || kind === "private";
|
|
106
|
+
}
|