eaa-kit 0.5.0 → 0.7.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 +53 -5
- package/dist/astro/index.d.ts +1 -1
- package/dist/astro/index.js +1 -1
- package/dist/audit/runners/worker.js +1 -1
- package/dist/audit-Bwm1xBdJ.js +2 -0
- package/dist/{audit-B282A3EA.js → audit-DRjAY2iY.js} +243 -107
- package/dist/{baseline-CuAS2lMk.js → baseline-BpE7DbDq.js} +36 -9
- package/dist/baseline-rtnj3RHB.js +2 -0
- package/dist/cache-D4dOIuOi.js +219 -0
- package/dist/checklist-DDd9jewi.js +44 -0
- package/dist/cli/index.js +130 -17
- package/dist/{command-C3D7JWn6.js → command-3r4fht5f.js} +102 -10
- package/dist/{component-C3GL1Mnu.js → component-ChisoFyY.js} +1 -1
- package/dist/{component-DKd3EHOg.js → component-DcCn3BJS.js} +1 -1
- package/dist/{coverage-B_Y6l-Ra.js → coverage-cb5kPp48.js} +45 -290
- package/dist/coverage-n1X9CkiG.js +3 -0
- package/dist/{crawl-BUWPgaGW.js → crawl-BHkJmUkA.js} +42 -8
- package/dist/criteria-CnPJKGeb.js +286 -0
- package/dist/eleventy/index.d.ts +1 -1
- package/dist/eleventy/index.js +1 -1
- package/dist/{html-C4vktg0A.js → html-BIxxF3FC.js} +54 -30
- package/dist/{impact-DZt2oBCP.js → impact-luGCnN8F.js} +11 -1
- package/dist/index.d.ts +87 -1
- package/dist/index.js +3 -3
- package/dist/{init-CW7LfGT5.js → init-DrFfzjvm.js} +3 -3
- package/dist/jsdom-BUYuVJJm.js +3 -0
- package/dist/{jsdom-B--cEH-G.js → jsdom-CsW_xVcA.js} +11 -12
- package/dist/{json-DROX33kh.js → json-Bus6YhxB.js} +13 -17
- package/dist/{json-D_Mnnft5.js → json-CrQmoTj6.js} +1 -1
- package/dist/{load-yAR4wzez.js → load-BaWMa-G_.js} +11 -2
- package/dist/load-n62iXDq_.js +2 -0
- package/dist/nuxt/index.d.ts +1 -1
- package/dist/nuxt/index.js +1 -1
- package/dist/{playwright-BojtYVUa.js → playwright-ukCr_5o3.js} +3 -2
- package/dist/{pool-BO25OIez.js → pool-B4tEUa2S.js} +6 -2
- package/dist/{remediation-CMBIrnpN.js → remediation-C0klJblu.js} +35 -4
- package/dist/{render-DbGOVmhx.js → render-C1sUZZI_.js} +145 -6
- package/dist/{result-BWcYXeRs.js → result-B7_zVxAX.js} +24 -1
- package/dist/result-CRmjjoAA.js +2 -0
- package/dist/review-CdMK2GpQ.js +161 -0
- package/dist/review-DIbOTmOM.js +2 -0
- package/dist/{run-CtcEUhbe.d.ts → run-BK2blxMI.d.ts} +18 -0
- package/dist/{run-C2nKFcb-.js → run-DrS0Komf.js} +6 -2
- package/dist/{sarif-B-UBcVu8.js → sarif-BNCE0PNT.js} +24 -12
- package/dist/statement/templates/at.de.md +14 -0
- package/dist/statement/templates/at.en.md +14 -0
- package/dist/statement/templates/ch.de.md +14 -0
- package/dist/statement/templates/ch.en.md +14 -0
- package/dist/statement/templates/de.de.md +14 -0
- package/dist/statement/templates/de.en.md +14 -0
- package/dist/statement/templates/es.en.md +14 -0
- package/dist/statement/templates/es.es.md +15 -0
- package/dist/statement/templates/fr.en.md +14 -0
- package/dist/statement/templates/fr.fr.md +15 -0
- package/dist/statement/templates/it.en.md +14 -0
- package/dist/statement/templates/it.it.md +15 -0
- package/dist/statement/templates/nl.en.md +14 -0
- package/dist/statement/templates/nl.nl.md +15 -0
- package/dist/{text-CKKpzkYM.js → text-BF1LHMiV.js} +22 -2
- package/dist/vite/index.d.ts +1 -1
- package/dist/vite/index.js +1 -1
- package/dist/webpack/index.d.ts +1 -1
- package/dist/webpack/index.js +1 -1
- package/dist/worksheet-BIxMtbUd.js +64 -0
- package/package.json +3 -2
- package/dist/audit-DyHPmGeD.js +0 -2
- package/dist/baseline-0u4df12v.js +0 -2
- package/dist/jsdom-22Bkt65v.js +0 -3
- package/dist/load-CFq2VQtT.js +0 -2
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { c as nullable, d as optional, h as string, l as number, m as safeParse, o as isoDate
|
|
1
|
+
import { c as nullable, d as optional, h as string, l as number, m as safeParse, o as isoDate, t as array, u as object, y as withDefault } from "./schema-DJSF4K05.js";
|
|
2
|
+
import { a as isoDate$1 } from "./text-BF1LHMiV.js";
|
|
2
3
|
import { t as elementFingerprint } from "./fingerprint-BjYV_0F7.js";
|
|
3
|
-
import {
|
|
4
|
+
import { s as findingElements } from "./result-B7_zVxAX.js";
|
|
4
5
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
5
6
|
import path from "node:path";
|
|
6
7
|
/** Default filename, used by the CLI when no path is given. */
|
|
@@ -18,7 +19,7 @@ const entrySchema = object({
|
|
|
18
19
|
/** ISO date the entry was written. */
|
|
19
20
|
acceptedOn: withDefault(string(), () => ""),
|
|
20
21
|
/** ISO date after which this entry stops suppressing anything. */
|
|
21
|
-
expiresOn: optional(isoDate
|
|
22
|
+
expiresOn: optional(isoDate()),
|
|
22
23
|
/** Why this is being lived with. Free text, for whoever reads the file. */
|
|
23
24
|
note: optional(string())
|
|
24
25
|
});
|
|
@@ -37,7 +38,7 @@ var BaselineError = class extends Error {
|
|
|
37
38
|
* can still report on what the run actually found.
|
|
38
39
|
*/
|
|
39
40
|
function applyBaseline(audits, baseline, options = {}) {
|
|
40
|
-
const today = isoDate(options.today ?? /* @__PURE__ */ new Date());
|
|
41
|
+
const today = isoDate$1(options.today ?? /* @__PURE__ */ new Date());
|
|
41
42
|
const expired = [];
|
|
42
43
|
const live = /* @__PURE__ */ new Map();
|
|
43
44
|
for (const entry of baseline.entries) if (entry.expiresOn !== void 0 && entry.expiresOn < today) expired.push(entry);
|
|
@@ -93,9 +94,38 @@ function applyBaseline(audits, baseline, options = {}) {
|
|
|
93
94
|
accepted
|
|
94
95
|
};
|
|
95
96
|
}
|
|
97
|
+
/**
|
|
98
|
+
* The baseline without the entries this run showed are gone.
|
|
99
|
+
*
|
|
100
|
+
* `applyBaseline` already decides which those are, and does it carefully: an
|
|
101
|
+
* entry counts as stale only when the page it names was audited and the element
|
|
102
|
+
* was not found. Entries for pages a narrowed run never looked at are not stale
|
|
103
|
+
* and are kept here, because from the outside a page nobody audited and a page
|
|
104
|
+
* that no longer exists are the same thing, and guessing wrong empties the file
|
|
105
|
+
* that is holding the rest of the site's build together.
|
|
106
|
+
*
|
|
107
|
+
* Nothing is added. That is the whole difference between this and recording a
|
|
108
|
+
* baseline again: re-recording accepts whatever the site is failing today,
|
|
109
|
+
* which on a bad day is a new barrier nobody agreed to.
|
|
110
|
+
*/
|
|
111
|
+
function pruneBaseline(baseline, stale) {
|
|
112
|
+
const gone = new Set(stale.map((entry) => key(entry.page, entry.ruleId, entry.fingerprint)));
|
|
113
|
+
if (gone.size === 0) return {
|
|
114
|
+
baseline,
|
|
115
|
+
removed: []
|
|
116
|
+
};
|
|
117
|
+
const kept = baseline.entries.filter((entry) => !gone.has(key(entry.page, entry.ruleId, entry.fingerprint)));
|
|
118
|
+
return {
|
|
119
|
+
baseline: {
|
|
120
|
+
...baseline,
|
|
121
|
+
entries: kept
|
|
122
|
+
},
|
|
123
|
+
removed: baseline.entries.filter((entry) => gone.has(key(entry.page, entry.ruleId, entry.fingerprint)))
|
|
124
|
+
};
|
|
125
|
+
}
|
|
96
126
|
/** Record every violation a run found, so a later run can fail only on new ones. */
|
|
97
127
|
function buildBaseline(audits, options = {}) {
|
|
98
|
-
const acceptedOn = isoDate(options.today ?? /* @__PURE__ */ new Date());
|
|
128
|
+
const acceptedOn = isoDate$1(options.today ?? /* @__PURE__ */ new Date());
|
|
99
129
|
const entries = [];
|
|
100
130
|
for (const audit of audits) for (const finding of audit.violations) for (const node of findingElements(finding)) entries.push({
|
|
101
131
|
page: audit.relativePath,
|
|
@@ -153,8 +183,5 @@ function key(page, ruleId, fingerprint) {
|
|
|
153
183
|
function byEntry(a, b) {
|
|
154
184
|
return a.page.localeCompare(b.page) || a.ruleId.localeCompare(b.ruleId) || a.fingerprint.localeCompare(b.fingerprint);
|
|
155
185
|
}
|
|
156
|
-
function isoDate(date) {
|
|
157
|
-
return date.toISOString().slice(0, 10);
|
|
158
|
-
}
|
|
159
186
|
//#endregion
|
|
160
|
-
export {
|
|
187
|
+
export { pruneBaseline as a, writeBaseline as c, buildBaseline as i, DEFAULT_BASELINE_FILE as n, readBaseline as o, applyBaseline as r, serialiseBaseline as s, BaselineError as t };
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { c as nullable, d as optional, h as string, i as enumeration, l as number, m as safeParse, t as array, u as object, y as withDefault } from "./schema-DJSF4K05.js";
|
|
2
|
+
import { a as isoDate } from "./text-BF1LHMiV.js";
|
|
3
|
+
import { mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { createHash } from "node:crypto";
|
|
6
|
+
const MANIFEST_FILE = "manifest.json";
|
|
7
|
+
const outcomeSchema = object({
|
|
8
|
+
ruleId: string(),
|
|
9
|
+
help: withDefault(string(), () => ""),
|
|
10
|
+
helpUrl: withDefault(string(), () => ""),
|
|
11
|
+
successCriteria: withDefault(array(string()), () => []),
|
|
12
|
+
enClauses: withDefault(array(string()), () => []),
|
|
13
|
+
tags: withDefault(array(string()), () => [])
|
|
14
|
+
});
|
|
15
|
+
const nodeSchema = object({
|
|
16
|
+
html: string(),
|
|
17
|
+
target: withDefault(array(string()), () => []),
|
|
18
|
+
failureSummary: optional(string())
|
|
19
|
+
});
|
|
20
|
+
const findingSchema = object({
|
|
21
|
+
ruleId: string(),
|
|
22
|
+
help: withDefault(string(), () => ""),
|
|
23
|
+
helpUrl: withDefault(string(), () => ""),
|
|
24
|
+
successCriteria: withDefault(array(string()), () => []),
|
|
25
|
+
enClauses: withDefault(array(string()), () => []),
|
|
26
|
+
tags: withDefault(array(string()), () => []),
|
|
27
|
+
impact: withDefault(nullable(string()), () => null),
|
|
28
|
+
nodes: withDefault(array(nodeSchema), () => [])
|
|
29
|
+
});
|
|
30
|
+
const incompleteSchema = object({
|
|
31
|
+
ruleId: string(),
|
|
32
|
+
help: withDefault(string(), () => ""),
|
|
33
|
+
helpUrl: withDefault(string(), () => ""),
|
|
34
|
+
successCriteria: withDefault(array(string()), () => []),
|
|
35
|
+
enClauses: withDefault(array(string()), () => []),
|
|
36
|
+
tags: withDefault(array(string()), () => []),
|
|
37
|
+
impact: withDefault(nullable(string()), () => null),
|
|
38
|
+
nodes: withDefault(array(nodeSchema), () => []),
|
|
39
|
+
reason: enumeration(["needs-review", "engine-limitation"]),
|
|
40
|
+
reasonDetail: withDefault(string(), () => "")
|
|
41
|
+
});
|
|
42
|
+
const entrySchema = object({
|
|
43
|
+
schemaVersion: number(),
|
|
44
|
+
/** The page this describes, as the run names it. */
|
|
45
|
+
page: string(),
|
|
46
|
+
/** ISO date the result was produced, printed by the reports. */
|
|
47
|
+
cachedOn: string(),
|
|
48
|
+
violations: withDefault(array(findingSchema), () => []),
|
|
49
|
+
incomplete: withDefault(array(incompleteSchema), () => []),
|
|
50
|
+
passes: withDefault(array(outcomeSchema), () => []),
|
|
51
|
+
inapplicable: withDefault(array(outcomeSchema), () => [])
|
|
52
|
+
});
|
|
53
|
+
const manifestSchema = object({
|
|
54
|
+
schemaVersion: number(),
|
|
55
|
+
/** Hash of everything in `CacheInputs`. A mismatch discards the cache. */
|
|
56
|
+
key: string(),
|
|
57
|
+
createdOn: withDefault(string(), () => "")
|
|
58
|
+
});
|
|
59
|
+
/**
|
|
60
|
+
* The identity of a page's content.
|
|
61
|
+
*
|
|
62
|
+
* Path and HTML together: the same markup at two paths is two pages, because
|
|
63
|
+
* the path is what a baseline, a diff and every report key on. A full digest
|
|
64
|
+
* rather than the sixteen characters `elementFingerprint` uses — that one is
|
|
65
|
+
* read by people in a committed file, and this one is read only by the tool,
|
|
66
|
+
* where a collision would serve one page's verdicts for another's.
|
|
67
|
+
*/
|
|
68
|
+
function pageKey(page) {
|
|
69
|
+
return createHash("sha256").update(`${page.relativePath}\n${page.html}`).digest("hex");
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* The identity of everything else the verdict depends on.
|
|
73
|
+
*
|
|
74
|
+
* Headers are hashed with the rest and never recorded: what a run sent can
|
|
75
|
+
* change what it was served, so it belongs in the key, and a credential does
|
|
76
|
+
* not belong on disk.
|
|
77
|
+
*/
|
|
78
|
+
function inputsKey(inputs) {
|
|
79
|
+
const headers = Object.entries(inputs.headers ?? {}).map(([name, value]) => `${name.toLowerCase()}=${value}`).sort().join("\n");
|
|
80
|
+
return createHash("sha256").update([
|
|
81
|
+
`tool=${inputs.toolVersion}`,
|
|
82
|
+
`axe=${inputs.axeVersion}`,
|
|
83
|
+
`tags=${[...inputs.tags].join(",")}`,
|
|
84
|
+
`engine=${inputs.engine}`,
|
|
85
|
+
`fast=${inputs.fast}`,
|
|
86
|
+
`viewport=${inputs.viewport ? `${inputs.viewport.width}x${inputs.viewport.height}` : ""}`,
|
|
87
|
+
`baseUrl=${inputs.baseUrl ?? ""}`,
|
|
88
|
+
`timeout=${inputs.timeoutMs ?? ""}`,
|
|
89
|
+
`headers=${createHash("sha256").update(headers).digest("hex")}`
|
|
90
|
+
].join("\n")).digest("hex");
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Open the cache for a run, discarding it if anything about the run changed.
|
|
94
|
+
*
|
|
95
|
+
* Everything is read up front and held in memory: an audit asks about every
|
|
96
|
+
* page before it audits any, the entries are small, and a synchronous answer
|
|
97
|
+
* is what lets the caller decide whether to import an engine at all.
|
|
98
|
+
*/
|
|
99
|
+
async function openCache(inputs, options = {}) {
|
|
100
|
+
const root = path.resolve(options.cwd ?? process.cwd(), options.dir ?? ".eaa-kit/cache");
|
|
101
|
+
const key = inputsKey(inputs);
|
|
102
|
+
const today = isoDate(options.today ?? /* @__PURE__ */ new Date());
|
|
103
|
+
const entries = await manifestMatches(root, key) ? await readEntries(root) : /* @__PURE__ */ new Map();
|
|
104
|
+
const written = /* @__PURE__ */ new Map();
|
|
105
|
+
return {
|
|
106
|
+
get(page) {
|
|
107
|
+
return entries.get(pageKey(page));
|
|
108
|
+
},
|
|
109
|
+
put(page, audit) {
|
|
110
|
+
if (audit.error !== void 0) return;
|
|
111
|
+
written.set(pageKey(page), toEntry(page, audit, today));
|
|
112
|
+
},
|
|
113
|
+
async flush() {
|
|
114
|
+
if (written.size === 0) return;
|
|
115
|
+
try {
|
|
116
|
+
await mkdir(root, { recursive: true });
|
|
117
|
+
await writeFile(path.join(root, MANIFEST_FILE), `${JSON.stringify({
|
|
118
|
+
schemaVersion: 1,
|
|
119
|
+
key,
|
|
120
|
+
createdOn: today
|
|
121
|
+
}, null, 2)}\n`, "utf8");
|
|
122
|
+
await Promise.all([...written].map(([hash, entry]) => writeFile(path.join(root, `${hash}.json`), `${JSON.stringify(entry)}\n`, "utf8")));
|
|
123
|
+
} catch {}
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Whether the stored cache was produced under the same conditions as this run.
|
|
129
|
+
*
|
|
130
|
+
* A mismatch empties the directory rather than leaving entries to be stepped
|
|
131
|
+
* over. They can never be read again — the key that would reach them is gone —
|
|
132
|
+
* and leaving them would grow `.eaa-kit/` without limit.
|
|
133
|
+
*/
|
|
134
|
+
async function manifestMatches(root, key) {
|
|
135
|
+
let raw;
|
|
136
|
+
try {
|
|
137
|
+
raw = await readFile(path.join(root, MANIFEST_FILE), "utf8");
|
|
138
|
+
} catch {
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
let parsed;
|
|
142
|
+
try {
|
|
143
|
+
parsed = JSON.parse(raw);
|
|
144
|
+
} catch {
|
|
145
|
+
await discard(root);
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
const result = safeParse(manifestSchema, parsed);
|
|
149
|
+
if (!result.success || result.data.schemaVersion !== 1 || result.data.key !== key) {
|
|
150
|
+
await discard(root);
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
async function discard(root) {
|
|
156
|
+
try {
|
|
157
|
+
await rm(root, {
|
|
158
|
+
recursive: true,
|
|
159
|
+
force: true
|
|
160
|
+
});
|
|
161
|
+
} catch {}
|
|
162
|
+
}
|
|
163
|
+
/** Every entry in the directory, keyed by page hash. A bad file is skipped. */
|
|
164
|
+
async function readEntries(root) {
|
|
165
|
+
let files;
|
|
166
|
+
try {
|
|
167
|
+
files = await readdir(root);
|
|
168
|
+
} catch {
|
|
169
|
+
return /* @__PURE__ */ new Map();
|
|
170
|
+
}
|
|
171
|
+
const entries = /* @__PURE__ */ new Map();
|
|
172
|
+
await Promise.all(files.filter((file) => file.endsWith(".json") && file !== MANIFEST_FILE).map(async (file) => {
|
|
173
|
+
try {
|
|
174
|
+
const parsed = JSON.parse(await readFile(path.join(root, file), "utf8"));
|
|
175
|
+
const result = safeParse(entrySchema, parsed);
|
|
176
|
+
if (!result.success || result.data.schemaVersion !== 1) return;
|
|
177
|
+
entries.set(path.basename(file, ".json"), result.data);
|
|
178
|
+
} catch {}
|
|
179
|
+
}));
|
|
180
|
+
return entries;
|
|
181
|
+
}
|
|
182
|
+
/** The parts of a result that describe the page rather than the run. */
|
|
183
|
+
function toEntry(page, audit, today) {
|
|
184
|
+
return {
|
|
185
|
+
schemaVersion: 1,
|
|
186
|
+
page: page.relativePath,
|
|
187
|
+
cachedOn: today,
|
|
188
|
+
violations: audit.violations,
|
|
189
|
+
incomplete: audit.incomplete,
|
|
190
|
+
passes: audit.passes,
|
|
191
|
+
inapplicable: audit.inapplicable
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* A stored result, put back together as this run's audit.
|
|
196
|
+
*
|
|
197
|
+
* The page's own facts come from the cache; everything about *this* run — where
|
|
198
|
+
* the file is, what URL it is audited under, which engine is running — comes
|
|
199
|
+
* from the run, because those are not properties of the result. `durationMs` is
|
|
200
|
+
* zero rather than the duration of a run that happened on another day, and
|
|
201
|
+
* `reused` is what stops every consumer downstream from mistaking this for work
|
|
202
|
+
* that was done just now.
|
|
203
|
+
*/
|
|
204
|
+
function fromEntry(entry, page, context) {
|
|
205
|
+
return {
|
|
206
|
+
relativePath: page.relativePath,
|
|
207
|
+
absolutePath: page.absolutePath,
|
|
208
|
+
url: context.url,
|
|
209
|
+
engine: context.engine,
|
|
210
|
+
violations: entry.violations,
|
|
211
|
+
incomplete: entry.incomplete,
|
|
212
|
+
passes: entry.passes,
|
|
213
|
+
inapplicable: entry.inapplicable,
|
|
214
|
+
durationMs: 0,
|
|
215
|
+
reused: { on: entry.cachedOn }
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
//#endregion
|
|
219
|
+
export { fromEntry, openCache };
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { n as count } from "./text-BF1LHMiV.js";
|
|
2
|
+
import { a as emitDocument, o as fail, s as note } from "./command-3r4fht5f.js";
|
|
3
|
+
import "./review-CdMK2GpQ.js";
|
|
4
|
+
import { access } from "node:fs/promises";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
//#region src/cli/checklist.ts
|
|
7
|
+
async function runChecklistCommand(options = {}) {
|
|
8
|
+
const cwd = options.cwd ?? process.cwd();
|
|
9
|
+
const recordPath = options.record ?? "eaa-review.json";
|
|
10
|
+
const { WCAG22_AA_CRITERIA } = await import("./coverage-n1X9CkiG.js");
|
|
11
|
+
const { answeredCount, blankReview, readReview, ReviewError, writeReview } = await import("./review-DIbOTmOM.js");
|
|
12
|
+
let existing;
|
|
13
|
+
try {
|
|
14
|
+
existing = await readReview(recordPath, cwd);
|
|
15
|
+
} catch (cause) {
|
|
16
|
+
if (!(cause instanceof ReviewError)) throw cause;
|
|
17
|
+
if (await exists(path.resolve(cwd, recordPath))) {
|
|
18
|
+
fail(cause.message);
|
|
19
|
+
return { exitCode: 2 };
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
const record = blankReview(WCAG22_AA_CRITERIA, existing);
|
|
23
|
+
if (options.reviewedBy !== void 0) record.reviewedBy = options.reviewedBy;
|
|
24
|
+
await writeReview(recordPath, record, cwd);
|
|
25
|
+
note(existing === void 0 ? `Review record written to ${recordPath}` : `Review record updated at ${recordPath}: ${count(answeredCount(record), "criterion")} already answered`);
|
|
26
|
+
const { buildWorksheet } = await import("./worksheet-BIxMtbUd.js");
|
|
27
|
+
await emitDocument(buildWorksheet({
|
|
28
|
+
recordPath,
|
|
29
|
+
review: record
|
|
30
|
+
}), options.output, cwd);
|
|
31
|
+
if (options.output !== void 0) note(`Worksheet written to ${options.output}`);
|
|
32
|
+
note("Answers go in the record; the worksheet is generated and never read back.");
|
|
33
|
+
return { exitCode: 0 };
|
|
34
|
+
}
|
|
35
|
+
async function exists(target) {
|
|
36
|
+
try {
|
|
37
|
+
await access(target);
|
|
38
|
+
return true;
|
|
39
|
+
} catch {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
//#endregion
|
|
44
|
+
export { runChecklistCommand };
|
package/dist/cli/index.js
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { c as meetsThreshold, n as IMPACT_LEVELS, o as impactRank, s as isImpactLevel, t as DEFAULT_FAIL_ON } from "../impact-luGCnN8F.js";
|
|
3
3
|
import { c as nullable, d as optional, h as string, l as number, m as safeParse, n as boolean, p as record, t as array, u as object, y as withDefault } from "../schema-DJSF4K05.js";
|
|
4
|
-
import { c as ConfigError, i as loadConfig, s as COUNTRIES, u as STATEMENT_LOCALES } from "../load-
|
|
5
|
-
import { i as readAuditReport, o as
|
|
6
|
-
import { n as count, t as collapse } from "../text-
|
|
4
|
+
import { c as ConfigError, i as loadConfig, s as COUNTRIES, u as STATEMENT_LOCALES } from "../load-BaWMa-G_.js";
|
|
5
|
+
import { c as StatementError, i as readAuditReport, o as checkStatementEvidence, s as refuses, t as renderStatement } from "../render-C1sUZZI_.js";
|
|
6
|
+
import { n as count, t as collapse } from "../text-BF1LHMiV.js";
|
|
7
7
|
import { t as TOOL_VERSION } from "../version-B3v4rNoG.js";
|
|
8
8
|
import { t as elementFingerprint } from "../fingerprint-BjYV_0F7.js";
|
|
9
|
-
import { i as buildBaseline, n as DEFAULT_BASELINE_FILE,
|
|
10
|
-
import { n as
|
|
11
|
-
import {
|
|
9
|
+
import { a as pruneBaseline, c as writeBaseline, i as buildBaseline, n as DEFAULT_BASELINE_FILE, o as readBaseline, r as applyBaseline, t as BaselineError } from "../baseline-BpE7DbDq.js";
|
|
10
|
+
import { a as emitDocument, c as runEngine, d as parseHeader, i as baselineInvocation, l as warn, n as auditDefaults, o as fail, r as auditInvocation, s as note, t as advise, u as basicAuth } from "../command-3r4fht5f.js";
|
|
11
|
+
import { t as DEFAULT_REVIEW_FILE } from "../review-CdMK2GpQ.js";
|
|
12
|
+
import { n as runAuditCommand, r as resolvePages, t as OUTPUT_FORMATS } from "../audit-DRjAY2iY.js";
|
|
12
13
|
import { enableCompileCache } from "node:module";
|
|
13
14
|
import { readFile } from "node:fs/promises";
|
|
14
15
|
import path from "node:path";
|
|
@@ -42,6 +43,7 @@ async function runBaselineCommand(dir, options = {}) {
|
|
|
42
43
|
const baseUrl = options.baseUrl ?? origin;
|
|
43
44
|
const audits = await runEngine(pages, {
|
|
44
45
|
cwd,
|
|
46
|
+
...options.headers === void 0 ? {} : { headers: options.headers },
|
|
45
47
|
...baseUrl === void 0 ? {} : { baseUrl },
|
|
46
48
|
...options.timeoutMs === void 0 ? {} : { timeoutMs: options.timeoutMs },
|
|
47
49
|
...options.browser ? { browser: true } : {},
|
|
@@ -60,11 +62,12 @@ async function runBaselineCommand(dir, options = {}) {
|
|
|
60
62
|
exitCode: 2
|
|
61
63
|
};
|
|
62
64
|
}
|
|
65
|
+
const target = options.output ?? "eaa-baseline.json";
|
|
66
|
+
if (options.prune) return await prune(audits, target, cwd);
|
|
63
67
|
const baseline = buildBaseline(audits, {
|
|
64
68
|
...options.note ? { note: options.note } : {},
|
|
65
69
|
...options.expiresOn ? { expiresOn: options.expiresOn } : {}
|
|
66
70
|
});
|
|
67
|
-
const target = options.output ?? "eaa-baseline.json";
|
|
68
71
|
try {
|
|
69
72
|
await writeBaseline(target, baseline, cwd);
|
|
70
73
|
} catch (cause) {
|
|
@@ -78,13 +81,60 @@ async function runBaselineCommand(dir, options = {}) {
|
|
|
78
81
|
throw cause;
|
|
79
82
|
}
|
|
80
83
|
const entries = baseline.entries.length;
|
|
81
|
-
note(`Wrote ${entries
|
|
84
|
+
note(`Wrote ${count(entries, "entry")} to ${target}`);
|
|
82
85
|
if (entries > 0) advise("These are barriers, not exceptions. Commit the file, then work the list down.");
|
|
83
86
|
return {
|
|
84
87
|
entries,
|
|
85
88
|
exitCode: 0
|
|
86
89
|
};
|
|
87
90
|
}
|
|
91
|
+
/**
|
|
92
|
+
* `--prune`: take out what this run shows is gone, and add nothing.
|
|
93
|
+
*
|
|
94
|
+
* The audit already tells anybody running it that entries no longer match and
|
|
95
|
+
* can be removed. Acting on that advice meant editing JSON by hand, entry by
|
|
96
|
+
* entry, against a file whose whole purpose is that nobody has to remember what
|
|
97
|
+
* is in it — so most people did not, and a baseline accumulated barriers that
|
|
98
|
+
* were fixed years ago while looking exactly like one that had not been read.
|
|
99
|
+
*
|
|
100
|
+
* Recording a new baseline is not the same operation and is not a substitute:
|
|
101
|
+
* it accepts whatever the site fails today, which on a bad day quietly adopts a
|
|
102
|
+
* barrier nobody agreed to.
|
|
103
|
+
*/
|
|
104
|
+
async function prune(audits, target, cwd) {
|
|
105
|
+
let existing;
|
|
106
|
+
try {
|
|
107
|
+
existing = await readBaseline(target, cwd);
|
|
108
|
+
} catch (cause) {
|
|
109
|
+
if (cause instanceof BaselineError) {
|
|
110
|
+
fail(cause.message);
|
|
111
|
+
return {
|
|
112
|
+
entries: 0,
|
|
113
|
+
exitCode: 2
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
throw cause;
|
|
117
|
+
}
|
|
118
|
+
const outcome = applyBaseline(audits, existing);
|
|
119
|
+
const { baseline, removed } = pruneBaseline(existing, outcome.stale);
|
|
120
|
+
if (removed.length === 0) {
|
|
121
|
+
note(`Nothing to remove from ${target}: every entry still matches something on this run`);
|
|
122
|
+
return {
|
|
123
|
+
entries: existing.entries.length,
|
|
124
|
+
removed: 0,
|
|
125
|
+
exitCode: 0
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
await writeBaseline(target, baseline, cwd);
|
|
129
|
+
note(`Removed ${count(removed.length, "entry")} from ${target}; ${count(baseline.entries.length, "entry")} left`);
|
|
130
|
+
for (const entry of removed) note(` ${entry.page} ${entry.ruleId}`);
|
|
131
|
+
if (outcome.expired.length > 0) advise(`${count(outcome.expired.length, "entry")} have expired and suppress nothing; --prune leaves them for you to decide about`);
|
|
132
|
+
return {
|
|
133
|
+
entries: baseline.entries.length,
|
|
134
|
+
removed: removed.length,
|
|
135
|
+
exitCode: 0
|
|
136
|
+
};
|
|
137
|
+
}
|
|
88
138
|
var DiffError = class extends Error {
|
|
89
139
|
name = "DiffError";
|
|
90
140
|
};
|
|
@@ -105,6 +155,8 @@ const reportSchema = object({
|
|
|
105
155
|
pages: withDefault(array(object({
|
|
106
156
|
path: string(),
|
|
107
157
|
error: withDefault(nullable(string()), () => null),
|
|
158
|
+
/** Present when the later run reused this page rather than auditing it. */
|
|
159
|
+
reusedFrom: optional(string()),
|
|
108
160
|
violations: withDefault(array(object({
|
|
109
161
|
ruleId: string(),
|
|
110
162
|
impact: withDefault(nullable(string()), () => null),
|
|
@@ -155,9 +207,22 @@ function entriesOf(report) {
|
|
|
155
207
|
}
|
|
156
208
|
return entries;
|
|
157
209
|
}
|
|
158
|
-
/**
|
|
210
|
+
/**
|
|
211
|
+
* Pages a run reached a verdict on *itself*.
|
|
212
|
+
*
|
|
213
|
+
* A page that errored is not one of them, and neither is a page whose result
|
|
214
|
+
* was reused from a cache: this comparison exists to say what changed between
|
|
215
|
+
* two runs, and a run that did not look at a page learned nothing about it. The
|
|
216
|
+
* difference matters in one direction only, and it is the direction that lies —
|
|
217
|
+
* a violation present in the earlier report and absent from a reused page would
|
|
218
|
+
* otherwise be announced as fixed, which is a changelog of work nobody did.
|
|
219
|
+
*
|
|
220
|
+
* A report written before reuse existed carries no marker, and is read exactly
|
|
221
|
+
* as it was meant when it was written: those runs audited every page they
|
|
222
|
+
* listed.
|
|
223
|
+
*/
|
|
159
224
|
function auditedPages(report) {
|
|
160
|
-
return new Set(report.pages.filter((page) => page.error === null).map((page) => page.path));
|
|
225
|
+
return new Set(report.pages.filter((page) => page.error === null && page.reusedFrom === void 0).map((page) => page.path));
|
|
161
226
|
}
|
|
162
227
|
function sideOf(report) {
|
|
163
228
|
return {
|
|
@@ -353,10 +418,28 @@ async function runStatementCommand(options = {}) {
|
|
|
353
418
|
});
|
|
354
419
|
let audit;
|
|
355
420
|
if (options.audit) audit = await readAuditReport(options.audit, options.cwd ?? process.cwd());
|
|
421
|
+
let review;
|
|
422
|
+
if (options.review) {
|
|
423
|
+
const { readReview } = await import("../review-DIbOTmOM.js");
|
|
424
|
+
review = await readReview(options.review, options.cwd ?? process.cwd());
|
|
425
|
+
}
|
|
426
|
+
const problems = checkStatementEvidence({
|
|
427
|
+
config,
|
|
428
|
+
...audit ? { audit } : {},
|
|
429
|
+
...review ? { review } : {}
|
|
430
|
+
});
|
|
431
|
+
for (const problem of problems) if (problem.severity === "refuses") fail(problem.message);
|
|
432
|
+
else advise(problem.message);
|
|
433
|
+
if (refuses(problems)) return {
|
|
434
|
+
document: "",
|
|
435
|
+
format,
|
|
436
|
+
exitCode: 2
|
|
437
|
+
};
|
|
356
438
|
const statement = await renderStatement(config, {
|
|
357
439
|
...options.locale ? { locale: options.locale } : {},
|
|
358
440
|
...options.country ? { country: options.country } : {},
|
|
359
|
-
...audit ? { audit } : {}
|
|
441
|
+
...audit ? { audit } : {},
|
|
442
|
+
...review ? { review } : {}
|
|
360
443
|
});
|
|
361
444
|
note(`Statement for ${config.site.url} from ${path.basename(configPath)} (${statement.template}, ${format})`);
|
|
362
445
|
if (audit) {
|
|
@@ -374,7 +457,8 @@ async function runStatementCommand(options = {}) {
|
|
|
374
457
|
exitCode: 0
|
|
375
458
|
};
|
|
376
459
|
} catch (cause) {
|
|
377
|
-
|
|
460
|
+
const { ReviewError } = await import("../review-DIbOTmOM.js");
|
|
461
|
+
if (cause instanceof ConfigError || cause instanceof StatementError || cause instanceof ReviewError) {
|
|
378
462
|
fail(cause.message);
|
|
379
463
|
if (cause instanceof ConfigError) for (const issue of cause.issues) note(` ${issue}`);
|
|
380
464
|
return {
|
|
@@ -453,31 +537,60 @@ const parseFormat = oneOf(OUTPUT_FORMATS);
|
|
|
453
537
|
const parsePositive = wholeNumber(1);
|
|
454
538
|
const parseDepth = wholeNumber(0);
|
|
455
539
|
const parseConcurrency = wholeNumber(1);
|
|
540
|
+
/**
|
|
541
|
+
* `--header` repeated, collected into a list the command turns into a record.
|
|
542
|
+
*
|
|
543
|
+
* Validated here so a typo stops the run before a site is crawled with a
|
|
544
|
+
* malformed credential — and the error names the header rather than repeating
|
|
545
|
+
* the value, because a bad `--header` is exactly the case where the value is a
|
|
546
|
+
* token and the terminal is a CI log.
|
|
547
|
+
*/
|
|
548
|
+
function collectHeader(value, previous = []) {
|
|
549
|
+
try {
|
|
550
|
+
parseHeader(value);
|
|
551
|
+
} catch (cause) {
|
|
552
|
+
throw new InvalidArgumentError(cause instanceof Error ? cause.message : String(cause));
|
|
553
|
+
}
|
|
554
|
+
return [...previous, value];
|
|
555
|
+
}
|
|
556
|
+
function parseBasicAuth(value) {
|
|
557
|
+
try {
|
|
558
|
+
basicAuth(value);
|
|
559
|
+
} catch (cause) {
|
|
560
|
+
throw new InvalidArgumentError(cause instanceof Error ? cause.message : String(cause));
|
|
561
|
+
}
|
|
562
|
+
return value;
|
|
563
|
+
}
|
|
456
564
|
const program = new Command();
|
|
457
565
|
program.exitOverride();
|
|
458
566
|
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");
|
|
459
|
-
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("|")}; default: ${DEFAULT_FAIL_ON})`, parseImpact).option("--format <format>", `output format (${OUTPUT_FORMATS.join("|")}; default: console)`, parseFormat).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("--fast", "skip the rules the browserless engine cannot decide anyway, losing their element lists").option("--concurrency <n>", "pages to audit at once, 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").option("--config <path>", "take defaults from this config file, otherwise it is searched for").action(async (dir, flags) => {
|
|
567
|
+
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("--header <header>", "send this header with every request, e.g. \"Authorization: Bearer …\". Repeatable", collectHeader).option("--basic-auth <user:password>", "send an Authorization header for basic auth", parseBasicAuth).option("--fail-on <impact>", `exit 1 on violations at or above this impact (${IMPACT_LEVELS.join("|")}; default: ${DEFAULT_FAIL_ON})`, parseImpact).option("--format <format>", `output format (${OUTPUT_FORMATS.join("|")}; default: console)`, parseFormat).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("--fast", "skip the rules the browserless engine cannot decide anyway, losing their element lists").option("--concurrency <n>", "pages to audit at once, 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").option("--no-cache", "audit every page, reusing no result from an earlier run").option("--review <path>", "what a person checked, from eaa-kit checklist").option("--review-max-age <days>", "stop counting review entries older than this", parseDepth).option("--config <path>", "take defaults from this config file, otherwise it is searched for").action(async (dir, flags) => {
|
|
460
568
|
const defaults = await auditDefaults({ ...flags.config ? { config: flags.config } : {} });
|
|
461
569
|
const invocation = auditInvocation(dir, defaults, flags);
|
|
462
570
|
const { exitCode } = await runAuditCommand(invocation.dir, invocation.options);
|
|
463
571
|
process.exitCode = exitCode;
|
|
464
572
|
});
|
|
465
|
-
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 (default: ./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>", "pages to audit at once, or 1 for none", parseConcurrency).option("--config <path>", "take defaults from this config file, otherwise it is searched for").action(async (dir, flags) => {
|
|
573
|
+
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 (default: ./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("--header <header>", "send this header with every request, e.g. \"Authorization: Bearer …\". Repeatable", collectHeader).option("--basic-auth <user:password>", "send an Authorization header for basic auth", parseBasicAuth).option("--output <path>", `where to write it (default: ${DEFAULT_BASELINE_FILE})`).option("--prune", "remove the entries this run shows are gone, instead of recording a new baseline").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>", "pages to audit at once, or 1 for none", parseConcurrency).option("--config <path>", "take defaults from this config file, otherwise it is searched for").action(async (dir, flags) => {
|
|
466
574
|
const defaults = await auditDefaults({ ...flags.config ? { config: flags.config } : {} });
|
|
467
575
|
const invocation = baselineInvocation(dir, defaults, flags);
|
|
468
576
|
const { exitCode } = await runBaselineCommand(invocation.dir, invocation.options);
|
|
469
577
|
process.exitCode = exitCode;
|
|
470
578
|
});
|
|
579
|
+
program.command("checklist").description("Write the manual review: the 34 WCAG criteria no automated rule can reach").option("--record <path>", `where the answers live (default: ${DEFAULT_REVIEW_FILE})`).option("--output <path>", "write the worksheet here instead of stdout").option("--reviewed-by <name>", "who is carrying out the review").action(async (flags) => {
|
|
580
|
+
const { runChecklistCommand } = await import("../checklist-DDd9jewi.js");
|
|
581
|
+
const { exitCode } = await runChecklistCommand(flags);
|
|
582
|
+
process.exitCode = exitCode;
|
|
583
|
+
});
|
|
471
584
|
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) => {
|
|
472
585
|
const { exitCode } = await runDiffCommand(before, after, flags);
|
|
473
586
|
process.exitCode = exitCode;
|
|
474
587
|
});
|
|
475
588
|
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) => {
|
|
476
|
-
const { runInitCommand } = await import("../init-
|
|
589
|
+
const { runInitCommand } = await import("../init-DrFfzjvm.js");
|
|
477
590
|
const { exitCode } = await runInitCommand(flags);
|
|
478
591
|
process.exitCode = exitCode;
|
|
479
592
|
});
|
|
480
|
-
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) => {
|
|
593
|
+
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("--review <path>", "say how many criteria a person checked, and check the conformance claim against them").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) => {
|
|
481
594
|
const { lang, ...options } = flags;
|
|
482
595
|
const { exitCode } = await runStatementCommand({
|
|
483
596
|
...options,
|