eaa-kit 0.2.1 → 0.4.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/README.md +51 -4
- package/dist/astro/index.d.ts +7 -34
- package/dist/astro/index.js +6 -26
- package/dist/audit/runners/worker.js +1 -1
- package/dist/audit-CPoZMXGM.js +779 -0
- package/dist/audit-CpXH2Mk8.js +2 -0
- package/dist/{baseline-CV_3lbER.js → baseline-22Y1NWxM.js} +1 -1
- package/dist/{baseline-CgBmzFTr.js → baseline-DB9CZGnV.js} +17 -27
- package/dist/cli/index.js +336 -142
- package/dist/collect-CFM8gEVv.js +134 -0
- package/dist/command-D8l_oYbV.js +77 -0
- package/dist/component-C3GL1Mnu.js +146 -0
- package/dist/component-DKd3EHOg.js +2 -0
- package/dist/coverage-B4IBKMO8.js +521 -0
- package/dist/{crawl-CtJbMNNb.js → crawl-BiI1Lau_.js} +13 -23
- package/dist/eleventy/index.d.ts +28 -0
- package/dist/eleventy/index.js +19 -0
- package/dist/frameworks-B4ClIJgE.js +314 -0
- package/dist/frameworks-etFg_O8K.js +2 -0
- package/dist/fs-BmPtmFke.js +40 -0
- package/dist/html-BGTO3ypW.js +543 -0
- package/dist/{impact-DvgBjupx.js → impact-DZt2oBCP.js} +15 -1
- package/dist/index.js +2 -2
- package/dist/{init-DRKIdpK1.js → init-DIWDE35F.js} +13 -10
- package/dist/jsdom-4IMzv0eE.js +3 -0
- package/dist/jsdom-DCpGSLfW.js +81 -0
- package/dist/{json-C9xS1PNC.js → json-DjEvy1nX.js} +19 -22
- package/dist/json-QQuFIw1W.js +2 -0
- package/dist/{load-sCkKsvGQ.js → load-5wRGLvub.js} +3 -9
- package/dist/nuxt/index.d.ts +35 -0
- package/dist/nuxt/index.js +23 -0
- package/dist/{playwright-DSRnXmcd.js → playwright-BWniOain.js} +70 -23
- package/dist/{pool-DixLeu8L.js → pool-BMevaLWD.js} +4 -12
- package/dist/{project-CufCqIE2.js → project-CzOnkLH6.js} +14 -21
- package/dist/project-MFrXcw1M.js +2 -0
- package/dist/remediation-Dtowi2EC.js +321 -0
- package/dist/{render-BO0nVrrZ.js → render-DrvXRCEn.js} +8 -16
- package/dist/{result-2aZPfM8w.js → result-DoamKFsp.js} +115 -1
- package/dist/routes-CmdRUuOs.js +265 -0
- package/dist/run-BMASMmwO.d.ts +45 -0
- package/dist/run-DB34BSOZ.js +53 -0
- package/dist/{sarif-eSCuI0eX.js → sarif-SR3_lLYd.js} +22 -36
- package/dist/{schema-CMZ8ItGk.js → schema-is6CGX2D.js} +4 -1
- package/dist/text-CKKpzkYM.js +43 -0
- package/dist/vite/index.d.ts +39 -0
- package/dist/vite/index.js +40 -0
- package/dist/webpack/index.d.ts +33 -0
- package/dist/webpack/index.js +20 -0
- package/package.json +36 -9
- package/dist/audit-B2dIKpJ5.js +0 -2
- package/dist/audit-DXpKkXsC.js +0 -950
- package/dist/escape-Dm1o_RAk.js +0 -21
- package/dist/html-BLEuzep6.js +0 -337
- package/dist/jsdom-BEu6Ra_2.js +0 -163
- package/dist/jsdom-C6dIyaxN.js +0 -3
- package/dist/routes-BxbSZKXC.js +0 -123
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as readBaseline, r as applyBaseline, t as BaselineError } from "./baseline-
|
|
1
|
+
import { a as readBaseline, r as applyBaseline, t as BaselineError } from "./baseline-DB9CZGnV.js";
|
|
2
2
|
export { BaselineError, applyBaseline, readBaseline };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { a as isoDate$1, c as number, l as object, m as string, p as safeParse, s as nullable, t as array, u as optional, v as withDefault } from "./schema-is6CGX2D.js";
|
|
2
2
|
import { t as elementFingerprint } from "./fingerprint-DRoneAjj.js";
|
|
3
|
+
import { a as findingElements } from "./result-DoamKFsp.js";
|
|
3
4
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
4
5
|
import path from "node:path";
|
|
5
6
|
/** Default filename, used by the CLI when no path is given. */
|
|
@@ -37,12 +38,10 @@ var BaselineError = class extends Error {
|
|
|
37
38
|
*/
|
|
38
39
|
function applyBaseline(audits, baseline, options = {}) {
|
|
39
40
|
const today = isoDate(options.today ?? /* @__PURE__ */ new Date());
|
|
40
|
-
const expired =
|
|
41
|
+
const expired = [];
|
|
41
42
|
const live = /* @__PURE__ */ new Map();
|
|
42
|
-
for (const entry of baseline.entries)
|
|
43
|
-
|
|
44
|
-
live.set(key(entry.page, entry.ruleId, entry.fingerprint), entry);
|
|
45
|
-
}
|
|
43
|
+
for (const entry of baseline.entries) if (entry.expiresOn !== void 0 && entry.expiresOn < today) expired.push(entry);
|
|
44
|
+
else live.set(key(entry.page, entry.ruleId, entry.fingerprint), entry);
|
|
46
45
|
const matched = /* @__PURE__ */ new Set();
|
|
47
46
|
let accepted = 0;
|
|
48
47
|
const next = audits.map((audit) => {
|
|
@@ -90,7 +89,7 @@ function applyBaseline(audits, baseline, options = {}) {
|
|
|
90
89
|
return {
|
|
91
90
|
audits: next,
|
|
92
91
|
stale: [...live.entries()].filter(([id, entry]) => !matched.has(id) && audited.has(entry.page)).map(([, entry]) => entry).sort(byEntry),
|
|
93
|
-
expired:
|
|
92
|
+
expired: expired.sort(byEntry),
|
|
94
93
|
accepted
|
|
95
94
|
};
|
|
96
95
|
}
|
|
@@ -98,26 +97,17 @@ function applyBaseline(audits, baseline, options = {}) {
|
|
|
98
97
|
function buildBaseline(audits, options = {}) {
|
|
99
98
|
const acceptedOn = isoDate(options.today ?? /* @__PURE__ */ new Date());
|
|
100
99
|
const entries = [];
|
|
101
|
-
for (const audit of audits) for (const finding of audit.violations) {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
fingerprint: elementFingerprint(finding.ruleId, node.selector, node.html),
|
|
113
|
-
selector: node.selector,
|
|
114
|
-
help: finding.help,
|
|
115
|
-
impact: finding.impact ?? null,
|
|
116
|
-
acceptedOn,
|
|
117
|
-
...options.expiresOn ? { expiresOn: options.expiresOn } : {},
|
|
118
|
-
...options.note ? { note: options.note } : {}
|
|
119
|
-
});
|
|
120
|
-
}
|
|
100
|
+
for (const audit of audits) for (const finding of audit.violations) for (const node of findingElements(finding)) entries.push({
|
|
101
|
+
page: audit.relativePath,
|
|
102
|
+
ruleId: finding.ruleId,
|
|
103
|
+
fingerprint: elementFingerprint(finding.ruleId, node.selector, node.html),
|
|
104
|
+
selector: node.selector,
|
|
105
|
+
help: finding.help,
|
|
106
|
+
impact: finding.impact ?? null,
|
|
107
|
+
acceptedOn,
|
|
108
|
+
...options.expiresOn ? { expiresOn: options.expiresOn } : {},
|
|
109
|
+
...options.note ? { note: options.note } : {}
|
|
110
|
+
});
|
|
121
111
|
return {
|
|
122
112
|
schemaVersion: 1,
|
|
123
113
|
createdOn: acceptedOn,
|
package/dist/cli/index.js
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { l as
|
|
3
|
-
import {
|
|
4
|
-
import { i as
|
|
2
|
+
import { c as number, f as record, l as object, m as string, n as boolean, p as safeParse, s as nullable, t as array, u as optional, v as withDefault } from "../schema-is6CGX2D.js";
|
|
3
|
+
import { l as STATEMENT_LOCALES, o as COUNTRIES, r as loadConfig, s as ConfigError } from "../load-5wRGLvub.js";
|
|
4
|
+
import { i as readAuditReport, o as StatementError, t as renderStatement } from "../render-DrvXRCEn.js";
|
|
5
|
+
import { a as impactRank, n as IMPACT_LEVELS, o as isImpactLevel, s as meetsThreshold, t as DEFAULT_FAIL_ON } from "../impact-DZt2oBCP.js";
|
|
6
|
+
import { n as count, t as collapse } from "../text-CKKpzkYM.js";
|
|
5
7
|
import { t as TOOL_VERSION } from "../version-B3v4rNoG.js";
|
|
6
|
-
import {
|
|
7
|
-
import { i as
|
|
8
|
-
import {
|
|
8
|
+
import { t as elementFingerprint } from "../fingerprint-DRoneAjj.js";
|
|
9
|
+
import { i as buildBaseline, n as DEFAULT_BASELINE_FILE, s as writeBaseline, t as BaselineError } from "../baseline-DB9CZGnV.js";
|
|
10
|
+
import { n as runAuditCommand, r as resolvePages, t as OUTPUT_FORMATS } from "../audit-CPoZMXGM.js";
|
|
11
|
+
import { a as runEngine, i as note, n as emitDocument, o as warn, r as fail, t as advise } from "../command-D8l_oYbV.js";
|
|
12
|
+
import { readFile } from "node:fs/promises";
|
|
9
13
|
import path from "node:path";
|
|
10
14
|
import { Command, InvalidArgumentError } from "commander";
|
|
11
15
|
import pc from "picocolors";
|
|
@@ -33,38 +37,23 @@ async function runBaselineCommand(dir, options = {}) {
|
|
|
33
37
|
exitCode: 2
|
|
34
38
|
};
|
|
35
39
|
const { pages, origin, label } = resolved;
|
|
36
|
-
|
|
37
|
-
const
|
|
38
|
-
const
|
|
40
|
+
note(`Auditing ${count(pages.length, "page")} in ${label}…`);
|
|
41
|
+
const baseUrl = options.baseUrl ?? origin;
|
|
42
|
+
const audits = await runEngine(pages, {
|
|
39
43
|
cwd,
|
|
40
|
-
...
|
|
41
|
-
...options.timeoutMs === void 0 ? {} : { timeoutMs: options.timeoutMs }
|
|
44
|
+
...baseUrl === void 0 ? {} : { baseUrl },
|
|
45
|
+
...options.timeoutMs === void 0 ? {} : { timeoutMs: options.timeoutMs },
|
|
46
|
+
...options.browser ? { browser: true } : {},
|
|
47
|
+
...options.concurrency === void 0 ? {} : { concurrency: options.concurrency },
|
|
48
|
+
...options.url === void 0 ? { directory: path.resolve(cwd, dir) } : {}
|
|
49
|
+
});
|
|
50
|
+
if (!audits) return {
|
|
51
|
+
entries: 0,
|
|
52
|
+
exitCode: 2
|
|
42
53
|
};
|
|
43
|
-
let audits;
|
|
44
|
-
if (options.browser) {
|
|
45
|
-
const { BrowserUnavailableError, runBrowserAudit } = await import("../playwright-DSRnXmcd.js");
|
|
46
|
-
try {
|
|
47
|
-
audits = await runBrowserAudit(options.url === void 0 ? path.resolve(cwd, dir) : void 0, pages, runnerOptions);
|
|
48
|
-
} catch (cause) {
|
|
49
|
-
if (cause instanceof BrowserUnavailableError) {
|
|
50
|
-
process.stderr.write(`${pc.red("error")} ${cause.message}\n`);
|
|
51
|
-
return {
|
|
52
|
-
entries: 0,
|
|
53
|
-
exitCode: 2
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
throw cause;
|
|
57
|
-
}
|
|
58
|
-
} else {
|
|
59
|
-
const { runPooledAudit } = await import("../pool-DixLeu8L.js");
|
|
60
|
-
audits = await runPooledAudit(pages, {
|
|
61
|
-
...runnerOptions,
|
|
62
|
-
...options.concurrency === void 0 ? {} : { concurrency: options.concurrency }
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
54
|
const unaudited = audits.filter((audit) => audit.error);
|
|
66
55
|
if (unaudited.length > 0) {
|
|
67
|
-
|
|
56
|
+
fail(`${unaudited.length} of ${audits.length} pages could not be audited, so no baseline was written`);
|
|
68
57
|
return {
|
|
69
58
|
entries: 0,
|
|
70
59
|
exitCode: 2
|
|
@@ -79,7 +68,7 @@ async function runBaselineCommand(dir, options = {}) {
|
|
|
79
68
|
await writeBaseline(target, baseline, cwd);
|
|
80
69
|
} catch (cause) {
|
|
81
70
|
if (cause instanceof BaselineError) {
|
|
82
|
-
|
|
71
|
+
fail(cause.message);
|
|
83
72
|
return {
|
|
84
73
|
entries: 0,
|
|
85
74
|
exitCode: 2
|
|
@@ -87,21 +76,267 @@ async function runBaselineCommand(dir, options = {}) {
|
|
|
87
76
|
}
|
|
88
77
|
throw cause;
|
|
89
78
|
}
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
if (
|
|
79
|
+
const entries = baseline.entries.length;
|
|
80
|
+
note(`Wrote ${entries} ${entries === 1 ? "entry" : "entries"} to ${target}`);
|
|
81
|
+
if (entries > 0) advise("These are barriers, not exceptions. Commit the file, then work the list down.");
|
|
93
82
|
return {
|
|
94
|
-
entries
|
|
83
|
+
entries,
|
|
95
84
|
exitCode: 0
|
|
96
85
|
};
|
|
97
86
|
}
|
|
87
|
+
var DiffError = class extends Error {
|
|
88
|
+
name = "DiffError";
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* Only the fields a comparison reads. `completeness` is optional: a report
|
|
92
|
+
* written before it existed says nothing about coverage, which is not the same
|
|
93
|
+
* as saying the run was complete, and is handled as the unknown it is.
|
|
94
|
+
*/
|
|
95
|
+
const reportSchema = object({
|
|
96
|
+
schemaVersion: number(),
|
|
97
|
+
generatedAt: string(),
|
|
98
|
+
target: withDefault(object({ source: withDefault(string(), () => "") }), () => ({ source: "" })),
|
|
99
|
+
completeness: optional(object({
|
|
100
|
+
complete: boolean(),
|
|
101
|
+
audited: withDefault(number(), () => 0)
|
|
102
|
+
})),
|
|
103
|
+
rules: withDefault(record(object({ help: withDefault(string(), () => "") })), () => ({})),
|
|
104
|
+
pages: withDefault(array(object({
|
|
105
|
+
path: string(),
|
|
106
|
+
error: withDefault(nullable(string()), () => null),
|
|
107
|
+
violations: withDefault(array(object({
|
|
108
|
+
ruleId: string(),
|
|
109
|
+
impact: withDefault(nullable(string()), () => null),
|
|
110
|
+
nodes: withDefault(array(object({
|
|
111
|
+
html: withDefault(string(), () => ""),
|
|
112
|
+
target: withDefault(array(string()), () => [])
|
|
113
|
+
})), () => [])
|
|
114
|
+
})), () => [])
|
|
115
|
+
})), () => [])
|
|
116
|
+
});
|
|
117
|
+
/**
|
|
118
|
+
* The elements a violation points at.
|
|
119
|
+
*
|
|
120
|
+
* Mirrors `findingElements` for the JSON shape, including its one subtlety: a
|
|
121
|
+
* rule can fail with no element attached, and it still has an identity. Getting
|
|
122
|
+
* this wrong here would make document-level failures invisible to a diff while
|
|
123
|
+
* remaining visible to the baseline.
|
|
124
|
+
*/
|
|
125
|
+
function elementsOf(violation) {
|
|
126
|
+
if (violation.nodes.length === 0) return [{
|
|
127
|
+
selector: "",
|
|
128
|
+
html: ""
|
|
129
|
+
}];
|
|
130
|
+
return violation.nodes.map((node) => ({
|
|
131
|
+
selector: node.target.join(" "),
|
|
132
|
+
html: node.html
|
|
133
|
+
}));
|
|
134
|
+
}
|
|
135
|
+
/** Every violating element in a report, keyed by page and element identity. */
|
|
136
|
+
function entriesOf(report) {
|
|
137
|
+
const entries = /* @__PURE__ */ new Map();
|
|
138
|
+
for (const page of report.pages) for (const violation of page.violations) {
|
|
139
|
+
const impact = violation.impact !== null && isImpactLevel(violation.impact) ? violation.impact : null;
|
|
140
|
+
for (const { selector, html } of elementsOf(violation)) {
|
|
141
|
+
const fingerprint = elementFingerprint(violation.ruleId, selector, html);
|
|
142
|
+
const key = `${page.path}\n${fingerprint}`;
|
|
143
|
+
if (entries.has(key)) continue;
|
|
144
|
+
entries.set(key, {
|
|
145
|
+
page: page.path,
|
|
146
|
+
ruleId: violation.ruleId,
|
|
147
|
+
fingerprint,
|
|
148
|
+
impact,
|
|
149
|
+
help: report.rules[violation.ruleId]?.help ?? violation.ruleId,
|
|
150
|
+
selector,
|
|
151
|
+
html
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return entries;
|
|
156
|
+
}
|
|
157
|
+
/** Pages a run reached a verdict on. A page that errored is not one of them. */
|
|
158
|
+
function auditedPages(report) {
|
|
159
|
+
return new Set(report.pages.filter((page) => page.error === null).map((page) => page.path));
|
|
160
|
+
}
|
|
161
|
+
function sideOf(report) {
|
|
162
|
+
return {
|
|
163
|
+
source: report.target.source,
|
|
164
|
+
generatedAt: report.generatedAt,
|
|
165
|
+
audited: report.completeness?.audited ?? auditedPages(report).size,
|
|
166
|
+
complete: report.completeness?.complete
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
/** Worst first, then by page, so two runs of one comparison agree. */
|
|
170
|
+
function bySeverityThenPage(a, b) {
|
|
171
|
+
return impactRank(a.impact) - impactRank(b.impact) || a.page.localeCompare(b.page) || a.ruleId.localeCompare(b.ruleId) || a.selector.localeCompare(b.selector);
|
|
172
|
+
}
|
|
173
|
+
/** Compare two parsed reports, earlier first. */
|
|
174
|
+
function diffReports(base, head) {
|
|
175
|
+
const before = entriesOf(base);
|
|
176
|
+
const after = entriesOf(head);
|
|
177
|
+
const measuredByHead = auditedPages(head);
|
|
178
|
+
const added = [];
|
|
179
|
+
const fixed = [];
|
|
180
|
+
const unchanged = [];
|
|
181
|
+
const unmeasured = [];
|
|
182
|
+
for (const [key, entry] of after) if (before.has(key)) unchanged.push(entry);
|
|
183
|
+
else added.push(entry);
|
|
184
|
+
for (const [key, entry] of before) {
|
|
185
|
+
if (after.has(key)) continue;
|
|
186
|
+
if (measuredByHead.has(entry.page)) fixed.push(entry);
|
|
187
|
+
else unmeasured.push(entry);
|
|
188
|
+
}
|
|
189
|
+
return {
|
|
190
|
+
base: sideOf(base),
|
|
191
|
+
head: sideOf(head),
|
|
192
|
+
added: added.sort(bySeverityThenPage),
|
|
193
|
+
fixed: fixed.sort(bySeverityThenPage),
|
|
194
|
+
unchanged: unchanged.sort(bySeverityThenPage),
|
|
195
|
+
unmeasured: unmeasured.sort(bySeverityThenPage)
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Read a JSON report from disk.
|
|
200
|
+
*
|
|
201
|
+
* Refuses a schema version it was not written against rather than reading it
|
|
202
|
+
* optimistically: a field that changed meaning is exactly what the version
|
|
203
|
+
* number exists to signal, and a diff built on a misread field would be wrong
|
|
204
|
+
* quietly.
|
|
205
|
+
*/
|
|
206
|
+
async function readReport(file, cwd = process.cwd()) {
|
|
207
|
+
const target = path.resolve(cwd, file);
|
|
208
|
+
let raw;
|
|
209
|
+
try {
|
|
210
|
+
raw = await readFile(target, "utf8");
|
|
211
|
+
} catch {
|
|
212
|
+
throw new DiffError(`Report not found: ${file}`);
|
|
213
|
+
}
|
|
214
|
+
let parsed;
|
|
215
|
+
try {
|
|
216
|
+
parsed = JSON.parse(raw);
|
|
217
|
+
} catch (cause) {
|
|
218
|
+
throw new DiffError(`${file} is not valid JSON (${cause.message})`);
|
|
219
|
+
}
|
|
220
|
+
const result = safeParse(reportSchema, parsed);
|
|
221
|
+
if (!result.success) {
|
|
222
|
+
const first = result.error.issues[0];
|
|
223
|
+
throw new DiffError(`${file} is not an eaa-kit JSON report${first === void 0 || first.path.length === 0 ? "" : ` at ${first.path.join(".")}`}: ${first?.message ?? "unrecognised shape"}`);
|
|
224
|
+
}
|
|
225
|
+
if (result.data.schemaVersion !== 1) throw new DiffError(`${file} is schemaVersion ${result.data.schemaVersion}; this version of eaa-kit reads 1`);
|
|
226
|
+
return result.data;
|
|
227
|
+
}
|
|
228
|
+
//#endregion
|
|
229
|
+
//#region src/cli/diff.ts
|
|
230
|
+
/**
|
|
231
|
+
* `eaa-kit diff <before.json> <after.json>`.
|
|
232
|
+
*
|
|
233
|
+
* What a change did to a site's accessibility, which is the question a review
|
|
234
|
+
* actually asks and which neither report answers on its own. Running the
|
|
235
|
+
* auditor on a branch prints everything wrong with the site; almost all of it
|
|
236
|
+
* was already there, and the two or three findings somebody introduced are
|
|
237
|
+
* somewhere in the middle of it.
|
|
238
|
+
*
|
|
239
|
+
* Deliberately stateless. A baseline is a file somebody commits and maintains,
|
|
240
|
+
* and it answers "what have we agreed to live with". This answers "what did
|
|
241
|
+
* this change do", needs no file and no decision, and can be run over any two
|
|
242
|
+
* reports after the fact.
|
|
243
|
+
*/
|
|
244
|
+
const DIFF_FORMATS = ["console", "json"];
|
|
245
|
+
/** Longest element markup shown before it is truncated. */
|
|
246
|
+
const MAX_SNIPPET = 100;
|
|
247
|
+
async function runDiffCommand(base, head, options = {}) {
|
|
248
|
+
const cwd = options.cwd ?? process.cwd();
|
|
249
|
+
let diff;
|
|
250
|
+
try {
|
|
251
|
+
diff = diffReports(await readReport(base, cwd), await readReport(head, cwd));
|
|
252
|
+
} catch (cause) {
|
|
253
|
+
if (cause instanceof DiffError) {
|
|
254
|
+
fail(cause.message);
|
|
255
|
+
return { exitCode: 2 };
|
|
256
|
+
}
|
|
257
|
+
throw cause;
|
|
258
|
+
}
|
|
259
|
+
const failOn = options.failOn ?? "serious";
|
|
260
|
+
const failing = diff.added.filter((entry) => meetsThreshold(entry.impact, failOn));
|
|
261
|
+
const body = (options.format ?? "console") === "json" ? `${JSON.stringify(toJson(diff, failOn, failing.length), null, 2)}\n` : `${formatDiff(diff, failOn, options.output === void 0)}\n`;
|
|
262
|
+
await emitDocument(body, options.output, cwd);
|
|
263
|
+
if (options.output !== void 0) note(`Diff written to ${options.output}`);
|
|
264
|
+
if (diff.unmeasured.length > 0) warn(`${count(diff.unmeasured.length, "violation")} could not be compared: the later run did not audit ${diff.unmeasured.length === 1 ? "its page" : "their pages"}`);
|
|
265
|
+
return {
|
|
266
|
+
diff,
|
|
267
|
+
exitCode: failing.length > 0 ? 1 : 0
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* The machine-readable form.
|
|
272
|
+
*
|
|
273
|
+
* `unchanged` is counted and not listed: it is the whole of the pre-existing
|
|
274
|
+
* report, it is already available in the report itself, and repeating it here
|
|
275
|
+
* would make the common case — a diff with three entries — a document nobody
|
|
276
|
+
* can read.
|
|
277
|
+
*/
|
|
278
|
+
function toJson(diff, failOn, failing) {
|
|
279
|
+
return {
|
|
280
|
+
base: diff.base,
|
|
281
|
+
head: diff.head,
|
|
282
|
+
summary: {
|
|
283
|
+
added: diff.added.length,
|
|
284
|
+
fixed: diff.fixed.length,
|
|
285
|
+
unchanged: diff.unchanged.length,
|
|
286
|
+
unmeasured: diff.unmeasured.length,
|
|
287
|
+
failOn,
|
|
288
|
+
failing
|
|
289
|
+
},
|
|
290
|
+
added: diff.added,
|
|
291
|
+
fixed: diff.fixed,
|
|
292
|
+
unmeasured: diff.unmeasured
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
function formatDiff(diff, failOn, colour = true) {
|
|
296
|
+
const c = pc.createColors(colour);
|
|
297
|
+
const lines = [""];
|
|
298
|
+
lines.push(c.bold("eaa-kit diff"));
|
|
299
|
+
lines.push(c.dim(` before ${describe(diff.base)}`));
|
|
300
|
+
lines.push(c.dim(` after ${describe(diff.head)}`));
|
|
301
|
+
lines.push("");
|
|
302
|
+
if (diff.added.length === 0 && diff.fixed.length === 0) lines.push(c.green("No change in violations."));
|
|
303
|
+
if (diff.added.length > 0) {
|
|
304
|
+
lines.push(c.red(c.bold(`New (${diff.added.length})`)));
|
|
305
|
+
lines.push(...diff.added.map((entry) => entryLine(entry, c)));
|
|
306
|
+
lines.push("");
|
|
307
|
+
}
|
|
308
|
+
if (diff.fixed.length > 0) {
|
|
309
|
+
lines.push(c.green(c.bold(`Fixed (${diff.fixed.length})`)));
|
|
310
|
+
lines.push(...diff.fixed.map((entry) => entryLine(entry, c)));
|
|
311
|
+
lines.push("");
|
|
312
|
+
}
|
|
313
|
+
if (diff.unmeasured.length > 0) {
|
|
314
|
+
lines.push(c.yellow(c.bold(`Not compared (${diff.unmeasured.length})`)));
|
|
315
|
+
lines.push(c.dim(" These failed before, and the later run did not audit the page they were on."));
|
|
316
|
+
lines.push(...diff.unmeasured.map((entry) => entryLine(entry, c)));
|
|
317
|
+
lines.push("");
|
|
318
|
+
}
|
|
319
|
+
const failing = diff.added.filter((entry) => meetsThreshold(entry.impact, failOn)).length;
|
|
320
|
+
lines.push(c.bold("Summary"));
|
|
321
|
+
lines.push(` ${diff.added.length} new · ${diff.fixed.length} fixed · ${diff.unchanged.length} unchanged`);
|
|
322
|
+
lines.push(failing > 0 ? c.red(` ${failing} new at or above ${failOn} (--fail-on ${failOn})`) : c.green(` no new violations at or above ${failOn} (--fail-on ${failOn})`));
|
|
323
|
+
return lines.join("\n");
|
|
324
|
+
}
|
|
325
|
+
function describe(side) {
|
|
326
|
+
const source = side.source === "" ? "unknown source" : side.source;
|
|
327
|
+
const coverage = side.complete === void 0 ? "coverage not recorded" : side.complete ? "complete" : "incomplete";
|
|
328
|
+
return `${source} · ${count(side.audited, "page")} · ${coverage} · ${side.generatedAt}`;
|
|
329
|
+
}
|
|
330
|
+
function entryLine(entry, c) {
|
|
331
|
+
const where = entry.selector === "" ? entry.page : `${entry.page} ${c.dim(entry.selector)}`;
|
|
332
|
+
const impact = entry.impact ?? "unclassified";
|
|
333
|
+
const snippet = entry.html === "" ? "" : `\n ${c.dim(collapse(entry.html, MAX_SNIPPET))}`;
|
|
334
|
+
return ` ${c.bold(entry.ruleId)} ${c.dim(`(${impact})`)} — ${entry.help}\n ${where}${snippet}`;
|
|
335
|
+
}
|
|
98
336
|
//#endregion
|
|
99
337
|
//#region src/cli/statement.ts
|
|
100
338
|
/** Markdown for a content directory, HTML for dropping straight onto a site. */
|
|
101
339
|
const STATEMENT_FORMATS = ["markdown", "html"];
|
|
102
|
-
function isStatementFormat(value) {
|
|
103
|
-
return STATEMENT_FORMATS.includes(value);
|
|
104
|
-
}
|
|
105
340
|
/**
|
|
106
341
|
* `eaa-kit statement`.
|
|
107
342
|
*
|
|
@@ -122,19 +357,16 @@ async function runStatementCommand(options = {}) {
|
|
|
122
357
|
...options.country ? { country: options.country } : {},
|
|
123
358
|
...audit ? { audit } : {}
|
|
124
359
|
});
|
|
125
|
-
|
|
360
|
+
note(`Statement for ${config.site.url} from ${path.basename(configPath)} (${statement.template}, ${format})`);
|
|
126
361
|
if (audit) {
|
|
127
|
-
|
|
128
|
-
|
|
362
|
+
const from = path.basename(options.audit ?? "");
|
|
363
|
+
note(`${count(audit.findings.length, "barrier")} taken from ${from}`);
|
|
364
|
+
if (audit.findings.length > 0) advise("Rewrite those descriptions in your own words before publishing.");
|
|
129
365
|
}
|
|
130
366
|
const document = format === "html" ? statement.html : statement.markdown;
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
await writeFile(target, document, "utf8");
|
|
135
|
-
process.stderr.write(pc.dim(`Written to ${options.output}\n`));
|
|
136
|
-
} else process.stdout.write(document);
|
|
137
|
-
process.stderr.write(pc.yellow("Review before publishing. This is a draft, not legal advice.\n"));
|
|
367
|
+
await emitDocument(document, options.output, options.cwd ?? process.cwd());
|
|
368
|
+
if (options.output !== void 0) note(`Written to ${options.output}`);
|
|
369
|
+
advise("Review before publishing. This is a draft, not legal advice.");
|
|
138
370
|
return {
|
|
139
371
|
document,
|
|
140
372
|
format,
|
|
@@ -142,8 +374,8 @@ async function runStatementCommand(options = {}) {
|
|
|
142
374
|
};
|
|
143
375
|
} catch (cause) {
|
|
144
376
|
if (cause instanceof ConfigError || cause instanceof StatementError) {
|
|
145
|
-
|
|
146
|
-
if (cause instanceof ConfigError) for (const issue of cause.issues)
|
|
377
|
+
fail(cause.message);
|
|
378
|
+
if (cause instanceof ConfigError) for (const issue of cause.issues) note(` ${issue}`);
|
|
147
379
|
return {
|
|
148
380
|
document: "",
|
|
149
381
|
format,
|
|
@@ -163,109 +395,71 @@ function formatFor(output) {
|
|
|
163
395
|
}
|
|
164
396
|
//#endregion
|
|
165
397
|
//#region src/cli/index.ts
|
|
398
|
+
/**
|
|
399
|
+
* A parser for a fixed set of words, listing them when the answer is not one.
|
|
400
|
+
*
|
|
401
|
+
* `normalise` is for `--country de`, where the accepted spelling differs from
|
|
402
|
+
* what somebody types.
|
|
403
|
+
*/
|
|
404
|
+
function oneOf(values, normalise = (value) => value) {
|
|
405
|
+
return (value) => {
|
|
406
|
+
const candidate = normalise(value);
|
|
407
|
+
if (!values.includes(candidate)) throw new InvalidArgumentError(`expected one of ${values.join(", ")}`);
|
|
408
|
+
return candidate;
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
/** A whole number no smaller than `min`. Depth allows 0: audit the entry page alone. */
|
|
412
|
+
function wholeNumber(min) {
|
|
413
|
+
return (value) => {
|
|
414
|
+
const parsed = Number(value);
|
|
415
|
+
if (!Number.isInteger(parsed) || parsed < min) throw new InvalidArgumentError(`expected a whole number of ${min} or more`);
|
|
416
|
+
return parsed;
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
function parseDate(value) {
|
|
420
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(value) || Number.isNaN(Date.parse(`${value}T00:00:00Z`))) throw new InvalidArgumentError("expected an ISO date, e.g. 2026-12-31");
|
|
421
|
+
return value;
|
|
422
|
+
}
|
|
423
|
+
const parseLocale = oneOf(STATEMENT_LOCALES);
|
|
424
|
+
const parseCountry = oneOf(COUNTRIES, (value) => value.toUpperCase());
|
|
425
|
+
const parseStatementFormat = oneOf(STATEMENT_FORMATS);
|
|
426
|
+
const parseImpact = oneOf(IMPACT_LEVELS);
|
|
427
|
+
const parseFormat = oneOf(OUTPUT_FORMATS);
|
|
428
|
+
const parsePositive = wholeNumber(1);
|
|
429
|
+
const parseDepth = wholeNumber(0);
|
|
430
|
+
const parseConcurrency = wholeNumber(1);
|
|
166
431
|
const program = new Command();
|
|
167
432
|
program.exitOverride();
|
|
168
433
|
program.name("eaa-kit").description("WCAG 2.2 AA auditor and EU accessibility statement generator for static sites.\nNot legal advice.").version(TOOL_VERSION, "-v, --version");
|
|
169
|
-
program.command("audit").description("Audit built HTML against WCAG 2.2 AA").argument("[dir]", "directory holding the built site (default: found automatically)").option("--include <globs...>", "glob patterns to audit, relative to dir").option("--exclude <globs...>", "glob patterns to skip").option("--base-url <url>", "audit pages under their real site URL").option("--url <url>", "audit a running site instead of a build directory").option("--no-build", "never run the project build or start its server").option("--per-page", "also list every page and its result").option("--allow-remote", "allow --url to crawl a host that is not localhost").option("--ignore-robots", "crawl paths robots.txt disallows").option("--max-pages <n>", "stop the crawl after this many pages", parsePositive).option("--max-depth <n>", "how far from the entry URL to follow links", parseDepth).option("--fail-on <impact>", `exit 1 on violations at or above this impact (${IMPACT_LEVELS.join("|")})`, parseImpact, DEFAULT_FAIL_ON).option("--format <format>", `output format (${OUTPUT_FORMATS.join("|")})`, parseFormat, "console").option("--output <path>", "write the report to a file instead of stdout").option("--browser", "audit in real Chromium, covering the rules jsdom cannot evaluate").option("--concurrency <n>", "worker threads to audit with, or 1 for none (default: from the page and core count)", parseConcurrency).option("--baseline <path>", "accept the violations recorded in this file; fail only on new ones").action(async (dir,
|
|
434
|
+
program.command("audit").description("Audit built HTML against WCAG 2.2 AA").argument("[dir]", "directory holding the built site (default: found automatically)").option("--include <globs...>", "glob patterns to audit, relative to dir").option("--exclude <globs...>", "glob patterns to skip").option("--base-url <url>", "audit pages under their real site URL").option("--url <url>", "audit a running site instead of a build directory").option("--no-build", "never run the project build or start its server").option("--per-page", "also list every page and its result").option("--manual", "what to check by hand for the rules this engine cannot evaluate").option("--coverage", "list every WCAG 2.2 A/AA criterion and what this run reached on it").option("--allow-remote", "allow --url to crawl a host that is not localhost").option("--ignore-robots", "crawl paths robots.txt disallows").option("--sitemap <path>", "where the site lists its pages, if not /sitemap.xml").option("--max-pages <n>", "stop the crawl after this many pages", parsePositive).option("--max-depth <n>", "how far from the entry URL to follow links", parseDepth).option("--fail-on <impact>", `exit 1 on violations at or above this impact (${IMPACT_LEVELS.join("|")})`, parseImpact, DEFAULT_FAIL_ON).option("--format <format>", `output format (${OUTPUT_FORMATS.join("|")})`, parseFormat, "console").option("--output <path>", "write the report to a file instead of stdout").option("--browser", "audit in real Chromium, covering the rules jsdom cannot evaluate").option("--concurrency <n>", "worker threads to audit with, or 1 for none (default: from the page and core count)", parseConcurrency).option("--baseline <path>", "accept the violations recorded in this file; fail only on new ones").action(async (dir, flags) => {
|
|
435
|
+
const { build, ...options } = flags;
|
|
170
436
|
const { exitCode } = await runAuditCommand(dir, {
|
|
171
|
-
...options
|
|
172
|
-
...
|
|
173
|
-
...Array.isArray(options["include"]) ? { include: options["include"] } : {},
|
|
174
|
-
...Array.isArray(options["exclude"]) ? { exclude: options["exclude"] } : {},
|
|
175
|
-
...typeof options["baseUrl"] === "string" ? { baseUrl: options["baseUrl"] } : {},
|
|
176
|
-
failOn: options["failOn"],
|
|
177
|
-
format: options["format"],
|
|
178
|
-
...typeof options["output"] === "string" ? { output: options["output"] } : {},
|
|
179
|
-
...options["browser"] === true ? { browser: true } : {},
|
|
180
|
-
...typeof options["concurrency"] === "number" ? { concurrency: options["concurrency"] } : {},
|
|
181
|
-
...typeof options["baseline"] === "string" ? { baseline: options["baseline"] } : {},
|
|
182
|
-
...typeof options["url"] === "string" ? { url: options["url"] } : {},
|
|
183
|
-
...options["allowRemote"] === true ? { allowRemote: true } : {},
|
|
184
|
-
...options["ignoreRobots"] === true ? { ignoreRobots: true } : {},
|
|
185
|
-
...typeof options["maxPages"] === "number" ? { maxPages: options["maxPages"] } : {},
|
|
186
|
-
...typeof options["maxDepth"] === "number" ? { maxDepth: options["maxDepth"] } : {}
|
|
437
|
+
...options,
|
|
438
|
+
...build === false ? { noBuild: true } : {}
|
|
187
439
|
});
|
|
188
440
|
process.exitCode = exitCode;
|
|
189
441
|
});
|
|
190
|
-
program.command("baseline").description("Record the violations a build already has, so later runs fail only on new ones").argument("[dir]", "directory holding the built site", "./dist").option("--include <globs...>", "glob patterns to audit, relative to dir").option("--exclude <globs...>", "glob patterns to skip").option("--base-url <url>", "audit pages under their real site URL").option("--url <url>", "record a baseline from a running site instead of a directory").option("--allow-remote", "allow --url to crawl a host that is not localhost").option("--ignore-robots", "crawl paths robots.txt disallows").option("--max-pages <n>", "stop the crawl after this many pages", parsePositive).option("--max-depth <n>", "how far from the entry URL to follow links", parseDepth).option("--output <path>", `where to write it (default: ${DEFAULT_BASELINE_FILE})`).option("--note <text>", "recorded on every entry, for whoever reads the file").option("--expires-on <date>", "ISO date after which the entries stop suppressing", parseDate).option("--browser", "audit in real Chromium instead of jsdom").option("--concurrency <n>", "worker threads to audit with, or 1 for none", parseConcurrency).action(async (dir,
|
|
191
|
-
const { exitCode } = await runBaselineCommand(dir,
|
|
192
|
-
...Array.isArray(options["include"]) ? { include: options["include"] } : {},
|
|
193
|
-
...Array.isArray(options["exclude"]) ? { exclude: options["exclude"] } : {},
|
|
194
|
-
...typeof options["baseUrl"] === "string" ? { baseUrl: options["baseUrl"] } : {},
|
|
195
|
-
...typeof options["output"] === "string" ? { output: options["output"] } : {},
|
|
196
|
-
...typeof options["note"] === "string" ? { note: options["note"] } : {},
|
|
197
|
-
...typeof options["expiresOn"] === "string" ? { expiresOn: options["expiresOn"] } : {},
|
|
198
|
-
...options["browser"] === true ? { browser: true } : {},
|
|
199
|
-
...typeof options["concurrency"] === "number" ? { concurrency: options["concurrency"] } : {},
|
|
200
|
-
...typeof options["url"] === "string" ? { url: options["url"] } : {},
|
|
201
|
-
...options["allowRemote"] === true ? { allowRemote: true } : {},
|
|
202
|
-
...options["ignoreRobots"] === true ? { ignoreRobots: true } : {},
|
|
203
|
-
...typeof options["maxPages"] === "number" ? { maxPages: options["maxPages"] } : {},
|
|
204
|
-
...typeof options["maxDepth"] === "number" ? { maxDepth: options["maxDepth"] } : {}
|
|
205
|
-
});
|
|
442
|
+
program.command("baseline").description("Record the violations a build already has, so later runs fail only on new ones").argument("[dir]", "directory holding the built site", "./dist").option("--include <globs...>", "glob patterns to audit, relative to dir").option("--exclude <globs...>", "glob patterns to skip").option("--base-url <url>", "audit pages under their real site URL").option("--url <url>", "record a baseline from a running site instead of a directory").option("--allow-remote", "allow --url to crawl a host that is not localhost").option("--ignore-robots", "crawl paths robots.txt disallows").option("--sitemap <path>", "where the site lists its pages, if not /sitemap.xml").option("--max-pages <n>", "stop the crawl after this many pages", parsePositive).option("--max-depth <n>", "how far from the entry URL to follow links", parseDepth).option("--output <path>", `where to write it (default: ${DEFAULT_BASELINE_FILE})`).option("--note <text>", "recorded on every entry, for whoever reads the file").option("--expires-on <date>", "ISO date after which the entries stop suppressing", parseDate).option("--browser", "audit in real Chromium instead of jsdom").option("--concurrency <n>", "worker threads to audit with, or 1 for none", parseConcurrency).action(async (dir, flags) => {
|
|
443
|
+
const { exitCode } = await runBaselineCommand(dir, flags);
|
|
206
444
|
process.exitCode = exitCode;
|
|
207
445
|
});
|
|
208
|
-
program.command("
|
|
209
|
-
const {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
});
|
|
446
|
+
program.command("diff").description("Compare two JSON reports: what a change made worse, and what it fixed").argument("<before>", "JSON report from before the change").argument("<after>", "JSON report from after it").option("--format <format>", `output format (${DIFF_FORMATS.join("|")})`, oneOf(DIFF_FORMATS)).option("--output <path>", "write the diff here instead of stdout").option("--fail-on <impact>", `exit 1 on NEW violations at or above this impact (${IMPACT_LEVELS.join("|")})`, parseImpact, DEFAULT_FAIL_ON).action(async (before, after, flags) => {
|
|
447
|
+
const { exitCode } = await runDiffCommand(before, after, flags);
|
|
448
|
+
process.exitCode = exitCode;
|
|
449
|
+
});
|
|
450
|
+
program.command("init").description("Write an eaa.config.json to generate statements from").option("--output <path>", "write here instead of eaa.config.json").option("--force", "overwrite a config that is already there").option("-y, --yes", "take every default without asking").action(async (flags) => {
|
|
451
|
+
const { runInitCommand } = await import("../init-DIWDE35F.js");
|
|
452
|
+
const { exitCode } = await runInitCommand(flags);
|
|
215
453
|
process.exitCode = exitCode;
|
|
216
454
|
});
|
|
217
|
-
program.command("statement").description("Generate an EU accessibility statement from eaa.config").option("--config <path>", "path to the config file, otherwise it is searched for").option("--lang <locale>", `statement language (${STATEMENT_LOCALES.join("|")})`, parseLocale).option("--country <code>", `override the country template (${COUNTRIES.join("|")})`, parseCountry).option("--audit <path>", "list the barriers from an eaa-kit audit --format json report").option("--format <format>", `output format (${STATEMENT_FORMATS.join("|")}), otherwise from the --output extension`, parseStatementFormat).option("--output <path>", "write the statement to a file instead of stdout").action(async (
|
|
455
|
+
program.command("statement").description("Generate an EU accessibility statement from eaa.config").option("--config <path>", "path to the config file, otherwise it is searched for").option("--lang <locale>", `statement language (${STATEMENT_LOCALES.join("|")})`, parseLocale).option("--country <code>", `override the country template (${COUNTRIES.join("|")})`, parseCountry).option("--audit <path>", "list the barriers from an eaa-kit audit --format json report").option("--format <format>", `output format (${STATEMENT_FORMATS.join("|")}), otherwise from the --output extension`, parseStatementFormat).option("--output <path>", "write the statement to a file instead of stdout").action(async (flags) => {
|
|
456
|
+
const { lang, ...options } = flags;
|
|
218
457
|
const { exitCode } = await runStatementCommand({
|
|
219
|
-
...
|
|
220
|
-
...
|
|
221
|
-
...options["country"] ? { country: options["country"] } : {},
|
|
222
|
-
...typeof options["audit"] === "string" ? { audit: options["audit"] } : {},
|
|
223
|
-
...options["format"] ? { format: options["format"] } : {},
|
|
224
|
-
...typeof options["output"] === "string" ? { output: options["output"] } : {}
|
|
458
|
+
...options,
|
|
459
|
+
...lang === void 0 ? {} : { locale: lang }
|
|
225
460
|
});
|
|
226
461
|
process.exitCode = exitCode;
|
|
227
462
|
});
|
|
228
|
-
function parseLocale(value) {
|
|
229
|
-
if (!STATEMENT_LOCALES.includes(value)) throw new InvalidArgumentError(`expected one of ${STATEMENT_LOCALES.join(", ")}`);
|
|
230
|
-
return value;
|
|
231
|
-
}
|
|
232
|
-
function parseCountry(value) {
|
|
233
|
-
const upper = value.toUpperCase();
|
|
234
|
-
if (!COUNTRIES.includes(upper)) throw new InvalidArgumentError(`expected one of ${COUNTRIES.join(", ")}`);
|
|
235
|
-
return upper;
|
|
236
|
-
}
|
|
237
|
-
function parseStatementFormat(value) {
|
|
238
|
-
if (!isStatementFormat(value)) throw new InvalidArgumentError(`expected one of ${STATEMENT_FORMATS.join(", ")}`);
|
|
239
|
-
return value;
|
|
240
|
-
}
|
|
241
|
-
function parseDate(value) {
|
|
242
|
-
if (!/^\d{4}-\d{2}-\d{2}$/.test(value) || Number.isNaN(Date.parse(`${value}T00:00:00Z`))) throw new InvalidArgumentError("expected an ISO date, e.g. 2026-12-31");
|
|
243
|
-
return value;
|
|
244
|
-
}
|
|
245
|
-
function parsePositive(value) {
|
|
246
|
-
const parsed = Number(value);
|
|
247
|
-
if (!Number.isInteger(parsed) || parsed < 1) throw new InvalidArgumentError("expected a whole number of 1 or more");
|
|
248
|
-
return parsed;
|
|
249
|
-
}
|
|
250
|
-
/** Depth 0 is meaningful here: audit only the entry page. */
|
|
251
|
-
function parseDepth(value) {
|
|
252
|
-
const parsed = Number(value);
|
|
253
|
-
if (!Number.isInteger(parsed) || parsed < 0) throw new InvalidArgumentError("expected a whole number of 0 or more");
|
|
254
|
-
return parsed;
|
|
255
|
-
}
|
|
256
|
-
function parseConcurrency(value) {
|
|
257
|
-
const parsed = Number(value);
|
|
258
|
-
if (!Number.isInteger(parsed) || parsed < 1) throw new InvalidArgumentError("expected a whole number of 1 or more");
|
|
259
|
-
return parsed;
|
|
260
|
-
}
|
|
261
|
-
function parseImpact(value) {
|
|
262
|
-
if (!isImpactLevel(value)) throw new InvalidArgumentError(`expected one of ${IMPACT_LEVELS.join(", ")}`);
|
|
263
|
-
return value;
|
|
264
|
-
}
|
|
265
|
-
function parseFormat(value) {
|
|
266
|
-
if (!isOutputFormat(value)) throw new InvalidArgumentError(`expected one of ${OUTPUT_FORMATS.join(", ")}`);
|
|
267
|
-
return value;
|
|
268
|
-
}
|
|
269
463
|
try {
|
|
270
464
|
await program.parseAsync(process.argv);
|
|
271
465
|
} catch (cause) {
|