eaa-kit 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -9
- package/dist/astro/index.d.ts +1 -1
- package/dist/astro/index.js +1 -1
- package/dist/audit/runners/worker.js +5 -1
- package/dist/{audit-CPoZMXGM.js → audit-B282A3EA.js} +43 -21
- package/dist/audit-DyHPmGeD.js +2 -0
- package/dist/{baseline-22Y1NWxM.js → baseline-0u4df12v.js} +1 -1
- package/dist/{baseline-DB9CZGnV.js → baseline-CuAS2lMk.js} +6 -5
- package/dist/cli/index.js +50 -19
- package/dist/{collect-CFM8gEVv.js → collect-Cu1q9NTB.js} +34 -3
- package/dist/command-C3D7JWn6.js +160 -0
- package/dist/{coverage-B4IBKMO8.js → coverage-B_Y6l-Ra.js} +3 -2
- package/dist/{crawl-BiI1Lau_.js → crawl-BUWPgaGW.js} +83 -9
- package/dist/eleventy/index.d.ts +1 -1
- package/dist/eleventy/index.js +1 -1
- package/dist/fingerprint-BjYV_0F7.js +67 -0
- package/dist/{html-BGTO3ypW.js → html-C4vktg0A.js} +3 -3
- package/dist/index.d.ts +119 -4
- package/dist/index.js +2 -2
- package/dist/{init-DIWDE35F.js → init-CW7LfGT5.js} +10 -4
- package/dist/jsdom-22Bkt65v.js +3 -0
- package/dist/{jsdom-DCpGSLfW.js → jsdom-B--cEH-G.js} +12 -4
- package/dist/{json-DjEvy1nX.js → json-DROX33kh.js} +4 -4
- package/dist/{json-QQuFIw1W.js → json-D_Mnnft5.js} +1 -1
- package/dist/load-CFq2VQtT.js +2 -0
- package/dist/{load-5wRGLvub.js → load-yAR4wzez.js} +133 -8
- package/dist/nuxt/index.d.ts +1 -1
- package/dist/nuxt/index.js +1 -1
- package/dist/{playwright-BWniOain.js → playwright-BojtYVUa.js} +30 -6
- package/dist/{pool-BMevaLWD.js → pool-BO25OIez.js} +52 -2
- package/dist/{remediation-Dtowi2EC.js → remediation-CMBIrnpN.js} +2 -2
- package/dist/{render-DrvXRCEn.js → render-DbGOVmhx.js} +47 -9
- package/dist/{result-DoamKFsp.js → result-BWcYXeRs.js} +37 -3
- package/dist/{run-DB34BSOZ.js → run-C2nKFcb-.js} +1 -1
- package/dist/{run-BMASMmwO.d.ts → run-CtcEUhbe.d.ts} +7 -0
- package/dist/{sarif-SR3_lLYd.js → sarif-B-UBcVu8.js} +3 -3
- package/dist/{schema-is6CGX2D.js → schema-DJSF4K05.js} +15 -1
- package/dist/statement/templates/es.en.md +125 -0
- package/dist/statement/templates/es.es.md +127 -0
- package/dist/statement/templates/fr.en.md +128 -0
- package/dist/statement/templates/fr.fr.md +131 -0
- package/dist/statement/templates/it.en.md +127 -0
- package/dist/statement/templates/it.it.md +130 -0
- package/dist/statement/templates/nl.en.md +125 -0
- package/dist/statement/templates/nl.nl.md +127 -0
- 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/package.json +6 -2
- package/dist/audit-CpXH2Mk8.js +0 -2
- package/dist/command-D8l_oYbV.js +0 -77
- package/dist/fingerprint-DRoneAjj.js +0 -20
- package/dist/jsdom-4IMzv0eE.js +0 -3
package/README.md
CHANGED
|
@@ -3,15 +3,17 @@
|
|
|
3
3
|
[](https://github.com/likeBloodMoon/eaa-kit/actions/workflows/ci.yml)
|
|
4
4
|
|
|
5
5
|
Build-time WCAG 2.2 AA auditor and EU accessibility statement generator for static sites,
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
in Austria
|
|
6
|
+
built for the freelancers and small agencies who have to comply with the European
|
|
7
|
+
Accessibility Act (in force since 28 June 2025) without an accessibility budget. It started
|
|
8
|
+
in the DACH region — the BFSG in Germany, the BaFG in Austria — and the statement now names
|
|
9
|
+
the statute and supervisory body of **seven countries**: Austria, Germany, Switzerland,
|
|
10
|
+
Spain, France, Italy and the Netherlands, each in its own language as well as English.
|
|
9
11
|
|
|
10
12
|
```bash
|
|
11
13
|
npx eaa-kit audit # WCAG 2.2 AA report; finds your build itself
|
|
12
14
|
npx eaa-kit diff a.json b.json # what a change made worse, and what it fixed
|
|
13
15
|
npx eaa-kit init # write an eaa.config.json
|
|
14
|
-
npx eaa-kit statement #
|
|
16
|
+
npx eaa-kit statement # accessibility statement, in one of seven countries
|
|
15
17
|
```
|
|
16
18
|
|
|
17
19
|
> **Not legal advice.** eaa-kit reports what an automated engine can and cannot determine
|
|
@@ -33,7 +35,9 @@ supported here either.
|
|
|
33
35
|
**Audits your build.** Globs the HTML out of `./dist`, parses it with jsdom and runs
|
|
34
36
|
axe-core against it. No Chromium download, fast enough for CI, and it never fetches
|
|
35
37
|
anything or executes your site's JavaScript. `--browser` swaps in real Chromium for the
|
|
36
|
-
rules that need layout and CSS
|
|
38
|
+
rules that need layout and CSS; `--fast` goes the other way and skips the rules the
|
|
39
|
+
browserless engine cannot decide anyway, rather than running them and discarding the
|
|
40
|
+
answer.
|
|
37
41
|
|
|
38
42
|
```bash
|
|
39
43
|
eaa-kit audit ./dist --fail-on serious
|
|
@@ -46,14 +50,21 @@ export, Nuxt, SvelteKit, anything behind a CMS — are audited running instead:
|
|
|
46
50
|
eaa-kit audit --url http://localhost:3000
|
|
47
51
|
```
|
|
48
52
|
|
|
49
|
-
**Writes the statement.** A Barrierefreiheitserklärung
|
|
50
|
-
|
|
51
|
-
|
|
53
|
+
**Writes the statement.** A Barrierefreiheitserklärung, a déclaration d'accessibilité, a
|
|
54
|
+
dichiarazione di accessibilità — whatever the country calls it — from one config file, as
|
|
55
|
+
Markdown or HTML, naming that country's statute and supervisory body, and optionally
|
|
56
|
+
listing the barriers a real audit found.
|
|
52
57
|
|
|
53
58
|
```bash
|
|
54
59
|
eaa-kit statement --output src/content/a11y.md
|
|
60
|
+
eaa-kit statement --country FR --lang fr # or ES, IT, NL, AT, DE, CH
|
|
55
61
|
```
|
|
56
62
|
|
|
63
|
+
Each country's statement is a document under its own law rather than a translation of
|
|
64
|
+
another's, so where a national regime asks for more than this — France's RGAA declaration,
|
|
65
|
+
Italy's filing with AgID — the text says so instead of letting a generated file look like
|
|
66
|
+
it settles the matter.
|
|
67
|
+
|
|
57
68
|
**Fails only on what is new.** The first run on a site that already exists finds
|
|
58
69
|
everything at once. A baseline records what is already wrong so the build fails on
|
|
59
70
|
regressions instead, without ever letting an accepted barrier look like a passing one.
|
|
@@ -96,6 +107,16 @@ fetch forty URLs, no longer produces a report that looks like a complete one.
|
|
|
96
107
|
SARIF for GitHub code scanning, and a self-contained HTML page for the client whose site
|
|
97
108
|
it is.
|
|
98
109
|
|
|
110
|
+
**Says it once.** The flags a project runs on every build belong in the project, not in the
|
|
111
|
+
build script that repeats them. `eaa.config` — the same file the statement reads — takes an
|
|
112
|
+
[`audit` block](docs/audit.md#defaults-from-eaaconfig) of defaults, and `baseline` reads the
|
|
113
|
+
keys that mean the same thing to it. A flag you type still wins, so a one-off `--browser`
|
|
114
|
+
check needs no edit to a committed file.
|
|
115
|
+
|
|
116
|
+
```jsonc
|
|
117
|
+
{ "audit": { "dir": "build", "failOn": "critical", "browser": true } }
|
|
118
|
+
```
|
|
119
|
+
|
|
99
120
|
**Runs in your build**: a [Vite plugin](docs/integrations.md#vite-plugin) covering SvelteKit
|
|
100
121
|
and Remix too, and integrations for
|
|
101
122
|
[Astro](docs/integrations.md#astro-integration),
|
|
@@ -117,7 +138,8 @@ eaa-kit audit --url http://localhost:8000 --sitemap /sitemap_index.xml
|
|
|
117
138
|
| | |
|
|
118
139
|
| --- | --- |
|
|
119
140
|
| [Auditing a build](docs/audit.md) | The `audit` command, both engines, exit codes, and what an automated run can and cannot tell you |
|
|
120
|
-
| [
|
|
141
|
+
| [Defaults from eaa.config](docs/audit.md#defaults-from-eaaconfig) | Writing the flags down once, and what still overrides them |
|
|
142
|
+
| [The statement command](docs/statement.md) | The config file, the seven countries, and filling a statement from audit results |
|
|
121
143
|
| [Baselines](docs/baseline.md) | Adopting the tool on a site that already has violations |
|
|
122
144
|
| [Comparing two runs](docs/reports.md#comparing-two-runs) | The `diff` command, and what it refuses to call fixed |
|
|
123
145
|
| [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 |
|
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-C2nKFcb-.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,5 @@
|
|
|
1
|
-
import { t as auditPage } from "../../jsdom-
|
|
1
|
+
import { t as auditPage } from "../../jsdom-B--cEH-G.js";
|
|
2
|
+
import { enableCompileCache } from "node:module";
|
|
2
3
|
import { parentPort, workerData } from "node:worker_threads";
|
|
3
4
|
//#region src/audit/runners/worker.ts
|
|
4
5
|
/**
|
|
@@ -17,6 +18,9 @@ import { parentPort, workerData } from "node:worker_threads";
|
|
|
17
18
|
* something catastrophic — the thread running out of memory — reaches the
|
|
18
19
|
* pool's error handler instead.
|
|
19
20
|
*/
|
|
21
|
+
try {
|
|
22
|
+
enableCompileCache();
|
|
23
|
+
} catch {}
|
|
20
24
|
const port = parentPort;
|
|
21
25
|
if (!port) throw new Error("eaa-kit audit worker was started outside a worker thread");
|
|
22
26
|
const options = workerData ?? {};
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { a as impactRank, i as impactLabel, r as countAtOrAbove } from "./impact-DZt2oBCP.js";
|
|
2
2
|
import { a as plural, n as count, t as collapse } from "./text-CKKpzkYM.js";
|
|
3
|
-
import { a as isShared, c as runCompleteness, i as groupIssues, n as blindRules, r as coverageParts, t as remediationFor } from "./remediation-
|
|
3
|
+
import { a as isShared, c as runCompleteness, i as groupIssues, n as blindRules, r as coverageParts, t as remediationFor } from "./remediation-CMBIrnpN.js";
|
|
4
4
|
import { r as componentPath } from "./component-C3GL1Mnu.js";
|
|
5
|
-
import { i as understandingUrl, n as coverageSummary, r as manualCheckFor, t as buildCoverage } from "./coverage-
|
|
6
|
-
import { a as
|
|
7
|
-
import { n as collectPages, r as emptyDirectoryHint, t as BuildDirectoryError } from "./collect-
|
|
5
|
+
import { i as understandingUrl, n as coverageSummary, r as manualCheckFor, t as buildCoverage } from "./coverage-B_Y6l-Ra.js";
|
|
6
|
+
import { a as emitDocument, c as runEngine, l as warn, o as fail, s as note, t as advise } from "./command-C3D7JWn6.js";
|
|
7
|
+
import { i as holdsHtml, n as collectPages, r as emptyDirectoryHint, t as BuildDirectoryError } from "./collect-Cu1q9NTB.js";
|
|
8
8
|
import path from "node:path";
|
|
9
9
|
import pc from "picocolors";
|
|
10
10
|
//#region src/audit/report/console.ts
|
|
@@ -187,13 +187,15 @@ function summary(audits, ctx) {
|
|
|
187
187
|
const elementCount = audits.reduce((total, audit) => total + audit.violations.reduce((sum, finding) => sum + finding.nodes.length, 0), 0);
|
|
188
188
|
const reviewCount = countRules(audits, "needs-review");
|
|
189
189
|
const pages = count(audits.length, "page");
|
|
190
|
+
const audited = audits.length - errored.length;
|
|
190
191
|
const lines = [line(ctx, "Summary", ctx.c.bold), ...completenessLines(ctx)];
|
|
191
|
-
if (ruleCount === 0)
|
|
192
|
-
|
|
192
|
+
if (ruleCount === 0 && audited === 0 && errored.length > 0) lines.push(line(ctx, " Nothing was audited: no page could be read.", ctx.c.red));
|
|
193
|
+
else if (ruleCount === 0) {
|
|
194
|
+
const clean = errored.length > 0 || ctx.completeness && !ctx.completeness.complete ? ` No violations across the ${count(audited, "page")} that were audited.` : ` No violations across ${pages}.`;
|
|
193
195
|
lines.push(line(ctx, clean, ctx.c.green));
|
|
194
196
|
} else {
|
|
195
197
|
lines.push(render(ctx, [{
|
|
196
|
-
text: ` ${count(ruleCount, "violation")} on ${withViolations.length} of ${
|
|
198
|
+
text: ` ${count(ruleCount, "violation")} on ${withViolations.length} of ${count(audited, "page")}`,
|
|
197
199
|
paint: ctx.c.red
|
|
198
200
|
}, {
|
|
199
201
|
text: ` (${count(elementCount, "element")})`,
|
|
@@ -272,7 +274,8 @@ function completenessLines(ctx) {
|
|
|
272
274
|
const lines = [];
|
|
273
275
|
if (completeness.unreachable.length > 0) {
|
|
274
276
|
const noun = plural(completeness.unreachable.length, "page");
|
|
275
|
-
|
|
277
|
+
const verb = completeness.unreachable.length === 1 ? "was" : "were";
|
|
278
|
+
lines.push(line(ctx, ` ${completeness.unreachable.length} ${noun} could not be reached, and ${verb} not audited`, ctx.c.yellow));
|
|
276
279
|
}
|
|
277
280
|
if (completeness.truncated) lines.push(line(ctx, " The run stopped at its page limit; the site has more", ctx.c.yellow));
|
|
278
281
|
if (lines.length > 0) lines.push(line(ctx, " This report describes what was audited, not the whole site.", ctx.c.dim));
|
|
@@ -484,6 +487,13 @@ async function resolvePages(directory, options = {}) {
|
|
|
484
487
|
return;
|
|
485
488
|
}
|
|
486
489
|
if (pages.length === 0) {
|
|
490
|
+
if ((options.include !== void 0 || options.exclude !== void 0) && await holdsHtml(directory)) {
|
|
491
|
+
warn(`No page in ${shown} matched the filters, so nothing was audited.`);
|
|
492
|
+
if (options.include !== void 0) note(` --include ${options.include.join(" ")}`);
|
|
493
|
+
if (options.exclude !== void 0) note(` --exclude ${options.exclude.join(" ")}`);
|
|
494
|
+
note(" Patterns are relative to the audited directory, with POSIX separators.");
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
487
497
|
warn(await emptyDirectoryHint(shown, cwd));
|
|
488
498
|
return;
|
|
489
499
|
}
|
|
@@ -495,6 +505,7 @@ async function resolvePages(directory, options = {}) {
|
|
|
495
505
|
}
|
|
496
506
|
return {
|
|
497
507
|
pages,
|
|
508
|
+
directory,
|
|
498
509
|
label: shown,
|
|
499
510
|
completeness: {
|
|
500
511
|
discovery: "directory",
|
|
@@ -511,7 +522,7 @@ async function resolvePages(directory, options = {}) {
|
|
|
511
522
|
* exit 2 — a run that reached no verdict, not a clean one.
|
|
512
523
|
*/
|
|
513
524
|
async function crawlPages(url, options) {
|
|
514
|
-
const { crawlSite, CrawlError, parseEntryUrl } = await import("./crawl-
|
|
525
|
+
const { crawlSite, CrawlError, parseEntryUrl } = await import("./crawl-BUWPgaGW.js");
|
|
515
526
|
let entry;
|
|
516
527
|
try {
|
|
517
528
|
entry = parseEntryUrl(url, options.allowRemote ?? false);
|
|
@@ -609,12 +620,13 @@ const OUTPUT_FORMATS = [
|
|
|
609
620
|
* piped somewhere without the chatter coming along.
|
|
610
621
|
*/
|
|
611
622
|
async function runAuditCommand(dir, options = {}) {
|
|
623
|
+
if (options.fast && options.browser) warn("--fast has no effect with --browser: a real browser can decide those rules.");
|
|
612
624
|
const resolved = await resolvePages(dir, options);
|
|
613
625
|
if (!resolved) return {
|
|
614
626
|
audits: [],
|
|
615
627
|
exitCode: 2
|
|
616
628
|
};
|
|
617
|
-
const { pages, origin, label, cleanup, completeness: collection } = resolved;
|
|
629
|
+
const { pages, origin, label, cleanup, directory, completeness: collection } = resolved;
|
|
618
630
|
try {
|
|
619
631
|
note(`Auditing ${count(pages.length, "page")} in ${label}${await describeEngine(pages, options)}…`);
|
|
620
632
|
const baseUrl = options.baseUrl ?? origin;
|
|
@@ -623,8 +635,9 @@ async function runAuditCommand(dir, options = {}) {
|
|
|
623
635
|
...baseUrl === void 0 ? {} : { baseUrl },
|
|
624
636
|
...options.timeoutMs === void 0 ? {} : { timeoutMs: options.timeoutMs },
|
|
625
637
|
...options.browser ? { browser: true } : {},
|
|
638
|
+
...options.fast && !options.browser ? { fast: true } : {},
|
|
626
639
|
...options.concurrency === void 0 ? {} : { concurrency: options.concurrency },
|
|
627
|
-
...
|
|
640
|
+
...directory === void 0 ? {} : { directory }
|
|
628
641
|
});
|
|
629
642
|
if (!audits) return {
|
|
630
643
|
audits: [],
|
|
@@ -666,7 +679,7 @@ async function runAuditCommand(dir, options = {}) {
|
|
|
666
679
|
* would be as wrong as silently passing.
|
|
667
680
|
*/
|
|
668
681
|
async function acceptBaseline(audits, options) {
|
|
669
|
-
const { applyBaseline, BaselineError, readBaseline } = await import("./baseline-
|
|
682
|
+
const { applyBaseline, BaselineError, readBaseline } = await import("./baseline-0u4df12v.js");
|
|
670
683
|
try {
|
|
671
684
|
const outcome = applyBaseline(audits, await readBaseline(options.baseline, options.cwd ?? process.cwd()));
|
|
672
685
|
if (outcome.accepted > 0) note(`Baseline accepted ${outcome.accepted} violating elements`);
|
|
@@ -693,9 +706,10 @@ async function acceptBaseline(audits, options) {
|
|
|
693
706
|
*/
|
|
694
707
|
async function describeEngine(pages, options) {
|
|
695
708
|
if (options.browser) return " with Chromium";
|
|
696
|
-
const { plannedWorkers } = await import("./pool-
|
|
709
|
+
const { plannedWorkers } = await import("./pool-BO25OIez.js");
|
|
697
710
|
const workers = options.concurrency ?? plannedWorkers(pages);
|
|
698
|
-
|
|
711
|
+
const threads = workers > 1 ? ` across ${workers} threads` : "";
|
|
712
|
+
return options.fast ? `${threads}, skipping what this engine cannot decide` : threads;
|
|
699
713
|
}
|
|
700
714
|
/**
|
|
701
715
|
* Emit the chosen format, to a file when --output is given and to stdout
|
|
@@ -710,7 +724,7 @@ async function emit(audits, dir, failOn, completeness, options) {
|
|
|
710
724
|
async function renderReport(audits, dir, failOn, completeness, format, toFile, options) {
|
|
711
725
|
switch (format) {
|
|
712
726
|
case "json": {
|
|
713
|
-
const { buildJsonReport, serialiseJsonReport } = await import("./json-
|
|
727
|
+
const { buildJsonReport, serialiseJsonReport } = await import("./json-D_Mnnft5.js");
|
|
714
728
|
return serialiseJsonReport(buildJsonReport(audits, {
|
|
715
729
|
directory: dir,
|
|
716
730
|
...options.url === void 0 ? {} : { sourceKind: "url" },
|
|
@@ -720,17 +734,17 @@ async function renderReport(audits, dir, failOn, completeness, format, toFile, o
|
|
|
720
734
|
}));
|
|
721
735
|
}
|
|
722
736
|
case "sarif": {
|
|
723
|
-
const { buildSarifReport, serialiseSarifReport } = await import("./sarif-
|
|
737
|
+
const { buildSarifReport, serialiseSarifReport } = await import("./sarif-B-UBcVu8.js");
|
|
724
738
|
return serialiseSarifReport(buildSarifReport(audits, {
|
|
725
739
|
directory: dir,
|
|
726
740
|
completeness
|
|
727
741
|
}));
|
|
728
742
|
}
|
|
729
743
|
case "html": {
|
|
730
|
-
const { buildHtmlReport } = await import("./html-
|
|
744
|
+
const { buildHtmlReport } = await import("./html-C4vktg0A.js");
|
|
731
745
|
const framework = await detectedFramework(options.cwd ?? process.cwd());
|
|
732
746
|
return buildHtmlReport(audits, {
|
|
733
|
-
...await sourceLookups(options.cwd ?? process.cwd()),
|
|
747
|
+
...await sourceLookups(options.cwd ?? process.cwd(), audits),
|
|
734
748
|
directory: dir,
|
|
735
749
|
failOn,
|
|
736
750
|
completeness,
|
|
@@ -741,7 +755,7 @@ async function renderReport(audits, dir, failOn, completeness, format, toFile, o
|
|
|
741
755
|
case "console": {
|
|
742
756
|
const framework = await detectedFramework(options.cwd ?? process.cwd());
|
|
743
757
|
return `${formatConsoleReport(audits, {
|
|
744
|
-
...await sourceLookups(options.cwd ?? process.cwd()),
|
|
758
|
+
...await sourceLookups(options.cwd ?? process.cwd(), audits),
|
|
745
759
|
dir,
|
|
746
760
|
failOn,
|
|
747
761
|
completeness,
|
|
@@ -765,15 +779,23 @@ async function detectedFramework(cwd) {
|
|
|
765
779
|
const { readPackageJson } = await import("./project-MFrXcw1M.js");
|
|
766
780
|
return (await detectFramework(cwd, await readPackageJson(cwd)))?.framework.id;
|
|
767
781
|
}
|
|
768
|
-
async function sourceLookups(cwd) {
|
|
782
|
+
async function sourceLookups(cwd, audits) {
|
|
769
783
|
const { buildRouteMap, sourceFor } = await import("./routes-CmdRUuOs.js");
|
|
770
|
-
const { buildComponentIndex, componentFor } = await import("./component-DKd3EHOg.js");
|
|
771
784
|
const routes = await buildRouteMap(cwd);
|
|
785
|
+
if (!hasElementsToAttribute(audits)) return {
|
|
786
|
+
sourceFor: (page) => sourceFor(routes, page),
|
|
787
|
+
componentFor: () => void 0
|
|
788
|
+
};
|
|
789
|
+
const { buildComponentIndex, componentFor } = await import("./component-DKd3EHOg.js");
|
|
772
790
|
const components = await buildComponentIndex(cwd);
|
|
773
791
|
return {
|
|
774
792
|
sourceFor: (page) => sourceFor(routes, page),
|
|
775
793
|
componentFor: (html) => componentFor(components, html)
|
|
776
794
|
};
|
|
777
795
|
}
|
|
796
|
+
/** Whether any element will be listed, and so looked up against the source. */
|
|
797
|
+
function hasElementsToAttribute(audits) {
|
|
798
|
+
return audits.some((audit) => audit.violations.length > 0 || (audit.accepted?.length ?? 0) > 0);
|
|
799
|
+
}
|
|
778
800
|
//#endregion
|
|
779
801
|
export { runAuditCommand as n, resolvePages as r, OUTPUT_FORMATS as t };
|
|
@@ -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-CuAS2lMk.js";
|
|
2
2
|
export { BaselineError, applyBaseline, readBaseline };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { t as elementFingerprint } from "./fingerprint-
|
|
3
|
-
import {
|
|
1
|
+
import { c as nullable, d as optional, h as string, l as number, m as safeParse, o as isoDate$1, t as array, u as object, y as withDefault } from "./schema-DJSF4K05.js";
|
|
2
|
+
import { t as elementFingerprint } from "./fingerprint-BjYV_0F7.js";
|
|
3
|
+
import { o as findingElements } from "./result-BWcYXeRs.js";
|
|
4
4
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
/** Default filename, used by the CLI when no path is given. */
|
|
@@ -109,7 +109,7 @@ function buildBaseline(audits, options = {}) {
|
|
|
109
109
|
...options.note ? { note: options.note } : {}
|
|
110
110
|
});
|
|
111
111
|
return {
|
|
112
|
-
schemaVersion:
|
|
112
|
+
schemaVersion: 2,
|
|
113
113
|
createdOn: acceptedOn,
|
|
114
114
|
entries: entries.sort(byEntry)
|
|
115
115
|
};
|
|
@@ -137,7 +137,8 @@ async function readBaseline(file, cwd = process.cwd()) {
|
|
|
137
137
|
const issues = result.error.issues.map((issue) => `${issue.path.join(".") || "document"}: ${issue.message}`).slice(0, 5);
|
|
138
138
|
throw new BaselineError(`${path.basename(target)} is not an eaa-kit baseline (${issues.join("; ")})`);
|
|
139
139
|
}
|
|
140
|
-
if (result.data.schemaVersion !==
|
|
140
|
+
if (result.data.schemaVersion !== 2) throw new BaselineError(`${path.basename(target)} has schemaVersion ${result.data.schemaVersion}; this version of eaa-kit reads 2.\n Record it again on the current build: eaa-kit baseline\n Read the new file before committing it: it lists what this run found, which is
|
|
141
|
+
not necessarily what the old one accepted.`);
|
|
141
142
|
return result.data;
|
|
142
143
|
}
|
|
143
144
|
async function writeBaseline(file, baseline, cwd = process.cwd()) {
|
package/dist/cli/index.js
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
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
2
|
import { a as impactRank, n as IMPACT_LEVELS, o as isImpactLevel, s as meetsThreshold, t as DEFAULT_FAIL_ON } from "../impact-DZt2oBCP.js";
|
|
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-yAR4wzez.js";
|
|
5
|
+
import { i as readAuditReport, o as StatementError, t as renderStatement } from "../render-DbGOVmhx.js";
|
|
6
6
|
import { n as count, t as collapse } from "../text-CKKpzkYM.js";
|
|
7
7
|
import { t as TOOL_VERSION } from "../version-B3v4rNoG.js";
|
|
8
|
-
import { t as elementFingerprint } from "../fingerprint-
|
|
9
|
-
import { i as buildBaseline, n as DEFAULT_BASELINE_FILE, s as writeBaseline, t as BaselineError } from "../baseline-
|
|
10
|
-
import { n as runAuditCommand, r as resolvePages, t as OUTPUT_FORMATS } from "../audit-
|
|
11
|
-
import { a as runEngine, i as
|
|
8
|
+
import { t as elementFingerprint } from "../fingerprint-BjYV_0F7.js";
|
|
9
|
+
import { i as buildBaseline, n as DEFAULT_BASELINE_FILE, s as writeBaseline, t as BaselineError } from "../baseline-CuAS2lMk.js";
|
|
10
|
+
import { n as runAuditCommand, r as resolvePages, t as OUTPUT_FORMATS } from "../audit-B282A3EA.js";
|
|
11
|
+
import { a as emitDocument, c as runEngine, i as baselineInvocation, l as warn, n as auditDefaults, o as fail, r as auditInvocation, s as note, t as advise } from "../command-C3D7JWn6.js";
|
|
12
|
+
import { enableCompileCache } from "node:module";
|
|
12
13
|
import { readFile } from "node:fs/promises";
|
|
13
14
|
import path from "node:path";
|
|
14
15
|
import { Command, InvalidArgumentError } from "commander";
|
|
@@ -36,7 +37,7 @@ async function runBaselineCommand(dir, options = {}) {
|
|
|
36
37
|
entries: 0,
|
|
37
38
|
exitCode: 2
|
|
38
39
|
};
|
|
39
|
-
const { pages, origin, label } = resolved;
|
|
40
|
+
const { pages, origin, label, directory } = resolved;
|
|
40
41
|
note(`Auditing ${count(pages.length, "page")} in ${label}…`);
|
|
41
42
|
const baseUrl = options.baseUrl ?? origin;
|
|
42
43
|
const audits = await runEngine(pages, {
|
|
@@ -45,7 +46,7 @@ async function runBaselineCommand(dir, options = {}) {
|
|
|
45
46
|
...options.timeoutMs === void 0 ? {} : { timeoutMs: options.timeoutMs },
|
|
46
47
|
...options.browser ? { browser: true } : {},
|
|
47
48
|
...options.concurrency === void 0 ? {} : { concurrency: options.concurrency },
|
|
48
|
-
...
|
|
49
|
+
...directory === void 0 ? {} : { directory }
|
|
49
50
|
});
|
|
50
51
|
if (!audits) return {
|
|
51
52
|
entries: 0,
|
|
@@ -222,7 +223,7 @@ async function readReport(file, cwd = process.cwd()) {
|
|
|
222
223
|
const first = result.error.issues[0];
|
|
223
224
|
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
|
}
|
|
225
|
-
if (result.data.schemaVersion !==
|
|
226
|
+
if (result.data.schemaVersion !== 2) throw new DiffError(`${file} is schemaVersion ${result.data.schemaVersion}; this version of eaa-kit reads 2.\n Write both reports with the same version of eaa-kit before comparing them.`);
|
|
226
227
|
return result.data;
|
|
227
228
|
}
|
|
228
229
|
//#endregion
|
|
@@ -396,6 +397,30 @@ function formatFor(output) {
|
|
|
396
397
|
//#endregion
|
|
397
398
|
//#region src/cli/index.ts
|
|
398
399
|
/**
|
|
400
|
+
* Reuse the compiled form of this tool and its dependencies between runs.
|
|
401
|
+
*
|
|
402
|
+
* Most of what a short audit costs is not the audit. jsdom alone is ~700 ms to
|
|
403
|
+
* import on a warm page cache, and V8 pays to parse and compile it from source
|
|
404
|
+
* on every single run — of a CLI somebody invokes from a build script, over and
|
|
405
|
+
* over, against code that has not changed since the last time. Node can cache
|
|
406
|
+
* the compiled bytecode instead, which is exactly the shape of this problem.
|
|
407
|
+
*
|
|
408
|
+
* Measured over ten pages: 2350 ms without, 2075 ms with. The saving is a fixed
|
|
409
|
+
* cost, so it counts for most on the small sites where fixed cost is most of
|
|
410
|
+
* the run.
|
|
411
|
+
*
|
|
412
|
+
* Best-effort by design. It is a cache: a read-only filesystem, a sandbox that
|
|
413
|
+
* forbids the directory, or a Node built without the feature all mean the run
|
|
414
|
+
* is a little slower, and none of them is a reason to fail an audit. Node's own
|
|
415
|
+
* NODE_DISABLE_COMPILE_CACHE is honoured by the call itself.
|
|
416
|
+
*/
|
|
417
|
+
function useCompileCache() {
|
|
418
|
+
try {
|
|
419
|
+
enableCompileCache();
|
|
420
|
+
} catch {}
|
|
421
|
+
}
|
|
422
|
+
useCompileCache();
|
|
423
|
+
/**
|
|
399
424
|
* A parser for a fixed set of words, listing them when the answer is not one.
|
|
400
425
|
*
|
|
401
426
|
* `normalise` is for `--country de`, where the accepted spelling differs from
|
|
@@ -431,16 +456,16 @@ const parseConcurrency = wholeNumber(1);
|
|
|
431
456
|
const program = new Command();
|
|
432
457
|
program.exitOverride();
|
|
433
458
|
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");
|
|
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
|
|
435
|
-
const {
|
|
436
|
-
const
|
|
437
|
-
|
|
438
|
-
...build === false ? { noBuild: true } : {}
|
|
439
|
-
});
|
|
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) => {
|
|
460
|
+
const defaults = await auditDefaults({ ...flags.config ? { config: flags.config } : {} });
|
|
461
|
+
const invocation = auditInvocation(dir, defaults, flags);
|
|
462
|
+
const { exitCode } = await runAuditCommand(invocation.dir, invocation.options);
|
|
440
463
|
process.exitCode = exitCode;
|
|
441
464
|
});
|
|
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
|
|
443
|
-
const
|
|
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) => {
|
|
466
|
+
const defaults = await auditDefaults({ ...flags.config ? { config: flags.config } : {} });
|
|
467
|
+
const invocation = baselineInvocation(dir, defaults, flags);
|
|
468
|
+
const { exitCode } = await runBaselineCommand(invocation.dir, invocation.options);
|
|
444
469
|
process.exitCode = exitCode;
|
|
445
470
|
});
|
|
446
471
|
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) => {
|
|
@@ -448,7 +473,7 @@ program.command("diff").description("Compare two JSON reports: what a change mad
|
|
|
448
473
|
process.exitCode = exitCode;
|
|
449
474
|
});
|
|
450
475
|
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-
|
|
476
|
+
const { runInitCommand } = await import("../init-CW7LfGT5.js");
|
|
452
477
|
const { exitCode } = await runInitCommand(flags);
|
|
453
478
|
process.exitCode = exitCode;
|
|
454
479
|
});
|
|
@@ -463,6 +488,12 @@ program.command("statement").description("Generate an EU accessibility statement
|
|
|
463
488
|
try {
|
|
464
489
|
await program.parseAsync(process.argv);
|
|
465
490
|
} catch (cause) {
|
|
491
|
+
if (cause instanceof ConfigError) {
|
|
492
|
+
fail(cause.message);
|
|
493
|
+
for (const issue of cause.issues) note(` ${issue}`);
|
|
494
|
+
process.exitCode = 2;
|
|
495
|
+
process.exit(2);
|
|
496
|
+
}
|
|
466
497
|
const error = cause;
|
|
467
498
|
if (typeof error.exitCode === "number") process.exitCode = error.exitCode === 0 ? 0 : 2;
|
|
468
499
|
else {
|
|
@@ -46,11 +46,32 @@ async function collectPages(dir, options = {}) {
|
|
|
46
46
|
const pages = [];
|
|
47
47
|
for (let i = 0; i < relativePaths.length; i += READ_CONCURRENCY) {
|
|
48
48
|
const batch = relativePaths.slice(i, i + READ_CONCURRENCY);
|
|
49
|
-
const read = await Promise.all(batch.map((relativePath) => readPage(root, relativePath, options.onUnreadable)));
|
|
49
|
+
const read = await Promise.all(batch.map((relativePath) => readPage(root, relativePath, options.onUnreadable, options.maxBytes ?? 33554432)));
|
|
50
50
|
for (const page of read) if (page !== void 0) pages.push(page);
|
|
51
51
|
}
|
|
52
52
|
return pages;
|
|
53
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* Whether the directory holds any HTML at all, ignoring include and exclude.
|
|
56
|
+
*
|
|
57
|
+
* Asked when a run collected nothing, to tell two different mistakes apart: a
|
|
58
|
+
* build directory with no pages in it, and a directory full of pages that the
|
|
59
|
+
* caller's own filters excluded. The advice for the first is wrong for the
|
|
60
|
+
* second — it names another directory to audit — so the question has to be
|
|
61
|
+
* answered before it is given.
|
|
62
|
+
*
|
|
63
|
+
* Globs rather than collecting: this only needs to know whether one file
|
|
64
|
+
* exists, and reading every page of a large build to answer that on a path that
|
|
65
|
+
* is about to print a warning and stop would be work for nothing.
|
|
66
|
+
*/
|
|
67
|
+
async function holdsHtml(dir) {
|
|
68
|
+
return (await glob([...DEFAULT_INCLUDE], {
|
|
69
|
+
cwd: path.resolve(dir),
|
|
70
|
+
ignore: [...DEFAULT_EXCLUDE],
|
|
71
|
+
onlyFiles: true,
|
|
72
|
+
dot: false
|
|
73
|
+
})).length > 0;
|
|
74
|
+
}
|
|
54
75
|
async function assertDirectory(root, original) {
|
|
55
76
|
let stats;
|
|
56
77
|
try {
|
|
@@ -61,9 +82,15 @@ async function assertDirectory(root, original) {
|
|
|
61
82
|
}
|
|
62
83
|
if (!stats.isDirectory()) throw new BuildDirectoryError(`Build path is not a directory: ${original}`, root);
|
|
63
84
|
}
|
|
64
|
-
async function readPage(root, relativePath, onUnreadable) {
|
|
85
|
+
async function readPage(root, relativePath, onUnreadable, maxBytes) {
|
|
65
86
|
const absolutePath = path.join(root, relativePath);
|
|
66
87
|
try {
|
|
88
|
+
const { size } = await stat(absolutePath);
|
|
89
|
+
if (size > maxBytes) {
|
|
90
|
+
if (onUnreadable === void 0) throw new Error(`${relativePath} is ${size} bytes, over the ${maxBytes} byte limit`);
|
|
91
|
+
onUnreadable(relativePath, `${megabytes(size)} MB, over the ${megabytes(maxBytes)} MB limit for one page`);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
67
94
|
return {
|
|
68
95
|
absolutePath,
|
|
69
96
|
relativePath,
|
|
@@ -130,5 +157,9 @@ function generic(head) {
|
|
|
130
157
|
function article(name) {
|
|
131
158
|
return /^[aeiou]/i.test(name) ? "an" : "a";
|
|
132
159
|
}
|
|
160
|
+
/** Sizes in the megabytes a person reads, not the bytes a computer counts. */
|
|
161
|
+
function megabytes(bytes) {
|
|
162
|
+
return (bytes / 1048576).toFixed(1).replace(/\.0$/, "");
|
|
163
|
+
}
|
|
133
164
|
//#endregion
|
|
134
|
-
export { stripBom as i, collectPages as n, emptyDirectoryHint as r, BuildDirectoryError as t };
|
|
165
|
+
export { stripBom as a, holdsHtml as i, collectPages as n, emptyDirectoryHint as r, BuildDirectoryError as t };
|