eaa-kit 0.5.0 → 0.6.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 +35 -2
- 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-DK2StT1_.js +2 -0
- package/dist/{audit-B282A3EA.js → audit-e_uixhPG.js} +95 -48
- package/dist/baseline-BscHfFSW.js +2 -0
- package/dist/{baseline-CuAS2lMk.js → baseline-D2qWmUC6.js} +36 -9
- package/dist/checklist-BtgkxMEa.js +44 -0
- package/dist/cli/index.js +111 -14
- package/dist/{command-C3D7JWn6.js → command-CZKp9Wi_.js} +101 -10
- package/dist/{remediation-CMBIrnpN.js → completeness-Dwq3CPB3.js} +60 -42
- package/dist/{component-C3GL1Mnu.js → component-ChisoFyY.js} +1 -1
- package/dist/{component-DKd3EHOg.js → component-DcCn3BJS.js} +1 -1
- package/dist/coverage-7bQqWNfN.js +2 -0
- package/dist/{coverage-B_Y6l-Ra.js → coverage-BD2_CYns.js} +44 -7
- package/dist/{crawl-BUWPgaGW.js → crawl-BHkJmUkA.js} +42 -8
- package/dist/eleventy/index.d.ts +1 -1
- package/dist/eleventy/index.js +1 -1
- package/dist/{html-C4vktg0A.js → html-Bmh7X_sM.js} +52 -30
- package/dist/{impact-DZt2oBCP.js → impact-luGCnN8F.js} +11 -1
- package/dist/index.d.ts +68 -1
- package/dist/index.js +3 -3
- package/dist/{init-CW7LfGT5.js → init-BjKYttxc.js} +3 -3
- package/dist/{jsdom-B--cEH-G.js → jsdom-CQar6OQS.js} +1 -1
- package/dist/jsdom-DMvU2h-f.js +3 -0
- package/dist/{json-D_Mnnft5.js → json-HJDgJp09.js} +1 -1
- package/dist/{json-DROX33kh.js → json-PNg6BeXp.js} +11 -16
- package/dist/load-Bc_xDGAh.js +2 -0
- package/dist/{load-yAR4wzez.js → load-vZdV_fD1.js} +5 -1
- package/dist/nuxt/index.d.ts +1 -1
- package/dist/nuxt/index.js +1 -1
- package/dist/{playwright-BojtYVUa.js → playwright-C-miJP2b.js} +3 -2
- package/dist/{pool-BO25OIez.js → pool-dkexq5xo.js} +2 -2
- package/dist/{render-DbGOVmhx.js → render-CtsqR0k-.js} +105 -4
- package/dist/{result-BWcYXeRs.js → result-DfUru6y_.js} +24 -1
- package/dist/review-CdMK2GpQ.js +161 -0
- package/dist/review-DIbOTmOM.js +2 -0
- package/dist/{run-C2nKFcb-.js → run-D7hdVXLA.js} +1 -1
- package/dist/{run-CtcEUhbe.d.ts → run-YiqyQA2b.d.ts} +10 -0
- package/dist/{sarif-B-UBcVu8.js → sarif-BKvq_AUm.js} +23 -12
- 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-eUjwYLxC.js +63 -0
- package/package.json +2 -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
package/README.md
CHANGED
|
@@ -9,11 +9,18 @@ in the DACH region — the BFSG in Germany, the BaFG in Austria — and the stat
|
|
|
9
9
|
the statute and supervisory body of **seven countries**: Austria, Germany, Switzerland,
|
|
10
10
|
Spain, France, Italy and the Netherlands, each in its own language as well as English.
|
|
11
11
|
|
|
12
|
+
0.6.0 adds the half no engine can do. Of the 55 WCAG 2.2 A and AA success criteria, 34 have
|
|
13
|
+
no automated rule at all: `checklist` writes those down as a review somebody works through,
|
|
14
|
+
`audit --review` reads the answers back beside what the run measured, and `statement` now
|
|
15
|
+
refuses to publish a claim of full conformance that either one contradicts. Sites behind a
|
|
16
|
+
login or a preview protection are auditable too.
|
|
17
|
+
|
|
12
18
|
```bash
|
|
13
19
|
npx eaa-kit audit # WCAG 2.2 AA report; finds your build itself
|
|
14
20
|
npx eaa-kit diff a.json b.json # what a change made worse, and what it fixed
|
|
15
21
|
npx eaa-kit init # write an eaa.config.json
|
|
16
22
|
npx eaa-kit statement # accessibility statement, in one of seven countries
|
|
23
|
+
npx eaa-kit checklist # the manual review no engine can do for you
|
|
17
24
|
```
|
|
18
25
|
|
|
19
26
|
> **Not legal advice.** eaa-kit reports what an automated engine can and cannot determine
|
|
@@ -95,6 +102,17 @@ could invent a fix that looks right and is not.
|
|
|
95
102
|
written in src/components/Header.astro:12
|
|
96
103
|
```
|
|
97
104
|
|
|
105
|
+
**Records the part no engine can do.** 34 of the 55 criteria have no automated rule at
|
|
106
|
+
all. `eaa-kit checklist` writes them out as a worksheet and a record; `audit --review`
|
|
107
|
+
reads the record back and reports what a person checked *beside* what the run measured,
|
|
108
|
+
never folded into it. A review never overrules the engine, never counts an entry nobody
|
|
109
|
+
answered, and never counts one that has aged out.
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
eaa-kit checklist --output a11y-review.md
|
|
113
|
+
eaa-kit audit --review eaa-review.json --coverage
|
|
114
|
+
```
|
|
115
|
+
|
|
98
116
|
**Says how much of WCAG it could reach.** WCAG 2.2 has 55 success criteria at Levels A and
|
|
99
117
|
AA. axe-core has rules touching 23 of them. Every run says so, and `--coverage` lists all
|
|
100
118
|
55 with what this run reached on each — never as a percentage, because most of WCAG cannot
|
|
@@ -133,6 +151,16 @@ that instead — following the site's own sitemap, wherever it keeps it.
|
|
|
133
151
|
eaa-kit audit --url http://localhost:8000 --sitemap /sitemap_index.xml
|
|
134
152
|
```
|
|
135
153
|
|
|
154
|
+
**Reaches a site behind a login.** A preview deployment or a staging CMS refuses anonymous
|
|
155
|
+
requests, which used to put both out of reach. `--basic-auth` and a repeatable `--header`
|
|
156
|
+
cover them, on the crawl and in the browser, and the credentials are never written into a
|
|
157
|
+
report, a baseline or a SARIF log — nor accepted from `eaa.config`, which is a committed
|
|
158
|
+
file.
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
eaa-kit audit --url https://preview.example.com --basic-auth user:password
|
|
162
|
+
```
|
|
163
|
+
|
|
136
164
|
## Documentation
|
|
137
165
|
|
|
138
166
|
| | |
|
|
@@ -143,6 +171,7 @@ eaa-kit audit --url http://localhost:8000 --sitemap /sitemap_index.xml
|
|
|
143
171
|
| [Baselines](docs/baseline.md) | Adopting the tool on a site that already has violations |
|
|
144
172
|
| [Comparing two runs](docs/reports.md#comparing-two-runs) | The `diff` command, and what it refuses to call fixed |
|
|
145
173
|
| [Coverage of WCAG](docs/audit.md#how-much-of-wcag-a-run-reaches) | What an automated engine can reach at all, and what it cannot |
|
|
174
|
+
| [Recording a manual review](docs/review.md) | The `checklist` command, the record, and what a review is not allowed to do |
|
|
146
175
|
| [Report formats](docs/reports.md) | The JSON contract, SARIF, and the HTML report |
|
|
147
176
|
| [Integrations](docs/integrations.md) | The build plugins, the GitHub Action, and the two builders that get a command instead |
|
|
148
177
|
|
|
@@ -169,11 +198,15 @@ you](docs/audit.md#what-the-browserless-engine-can-and-cannot-tell-you).
|
|
|
169
198
|
|
|
170
199
|
The same applies to the statement: it says what you told it. eaa-kit cannot check whether
|
|
171
200
|
those claims are true, and a statement claiming full conformance for a site that is not
|
|
172
|
-
conformant is worse than no statement at all
|
|
201
|
+
conformant is worse than no statement at all — which is why, given evidence, it now refuses
|
|
202
|
+
to write that particular one. `statement --audit` will not claim full conformance over a
|
|
203
|
+
report listing barriers, and `--review` will not claim it over criteria somebody recorded as
|
|
204
|
+
not met.
|
|
173
205
|
|
|
174
206
|
## Changelog
|
|
175
207
|
|
|
176
|
-
See [CHANGELOG.md](CHANGELOG.md).
|
|
208
|
+
See [CHANGELOG.md](CHANGELOG.md), and [ROADMAP.md](ROADMAP.md) for what 0.6.0 is meant to
|
|
209
|
+
add and what it deliberately will not.
|
|
177
210
|
|
|
178
211
|
## License
|
|
179
212
|
|
package/dist/astro/index.d.ts
CHANGED
package/dist/astro/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as auditBuild, t as BuildAuditError } from "../run-
|
|
1
|
+
import { n as auditBuild, t as BuildAuditError } from "../run-D7hdVXLA.js";
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
3
|
//#region src/astro/index.ts
|
|
4
4
|
var AstroAuditError = class extends Error {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as auditPage } from "../../jsdom-
|
|
1
|
+
import { t as auditPage } from "../../jsdom-CQar6OQS.js";
|
|
2
2
|
import { enableCompileCache } from "node:module";
|
|
3
3
|
import { parentPort, workerData } from "node:worker_threads";
|
|
4
4
|
//#region src/audit/runners/worker.ts
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
1
|
+
import { a as impactLabel, i as countAtOrAbove, r as byImpactThenRule } from "./impact-luGCnN8F.js";
|
|
2
|
+
import { n as count, o as plural, t as collapse } from "./text-BF1LHMiV.js";
|
|
3
|
+
import { c as runEngine } from "./result-DfUru6y_.js";
|
|
4
|
+
import { a as emitDocument, c as runEngine$1, l as warn, o as fail, s as note, t as advise } from "./command-CZKp9Wi_.js";
|
|
5
|
+
import { l as reviewSentence } from "./review-CdMK2GpQ.js";
|
|
6
|
+
import { r as componentPath } from "./component-ChisoFyY.js";
|
|
7
|
+
import { i as reviewSummary, n as buildCoverage, o as manualCheckFor, r as coverageSummary, s as understandingUrl } from "./coverage-BD2_CYns.js";
|
|
8
|
+
import { a as blindRules, c as isShared, i as remediationFor, l as issueTotals, o as coverageParts, r as runCompleteness, s as groupIssues } from "./completeness-Dwq3CPB3.js";
|
|
9
|
+
import { n as buildSummary } from "./json-PNg6BeXp.js";
|
|
7
10
|
import { i as holdsHtml, n as collectPages, r as emptyDirectoryHint, t as BuildDirectoryError } from "./collect-Cu1q9NTB.js";
|
|
8
11
|
import path from "node:path";
|
|
9
12
|
import pc from "picocolors";
|
|
@@ -51,6 +54,7 @@ function context(options) {
|
|
|
51
54
|
manual: options.manual ?? false,
|
|
52
55
|
coverage: options.coverage ?? false,
|
|
53
56
|
framework: options.framework,
|
|
57
|
+
review: options.review,
|
|
54
58
|
completeness: options.completeness,
|
|
55
59
|
c,
|
|
56
60
|
symbol: (kind) => {
|
|
@@ -92,7 +96,7 @@ function line(ctx, text, paint) {
|
|
|
92
96
|
}]);
|
|
93
97
|
}
|
|
94
98
|
function headerLines(audits, ctx) {
|
|
95
|
-
const engineLabel = (audits
|
|
99
|
+
const engineLabel = runEngine(audits) === "browser" ? "chromium" : "jsdom (browserless)";
|
|
96
100
|
return [render(ctx, [{
|
|
97
101
|
text: "eaa-kit audit",
|
|
98
102
|
paint: ctx.c.bold
|
|
@@ -120,7 +124,7 @@ function pageSection(audit, ctx) {
|
|
|
120
124
|
}, { text: audit.error }]), "");
|
|
121
125
|
return lines;
|
|
122
126
|
}
|
|
123
|
-
for (const finding of
|
|
127
|
+
for (const finding of [...audit.violations].sort(byImpactThenRule)) lines.push(...violationLines(finding, ctx));
|
|
124
128
|
for (const finding of audit.incomplete.filter((item) => item.reason === "needs-review")) lines.push(render(ctx, [
|
|
125
129
|
{
|
|
126
130
|
text: ` ${ctx.symbol("review")} `,
|
|
@@ -145,7 +149,7 @@ function pageSection(audit, ctx) {
|
|
|
145
149
|
paint: clean ? ctx.c.green : ctx.c.dim
|
|
146
150
|
}]));
|
|
147
151
|
}
|
|
148
|
-
for (const finding of
|
|
152
|
+
for (const finding of [...audit.accepted ?? []].sort(byImpactThenRule)) {
|
|
149
153
|
const elements = count(finding.nodes.length, "element");
|
|
150
154
|
lines.push(line(ctx, ` · ${finding.ruleId} accepted by the baseline (${elements})`, ctx.c.dim));
|
|
151
155
|
}
|
|
@@ -157,7 +161,7 @@ function coverageLine(audit, ctx) {
|
|
|
157
161
|
return line(ctx, ` ${coverageParts(audit).join(" · ")}`, ctx.c.dim);
|
|
158
162
|
}
|
|
159
163
|
function violationLines(finding, ctx) {
|
|
160
|
-
const impact = finding.impact
|
|
164
|
+
const impact = impactLabel(finding.impact);
|
|
161
165
|
const lines = [render(ctx, [
|
|
162
166
|
{
|
|
163
167
|
text: ` ${ctx.symbol("violation")} `,
|
|
@@ -181,35 +185,31 @@ function violationLines(finding, ctx) {
|
|
|
181
185
|
return lines;
|
|
182
186
|
}
|
|
183
187
|
function summary(audits, ctx) {
|
|
184
|
-
const
|
|
185
|
-
const errored = audits.filter((audit) => audit.error);
|
|
186
|
-
const ruleCount = audits.reduce((total, audit) => total + audit.violations.length, 0);
|
|
187
|
-
const elementCount = audits.reduce((total, audit) => total + audit.violations.reduce((sum, finding) => sum + finding.nodes.length, 0), 0);
|
|
188
|
+
const totals = buildSummary(audits, ctx.failOn);
|
|
188
189
|
const reviewCount = countRules(audits, "needs-review");
|
|
189
190
|
const pages = count(audits.length, "page");
|
|
190
|
-
const audited =
|
|
191
|
+
const audited = totals.pages - totals.pagesNotAudited;
|
|
191
192
|
const lines = [line(ctx, "Summary", ctx.c.bold), ...completenessLines(ctx)];
|
|
192
|
-
if (
|
|
193
|
-
else if (
|
|
194
|
-
const clean =
|
|
193
|
+
if (totals.violations === 0 && audited === 0 && totals.pagesNotAudited > 0) lines.push(line(ctx, " Nothing was audited: no page could be read.", ctx.c.red));
|
|
194
|
+
else if (totals.violations === 0) {
|
|
195
|
+
const clean = totals.pagesNotAudited > 0 || ctx.completeness && !ctx.completeness.complete ? ` No violations across the ${count(audited, "page")} that were audited.` : ` No violations across ${pages}.`;
|
|
195
196
|
lines.push(line(ctx, clean, ctx.c.green));
|
|
196
197
|
} else {
|
|
197
198
|
lines.push(render(ctx, [{
|
|
198
|
-
text: ` ${count(
|
|
199
|
+
text: ` ${count(totals.violations, "violation")} on ${totals.pagesWithViolations} of ${count(audited, "page")}`,
|
|
199
200
|
paint: ctx.c.red
|
|
200
201
|
}, {
|
|
201
|
-
text: ` (${count(
|
|
202
|
+
text: ` (${count(totals.violatingElements, "element")})`,
|
|
202
203
|
paint: ctx.c.dim
|
|
203
204
|
}]));
|
|
204
|
-
lines.push(thresholdLine(
|
|
205
|
+
lines.push(thresholdLine(totals.failing, ctx));
|
|
205
206
|
}
|
|
206
207
|
if (reviewCount > 0) {
|
|
207
208
|
const verb = reviewCount === 1 ? "needs" : "need";
|
|
208
209
|
lines.push(line(ctx, ` ${count(reviewCount, "rule")} ${verb} manual review`, ctx.c.yellow));
|
|
209
210
|
}
|
|
210
|
-
if (
|
|
211
|
-
|
|
212
|
-
if (accepted > 0) lines.push(line(ctx, ` ${count(accepted, "element")} accepted by the baseline, not counted above`, ctx.c.dim));
|
|
211
|
+
if (totals.pagesNotAudited > 0) lines.push(line(ctx, ` ${count(totals.pagesNotAudited, "page")} could not be audited`, ctx.c.red));
|
|
212
|
+
if (totals.accepted > 0) lines.push(line(ctx, ` ${count(totals.accepted, "element")} accepted by the baseline, not counted above`, ctx.c.dim));
|
|
213
213
|
lines.push(...blindSection(audits, ctx));
|
|
214
214
|
lines.push(...coverageSection(audits, ctx));
|
|
215
215
|
return lines;
|
|
@@ -223,12 +223,14 @@ function summary(audits, ctx) {
|
|
|
223
223
|
* it could be read as boilerplate.
|
|
224
224
|
*/
|
|
225
225
|
function coverageSection(audits, ctx) {
|
|
226
|
-
const coverage = buildCoverage(audits);
|
|
226
|
+
const coverage = buildCoverage(audits, void 0, ctx.review);
|
|
227
227
|
const lines = ["", ...wrap(coverageSummary(coverage), ctx.width - 2).map((text) => line(ctx, ` ${text}`, ctx.c.dim))];
|
|
228
228
|
if (coverage.browserWouldAnswer > 0) {
|
|
229
229
|
const verb = coverage.browserWouldAnswer === 1 ? "criterion" : "criteria";
|
|
230
230
|
lines.push(line(ctx, ` --browser would answer ${coverage.browserWouldAnswer} more ${verb}.`, ctx.c.dim));
|
|
231
231
|
}
|
|
232
|
+
const review = reviewSummary(coverage);
|
|
233
|
+
if (review !== void 0) lines.push(...wrap(review, ctx.width - 2).map((text) => line(ctx, ` ${text}`, ctx.c.dim)));
|
|
232
234
|
if (!ctx.coverage) {
|
|
233
235
|
lines.push(line(ctx, " Run with --coverage for the criterion-by-criterion breakdown.", ctx.c.dim));
|
|
234
236
|
return lines;
|
|
@@ -248,6 +250,8 @@ function coverageSection(audits, ctx) {
|
|
|
248
250
|
paint
|
|
249
251
|
}
|
|
250
252
|
]));
|
|
253
|
+
const recorded = criterion.review;
|
|
254
|
+
if (recorded !== void 0) lines.push(line(ctx, ` ${reviewSentence(recorded)}`, ctx.c.dim));
|
|
251
255
|
}
|
|
252
256
|
return lines;
|
|
253
257
|
}
|
|
@@ -285,9 +289,8 @@ function completenessLines(ctx) {
|
|
|
285
289
|
* Why the run passed or failed. Without this, a build that exits 0 while the
|
|
286
290
|
* report lists violations looks like a bug rather than a threshold choice.
|
|
287
291
|
*/
|
|
288
|
-
function thresholdLine(
|
|
292
|
+
function thresholdLine(failing, ctx) {
|
|
289
293
|
const failOn = ctx.failOn;
|
|
290
|
-
const failing = countAtOrAbove(audits, failOn);
|
|
291
294
|
if (failing === 0) return line(ctx, ` none at or above ${failOn} (--fail-on ${failOn}), so this run passes`, ctx.c.green);
|
|
292
295
|
return line(ctx, ` ${failing} at or above ${failOn} (--fail-on ${failOn})`, ctx.c.red);
|
|
293
296
|
}
|
|
@@ -332,9 +335,6 @@ function countRules(audits, reason) {
|
|
|
332
335
|
for (const audit of audits) for (const finding of audit.incomplete) if (finding.reason === reason) ruleIds.add(finding.ruleId);
|
|
333
336
|
return ruleIds.size;
|
|
334
337
|
}
|
|
335
|
-
function sortByImpact(findings) {
|
|
336
|
-
return [...findings].sort((a, b) => impactRank(a.impact) - impactRank(b.impact) || a.ruleId.localeCompare(b.ruleId));
|
|
337
|
-
}
|
|
338
338
|
function criteria(finding) {
|
|
339
339
|
return finding.successCriteria.length > 0 ? `, WCAG ${finding.successCriteria.join(" ")}` : "";
|
|
340
340
|
}
|
|
@@ -350,8 +350,7 @@ function criteria(finding) {
|
|
|
350
350
|
function issuesSection(audits, ctx) {
|
|
351
351
|
const issues = groupIssues(audits);
|
|
352
352
|
if (issues.length === 0) return [];
|
|
353
|
-
const elements
|
|
354
|
-
const occurrences = issues.reduce((total, issue) => total + issue.occurrences, 0);
|
|
353
|
+
const { elements, occurrences } = issueTotals(issues);
|
|
355
354
|
const lines = [line(ctx, "Issues", ctx.c.bold)];
|
|
356
355
|
const intro = occurrences === elements ? ` ${count(elements, "distinct element")} to fix.` : ` ${count(occurrences, "violation")} across the site come from ${count(elements, "distinct element")}.`;
|
|
357
356
|
lines.push(line(ctx, intro, ctx.c.dim));
|
|
@@ -522,7 +521,7 @@ async function resolvePages(directory, options = {}) {
|
|
|
522
521
|
* exit 2 — a run that reached no verdict, not a clean one.
|
|
523
522
|
*/
|
|
524
523
|
async function crawlPages(url, options) {
|
|
525
|
-
const { crawlSite, CrawlError, parseEntryUrl } = await import("./crawl-
|
|
524
|
+
const { collapsedOnto, crawlSite, CrawlError, parseEntryUrl } = await import("./crawl-BHkJmUkA.js");
|
|
526
525
|
let entry;
|
|
527
526
|
try {
|
|
528
527
|
entry = parseEntryUrl(url, options.allowRemote ?? false);
|
|
@@ -540,7 +539,8 @@ async function crawlPages(url, options) {
|
|
|
540
539
|
...options.sitemap === void 0 ? {} : { sitemap: options.sitemap },
|
|
541
540
|
...options.maxPages === void 0 ? {} : { maxPages: options.maxPages },
|
|
542
541
|
...options.maxDepth === void 0 ? {} : { maxDepth: options.maxDepth },
|
|
543
|
-
...options.timeoutMs === void 0 ? {} : { timeoutMs: options.timeoutMs }
|
|
542
|
+
...options.timeoutMs === void 0 ? {} : { timeoutMs: options.timeoutMs },
|
|
543
|
+
...options.headers === void 0 ? {} : { headers: options.headers }
|
|
544
544
|
});
|
|
545
545
|
if (result.pages.length === 0 && result.failures.length > 0) {
|
|
546
546
|
fail(`Could not fetch ${entry.href} (${result.failures[0]?.reason})`);
|
|
@@ -556,16 +556,26 @@ async function crawlPages(url, options) {
|
|
|
556
556
|
if (result.failures.length > 10) note(` …and ${result.failures.length - 10} more`);
|
|
557
557
|
}
|
|
558
558
|
if (result.truncated) warn(`Stopped at ${count(result.pages.length, "page")}; the site has more. Raise --max-pages to go further.`);
|
|
559
|
+
const collapsed = collapsedOnto(result);
|
|
560
|
+
if (collapsed.length > 0) {
|
|
561
|
+
const landedOn = collapsed[0]?.landedOn ?? "";
|
|
562
|
+
warn(`Every page requested answered at ${landedOn}, which is not where it was asked.`);
|
|
563
|
+
note(" A sign-in page in front of the site looks like this.");
|
|
564
|
+
note(" If it is behind one, pass --basic-auth user:password or --header \"Cookie: …\".");
|
|
565
|
+
}
|
|
559
566
|
return {
|
|
560
567
|
pages: result.pages,
|
|
561
568
|
origin: result.origin,
|
|
562
569
|
completeness: {
|
|
563
570
|
discovery: result.discovery,
|
|
564
571
|
collected: result.pages.length,
|
|
565
|
-
unreachable: result.failures.map((failure) => ({
|
|
572
|
+
unreachable: [...result.failures.map((failure) => ({
|
|
566
573
|
location: failure.url,
|
|
567
574
|
reason: failure.reason
|
|
568
|
-
})),
|
|
575
|
+
})), ...collapsed.map((redirect) => ({
|
|
576
|
+
location: redirect.requested,
|
|
577
|
+
reason: `answered at ${redirect.landedOn} instead, so this page was not audited`
|
|
578
|
+
}))],
|
|
569
579
|
truncated: result.truncated
|
|
570
580
|
}
|
|
571
581
|
};
|
|
@@ -627,11 +637,13 @@ async function runAuditCommand(dir, options = {}) {
|
|
|
627
637
|
exitCode: 2
|
|
628
638
|
};
|
|
629
639
|
const { pages, origin, label, cleanup, directory, completeness: collection } = resolved;
|
|
640
|
+
if (options.headers !== void 0 && directory !== void 0 && !options.browser) warn("--header and --basic-auth apply to pages that are fetched; this run read files.");
|
|
630
641
|
try {
|
|
631
642
|
note(`Auditing ${count(pages.length, "page")} in ${label}${await describeEngine(pages, options)}…`);
|
|
632
643
|
const baseUrl = options.baseUrl ?? origin;
|
|
633
|
-
let audits = await runEngine(pages, {
|
|
644
|
+
let audits = await runEngine$1(pages, {
|
|
634
645
|
cwd: options.cwd ?? process.cwd(),
|
|
646
|
+
...options.headers === void 0 ? {} : { headers: options.headers },
|
|
635
647
|
...baseUrl === void 0 ? {} : { baseUrl },
|
|
636
648
|
...options.timeoutMs === void 0 ? {} : { timeoutMs: options.timeoutMs },
|
|
637
649
|
...options.browser ? { browser: true } : {},
|
|
@@ -653,7 +665,12 @@ async function runAuditCommand(dir, options = {}) {
|
|
|
653
665
|
audits = applied;
|
|
654
666
|
}
|
|
655
667
|
const completeness = runCompleteness(audits, collection);
|
|
656
|
-
|
|
668
|
+
const review = await loadReview(options);
|
|
669
|
+
if (review === FAILED) return {
|
|
670
|
+
audits,
|
|
671
|
+
exitCode: 2
|
|
672
|
+
};
|
|
673
|
+
await emit(audits, label, failOn, completeness, options, review);
|
|
657
674
|
const unaudited = audits.filter((audit) => audit.error);
|
|
658
675
|
if (unaudited.length > 0) {
|
|
659
676
|
fail(`${unaudited.length} of ${audits.length} pages could not be audited`);
|
|
@@ -679,7 +696,7 @@ async function runAuditCommand(dir, options = {}) {
|
|
|
679
696
|
* would be as wrong as silently passing.
|
|
680
697
|
*/
|
|
681
698
|
async function acceptBaseline(audits, options) {
|
|
682
|
-
const { applyBaseline, BaselineError, readBaseline } = await import("./baseline-
|
|
699
|
+
const { applyBaseline, BaselineError, readBaseline } = await import("./baseline-BscHfFSW.js");
|
|
683
700
|
try {
|
|
684
701
|
const outcome = applyBaseline(audits, await readBaseline(options.baseline, options.cwd ?? process.cwd()));
|
|
685
702
|
if (outcome.accepted > 0) note(`Baseline accepted ${outcome.accepted} violating elements`);
|
|
@@ -697,6 +714,32 @@ async function acceptBaseline(audits, options) {
|
|
|
697
714
|
throw cause;
|
|
698
715
|
}
|
|
699
716
|
}
|
|
717
|
+
/** Distinguishes "no review asked for" from "the review could not be read". */
|
|
718
|
+
const FAILED = Symbol("review-failed");
|
|
719
|
+
/**
|
|
720
|
+
* Read the review record, when one was asked for.
|
|
721
|
+
*
|
|
722
|
+
* Returns undefined when no `--review` was given, which is the ordinary case,
|
|
723
|
+
* and the sentinel when one was given and could not be read.
|
|
724
|
+
*/
|
|
725
|
+
async function loadReview(options) {
|
|
726
|
+
if (options.review === void 0) return void 0;
|
|
727
|
+
const { answeredCount, readReview, ReviewError } = await import("./review-DIbOTmOM.js");
|
|
728
|
+
try {
|
|
729
|
+
const record = await readReview(options.review, options.cwd ?? process.cwd());
|
|
730
|
+
note(`Review record: ${count(answeredCount(record), "criterion")} answered`);
|
|
731
|
+
return {
|
|
732
|
+
record,
|
|
733
|
+
...options.reviewMaxAge === void 0 ? {} : { maxAgeDays: options.reviewMaxAge }
|
|
734
|
+
};
|
|
735
|
+
} catch (cause) {
|
|
736
|
+
if (cause instanceof ReviewError) {
|
|
737
|
+
fail(cause.message);
|
|
738
|
+
return FAILED;
|
|
739
|
+
}
|
|
740
|
+
throw cause;
|
|
741
|
+
}
|
|
742
|
+
}
|
|
700
743
|
/**
|
|
701
744
|
* What the progress line says about the engine.
|
|
702
745
|
*
|
|
@@ -706,7 +749,7 @@ async function acceptBaseline(audits, options) {
|
|
|
706
749
|
*/
|
|
707
750
|
async function describeEngine(pages, options) {
|
|
708
751
|
if (options.browser) return " with Chromium";
|
|
709
|
-
const { plannedWorkers } = await import("./pool-
|
|
752
|
+
const { plannedWorkers } = await import("./pool-dkexq5xo.js");
|
|
710
753
|
const workers = options.concurrency ?? plannedWorkers(pages);
|
|
711
754
|
const threads = workers > 1 ? ` across ${workers} threads` : "";
|
|
712
755
|
return options.fast ? `${threads}, skipping what this engine cannot decide` : threads;
|
|
@@ -716,38 +759,41 @@ async function describeEngine(pages, options) {
|
|
|
716
759
|
* otherwise. Colour is dropped when writing to a file, since escape codes in a
|
|
717
760
|
* saved report are noise.
|
|
718
761
|
*/
|
|
719
|
-
async function emit(audits, dir, failOn, completeness, options) {
|
|
720
|
-
const body = await renderReport(audits, dir, failOn, completeness, options.format ?? "console", typeof options.output === "string", options);
|
|
762
|
+
async function emit(audits, dir, failOn, completeness, options, review) {
|
|
763
|
+
const body = await renderReport(audits, dir, failOn, completeness, options.format ?? "console", typeof options.output === "string", options, review);
|
|
721
764
|
await emitDocument(body, options.output, options.cwd ?? process.cwd());
|
|
722
765
|
if (options.output !== void 0) note(`Report written to ${options.output}`);
|
|
723
766
|
}
|
|
724
|
-
async function renderReport(audits, dir, failOn, completeness, format, toFile, options) {
|
|
767
|
+
async function renderReport(audits, dir, failOn, completeness, format, toFile, options, review) {
|
|
725
768
|
switch (format) {
|
|
726
769
|
case "json": {
|
|
727
|
-
const { buildJsonReport, serialiseJsonReport } = await import("./json-
|
|
770
|
+
const { buildJsonReport, serialiseJsonReport } = await import("./json-HJDgJp09.js");
|
|
728
771
|
return serialiseJsonReport(buildJsonReport(audits, {
|
|
729
772
|
directory: dir,
|
|
730
773
|
...options.url === void 0 ? {} : { sourceKind: "url" },
|
|
731
774
|
failOn,
|
|
732
775
|
completeness,
|
|
776
|
+
...review === void 0 ? {} : { review },
|
|
733
777
|
...options.baseUrl ? { baseUrl: options.baseUrl } : {}
|
|
734
778
|
}));
|
|
735
779
|
}
|
|
736
780
|
case "sarif": {
|
|
737
|
-
const { buildSarifReport, serialiseSarifReport } = await import("./sarif-
|
|
781
|
+
const { buildSarifReport, serialiseSarifReport } = await import("./sarif-BKvq_AUm.js");
|
|
738
782
|
return serialiseSarifReport(buildSarifReport(audits, {
|
|
739
783
|
directory: dir,
|
|
740
|
-
completeness
|
|
784
|
+
completeness,
|
|
785
|
+
...review === void 0 ? {} : { review }
|
|
741
786
|
}));
|
|
742
787
|
}
|
|
743
788
|
case "html": {
|
|
744
|
-
const { buildHtmlReport } = await import("./html-
|
|
789
|
+
const { buildHtmlReport } = await import("./html-Bmh7X_sM.js");
|
|
745
790
|
const framework = await detectedFramework(options.cwd ?? process.cwd());
|
|
746
791
|
return buildHtmlReport(audits, {
|
|
747
792
|
...await sourceLookups(options.cwd ?? process.cwd(), audits),
|
|
748
793
|
directory: dir,
|
|
749
794
|
failOn,
|
|
750
795
|
completeness,
|
|
796
|
+
...review === void 0 ? {} : { review },
|
|
751
797
|
...framework === void 0 ? {} : { framework },
|
|
752
798
|
...options.baseUrl ? { baseUrl: options.baseUrl } : {}
|
|
753
799
|
});
|
|
@@ -759,6 +805,7 @@ async function renderReport(audits, dir, failOn, completeness, format, toFile, o
|
|
|
759
805
|
dir,
|
|
760
806
|
failOn,
|
|
761
807
|
completeness,
|
|
808
|
+
...review === void 0 ? {} : { review },
|
|
762
809
|
...options.perPage ? { perPage: true } : {},
|
|
763
810
|
...options.manual ? { manual: true } : {},
|
|
764
811
|
...options.coverage ? { coverage: true } : {},
|
|
@@ -786,7 +833,7 @@ async function sourceLookups(cwd, audits) {
|
|
|
786
833
|
sourceFor: (page) => sourceFor(routes, page),
|
|
787
834
|
componentFor: () => void 0
|
|
788
835
|
};
|
|
789
|
-
const { buildComponentIndex, componentFor } = await import("./component-
|
|
836
|
+
const { buildComponentIndex, componentFor } = await import("./component-DcCn3BJS.js");
|
|
790
837
|
const components = await buildComponentIndex(cwd);
|
|
791
838
|
return {
|
|
792
839
|
sourceFor: (page) => sourceFor(routes, page),
|
|
@@ -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 { o as findingElements } from "./result-
|
|
4
|
+
import { o as findingElements } from "./result-DfUru6y_.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,44 @@
|
|
|
1
|
+
import { n as count } from "./text-BF1LHMiV.js";
|
|
2
|
+
import { a as emitDocument, o as fail, s as note } from "./command-CZKp9Wi_.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-7bQqWNfN.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-eUjwYLxC.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 };
|