eaa-kit 0.6.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 +23 -8
- 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-e_uixhPG.js → audit-DRjAY2iY.js} +164 -75
- package/dist/{baseline-D2qWmUC6.js → baseline-BpE7DbDq.js} +1 -1
- package/dist/{baseline-BscHfFSW.js → baseline-rtnj3RHB.js} +1 -1
- package/dist/cache-D4dOIuOi.js +219 -0
- package/dist/{checklist-BtgkxMEa.js → checklist-DDd9jewi.js} +3 -3
- package/dist/cli/index.js +28 -12
- package/dist/{command-CZKp9Wi_.js → command-3r4fht5f.js} +6 -5
- package/dist/{coverage-BD2_CYns.js → coverage-cb5kPp48.js} +3 -285
- package/dist/coverage-n1X9CkiG.js +3 -0
- 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-Bmh7X_sM.js → html-BIxxF3FC.js} +6 -4
- package/dist/index.d.ts +19 -0
- package/dist/index.js +2 -2
- package/dist/{init-BjKYttxc.js → init-DrFfzjvm.js} +2 -2
- package/dist/jsdom-BUYuVJJm.js +3 -0
- package/dist/{jsdom-CQar6OQS.js → jsdom-CsW_xVcA.js} +11 -12
- package/dist/{json-PNg6BeXp.js → json-Bus6YhxB.js} +4 -3
- package/dist/{json-HJDgJp09.js → json-CrQmoTj6.js} +1 -1
- package/dist/{load-vZdV_fD1.js → load-BaWMa-G_.js} +6 -1
- 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-C-miJP2b.js → playwright-ukCr_5o3.js} +1 -1
- package/dist/{pool-dkexq5xo.js → pool-B4tEUa2S.js} +6 -2
- package/dist/{completeness-Dwq3CPB3.js → remediation-C0klJblu.js} +55 -42
- package/dist/{render-CtsqR0k-.js → render-C1sUZZI_.js} +41 -3
- package/dist/{result-DfUru6y_.js → result-B7_zVxAX.js} +1 -1
- package/dist/result-CRmjjoAA.js +2 -0
- package/dist/{run-YiqyQA2b.d.ts → run-BK2blxMI.d.ts} +8 -0
- package/dist/{run-D7hdVXLA.js → run-DrS0Komf.js} +6 -2
- package/dist/{sarif-BKvq_AUm.js → sarif-BNCE0PNT.js} +3 -2
- 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/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 → worksheet-BIxMtbUd.js} +3 -2
- package/package.json +3 -2
- package/dist/audit-DK2StT1_.js +0 -2
- package/dist/coverage-7bQqWNfN.js +0 -2
- package/dist/jsdom-DMvU2h-f.js +0 -3
- package/dist/load-Bc_xDGAh.js +0 -2
package/README.md
CHANGED
|
@@ -9,11 +9,19 @@ 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.
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
0.7.0 makes a run cost what it should. A page that has not changed byte for byte is not
|
|
13
|
+
audited again, and a run with nothing to re-audit never loads an engine at all: twenty
|
|
14
|
+
pages go from ~2,520 ms to ~230 ms, which is about what starting the process costs. A
|
|
15
|
+
reused result is never passed off as a fresh one — every report says how much was reused
|
|
16
|
+
and from when, and `diff` will not call a page fixed that this run did not look at. The
|
|
17
|
+
statement now also says how many of WCAG's 55 criteria a person checked by hand, and
|
|
18
|
+
`pnpm bench` prints the numbers in this paragraph for your own machine.
|
|
19
|
+
|
|
20
|
+
0.6.0 added the half no engine can do. Of the 55 WCAG 2.2 A and AA success criteria, 34
|
|
21
|
+
have no automated rule at all: `checklist` writes those down as a review somebody works
|
|
22
|
+
through, `audit --review` reads the answers back beside what the run measured, and
|
|
23
|
+
`statement` refuses to publish a claim of full conformance that either one contradicts.
|
|
24
|
+
Sites behind a login or a preview protection are auditable too.
|
|
17
25
|
|
|
18
26
|
```bash
|
|
19
27
|
npx eaa-kit audit # WCAG 2.2 AA report; finds your build itself
|
|
@@ -114,13 +122,20 @@ eaa-kit audit --review eaa-review.json --coverage
|
|
|
114
122
|
```
|
|
115
123
|
|
|
116
124
|
**Says how much of WCAG it could reach.** WCAG 2.2 has 55 success criteria at Levels A and
|
|
117
|
-
AA.
|
|
118
|
-
55 with what this run reached on each — never as a percentage,
|
|
119
|
-
be automated and a score would present that as a fact about
|
|
125
|
+
AA. This tool has rules for 21 of them, and 34 need a person. Every run says so, and
|
|
126
|
+
`--coverage` lists all 55 with what this run reached on each — never as a percentage,
|
|
127
|
+
because most of WCAG cannot be automated and a score would present that as a fact about
|
|
128
|
+
your site.
|
|
120
129
|
|
|
121
130
|
**Says what it did not measure.** A crawl that stopped at its page limit, or could not
|
|
122
131
|
fetch forty URLs, no longer produces a report that looks like a complete one.
|
|
123
132
|
|
|
133
|
+
**Audits only what changed.** A page whose markup is byte-identical to the last run's keeps
|
|
134
|
+
the result it already had, and a run with nothing to re-audit never loads an engine —
|
|
135
|
+
twenty pages in ~230 ms instead of ~2,520 ms, which is about what starting the process
|
|
136
|
+
costs. Reuse is counted apart from auditing in all four reports, and `diff` will not call a
|
|
137
|
+
page fixed that this run did not look at. `--no-cache` audits everything regardless.
|
|
138
|
+
|
|
124
139
|
**Reports in four shapes**: a console report for whoever ran it, JSON for other tools,
|
|
125
140
|
SARIF for GitHub code scanning, and a self-contained HTML page for the client whose site
|
|
126
141
|
it is.
|
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-DrS0Komf.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-CsW_xVcA.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,12 +1,13 @@
|
|
|
1
1
|
import { a as impactLabel, i as countAtOrAbove, r as byImpactThenRule } from "./impact-luGCnN8F.js";
|
|
2
2
|
import { n as count, o as plural, t as collapse } from "./text-BF1LHMiV.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { t as TOOL_VERSION } from "./version-B3v4rNoG.js";
|
|
4
|
+
import { n as DEFAULT_TAGS, u as runEngine } from "./result-B7_zVxAX.js";
|
|
5
|
+
import { a as emitDocument, c as runEngine$1, l as warn, o as fail, s as note, t as advise } from "./command-3r4fht5f.js";
|
|
5
6
|
import { l as reviewSentence } from "./review-CdMK2GpQ.js";
|
|
7
|
+
import { a as isShared, i as groupIssues, l as reusedPart, n as blindRules, o as issueTotals, r as coverageParts, t as remediationFor, u as runCompleteness } from "./remediation-C0klJblu.js";
|
|
6
8
|
import { r as componentPath } from "./component-ChisoFyY.js";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { n as buildSummary } from "./json-PNg6BeXp.js";
|
|
9
|
+
import { a as manualCheckFor, n as coverageSummary, o as understandingUrl, r as reviewSummary, t as buildCoverage } from "./coverage-cb5kPp48.js";
|
|
10
|
+
import { n as buildSummary } from "./json-Bus6YhxB.js";
|
|
10
11
|
import { i as holdsHtml, n as collectPages, r as emptyDirectoryHint, t as BuildDirectoryError } from "./collect-Cu1q9NTB.js";
|
|
11
12
|
import path from "node:path";
|
|
12
13
|
import pc from "picocolors";
|
|
@@ -77,7 +78,7 @@ function supportsUnicode() {
|
|
|
77
78
|
* Joins segments into one line, trimming the group to the terminal width.
|
|
78
79
|
* Colour codes are applied after trimming, so they never count towards it.
|
|
79
80
|
*/
|
|
80
|
-
function render(ctx, segments) {
|
|
81
|
+
function render$1(ctx, segments) {
|
|
81
82
|
let remaining = ctx.width;
|
|
82
83
|
const parts = [];
|
|
83
84
|
for (const segment of segments) {
|
|
@@ -90,14 +91,14 @@ function render(ctx, segments) {
|
|
|
90
91
|
}
|
|
91
92
|
/** One line from a single segment, which is what most of this report is. */
|
|
92
93
|
function line(ctx, text, paint) {
|
|
93
|
-
return render(ctx, paint === void 0 ? [{ text }] : [{
|
|
94
|
+
return render$1(ctx, paint === void 0 ? [{ text }] : [{
|
|
94
95
|
text,
|
|
95
96
|
paint
|
|
96
97
|
}]);
|
|
97
98
|
}
|
|
98
99
|
function headerLines(audits, ctx) {
|
|
99
100
|
const engineLabel = runEngine(audits) === "browser" ? "chromium" : "jsdom (browserless)";
|
|
100
|
-
return [render(ctx, [{
|
|
101
|
+
return [render$1(ctx, [{
|
|
101
102
|
text: "eaa-kit audit",
|
|
102
103
|
paint: ctx.c.bold
|
|
103
104
|
}, {
|
|
@@ -118,14 +119,14 @@ function legendLines(ctx) {
|
|
|
118
119
|
function pageSection(audit, ctx) {
|
|
119
120
|
const lines = [line(ctx, audit.relativePath, ctx.c.underline)];
|
|
120
121
|
if (audit.error) {
|
|
121
|
-
lines.push(render(ctx, [{
|
|
122
|
+
lines.push(render$1(ctx, [{
|
|
122
123
|
text: ` ${ctx.symbol("error")} not audited: `,
|
|
123
124
|
paint: ctx.c.red
|
|
124
125
|
}, { text: audit.error }]), "");
|
|
125
126
|
return lines;
|
|
126
127
|
}
|
|
127
128
|
for (const finding of [...audit.violations].sort(byImpactThenRule)) lines.push(...violationLines(finding, ctx));
|
|
128
|
-
for (const finding of audit.incomplete.filter((item) => item.reason === "needs-review")) lines.push(render(ctx, [
|
|
129
|
+
for (const finding of audit.incomplete.filter((item) => item.reason === "needs-review")) lines.push(render$1(ctx, [
|
|
129
130
|
{
|
|
130
131
|
text: ` ${ctx.symbol("review")} `,
|
|
131
132
|
paint: ctx.c.yellow
|
|
@@ -141,7 +142,7 @@ function pageSection(audit, ctx) {
|
|
|
141
142
|
]));
|
|
142
143
|
if (audit.violations.length === 0) {
|
|
143
144
|
const clean = (audit.accepted ?? []).length === 0;
|
|
144
|
-
lines.push(render(ctx, [{
|
|
145
|
+
lines.push(render$1(ctx, [{
|
|
145
146
|
text: ` ${ctx.symbol("clean")} `,
|
|
146
147
|
paint: clean ? ctx.c.green : ctx.c.dim
|
|
147
148
|
}, {
|
|
@@ -162,7 +163,7 @@ function coverageLine(audit, ctx) {
|
|
|
162
163
|
}
|
|
163
164
|
function violationLines(finding, ctx) {
|
|
164
165
|
const impact = impactLabel(finding.impact);
|
|
165
|
-
const lines = [render(ctx, [
|
|
166
|
+
const lines = [render$1(ctx, [
|
|
166
167
|
{
|
|
167
168
|
text: ` ${ctx.symbol("violation")} `,
|
|
168
169
|
paint: ctx.c.red
|
|
@@ -195,7 +196,7 @@ function summary(audits, ctx) {
|
|
|
195
196
|
const clean = totals.pagesNotAudited > 0 || ctx.completeness && !ctx.completeness.complete ? ` No violations across the ${count(audited, "page")} that were audited.` : ` No violations across ${pages}.`;
|
|
196
197
|
lines.push(line(ctx, clean, ctx.c.green));
|
|
197
198
|
} else {
|
|
198
|
-
lines.push(render(ctx, [{
|
|
199
|
+
lines.push(render$1(ctx, [{
|
|
199
200
|
text: ` ${count(totals.violations, "violation")} on ${totals.pagesWithViolations} of ${count(audited, "page")}`,
|
|
200
201
|
paint: ctx.c.red
|
|
201
202
|
}, {
|
|
@@ -239,7 +240,7 @@ function coverageSection(audits, ctx) {
|
|
|
239
240
|
for (const criterion of coverage.criteria) {
|
|
240
241
|
const paint = criterion.status === "evaluated" ? ctx.c.green : ctx.c.dim;
|
|
241
242
|
const note = criterion.browserWouldAnswer ? " (--browser would answer this)" : "";
|
|
242
|
-
lines.push(render(ctx, [
|
|
243
|
+
lines.push(render$1(ctx, [
|
|
243
244
|
{
|
|
244
245
|
text: ` ${criterion.number} `,
|
|
245
246
|
paint: ctx.c.bold
|
|
@@ -274,8 +275,10 @@ const STATUS_WORDS = {
|
|
|
274
275
|
*/
|
|
275
276
|
function completenessLines(ctx) {
|
|
276
277
|
const completeness = ctx.completeness;
|
|
277
|
-
if (completeness === void 0
|
|
278
|
-
const
|
|
278
|
+
if (completeness === void 0) return [];
|
|
279
|
+
const reused = reusedPart(completeness);
|
|
280
|
+
const lines = reused === void 0 ? [] : [line(ctx, ` ${reused}`, ctx.c.dim)];
|
|
281
|
+
if (completeness.complete) return lines;
|
|
279
282
|
if (completeness.unreachable.length > 0) {
|
|
280
283
|
const noun = plural(completeness.unreachable.length, "page");
|
|
281
284
|
const verb = completeness.unreachable.length === 1 ? "was" : "were";
|
|
@@ -309,7 +312,7 @@ function blindSection(audits, ctx) {
|
|
|
309
312
|
line(ctx, " They are never reported as passing.", ctx.c.dim)
|
|
310
313
|
];
|
|
311
314
|
for (const { ruleId, pages, finding } of blind) {
|
|
312
|
-
lines.push(render(ctx, [
|
|
315
|
+
lines.push(render$1(ctx, [
|
|
313
316
|
{ text: ` ${ctx.symbol("blind")} ` },
|
|
314
317
|
{ text: ruleId },
|
|
315
318
|
{
|
|
@@ -356,7 +359,7 @@ function issuesSection(audits, ctx) {
|
|
|
356
359
|
lines.push(line(ctx, intro, ctx.c.dim));
|
|
357
360
|
for (const issue of issues) {
|
|
358
361
|
lines.push("");
|
|
359
|
-
lines.push(render(ctx, [
|
|
362
|
+
lines.push(render$1(ctx, [
|
|
360
363
|
{ text: ` ${ctx.symbol("violation")} ` },
|
|
361
364
|
{
|
|
362
365
|
text: issue.ruleId,
|
|
@@ -397,7 +400,7 @@ function remediationLines(ruleId, html, ctx) {
|
|
|
397
400
|
const width = ctx.width - 6 - 2;
|
|
398
401
|
const lines = [...wrap(remediation.why, width).map((text) => line(ctx, `${indent}${text}`, ctx.c.dim)), ...wrap(`Fix: ${remediation.fix}`, width).map((text) => line(ctx, `${indent}${text}`))];
|
|
399
402
|
const example = html === void 0 ? void 0 : remediation.example?.(html);
|
|
400
|
-
if (example !== void 0) lines.push(render(ctx, [{
|
|
403
|
+
if (example !== void 0) lines.push(render$1(ctx, [{
|
|
401
404
|
text: `${indent}→ `,
|
|
402
405
|
paint: ctx.c.green
|
|
403
406
|
}, { text: collapse(example) }]));
|
|
@@ -408,14 +411,14 @@ function whereLines(element, ctx) {
|
|
|
408
411
|
const shown = element.pages.slice(0, ctx.maxNodes);
|
|
409
412
|
const rest = element.pages.length - shown.length;
|
|
410
413
|
const component = ctx.componentFor(element.html);
|
|
411
|
-
const lines = component === void 0 ? [] : [render(ctx, [{
|
|
414
|
+
const lines = component === void 0 ? [] : [render$1(ctx, [{
|
|
412
415
|
text: " written in ",
|
|
413
416
|
paint: ctx.c.dim
|
|
414
417
|
}, { text: componentPath(component) }])];
|
|
415
418
|
lines.push(line(ctx, ` on ${count(element.pages.length, "page")}:`, ctx.c.dim));
|
|
416
419
|
for (const page of shown) {
|
|
417
420
|
const source = ctx.sourceFor(page);
|
|
418
|
-
lines.push(render(ctx, [{
|
|
421
|
+
lines.push(render$1(ctx, [{
|
|
419
422
|
text: ` ${page}`,
|
|
420
423
|
paint: ctx.c.dim
|
|
421
424
|
}, ...source === void 0 ? [] : [{
|
|
@@ -496,12 +499,7 @@ async function resolvePages(directory, options = {}) {
|
|
|
496
499
|
warn(await emptyDirectoryHint(shown, cwd));
|
|
497
500
|
return;
|
|
498
501
|
}
|
|
499
|
-
|
|
500
|
-
const verb = unreachable.length === 1 ? "file was" : "files were";
|
|
501
|
-
warn(`${unreachable.length} ${verb} not readable, and so not audited:`);
|
|
502
|
-
for (const file of unreachable.slice(0, 10)) note(` ${file.location} — ${file.reason}`);
|
|
503
|
-
if (unreachable.length > 10) note(` …and ${unreachable.length - 10} more`);
|
|
504
|
-
}
|
|
502
|
+
warnUnmeasured(unreachable, "file", "readable");
|
|
505
503
|
return {
|
|
506
504
|
pages,
|
|
507
505
|
directory,
|
|
@@ -515,6 +513,22 @@ async function resolvePages(directory, options = {}) {
|
|
|
515
513
|
};
|
|
516
514
|
}
|
|
517
515
|
/**
|
|
516
|
+
* Name what was missed, up to ten of them, and count the rest.
|
|
517
|
+
*
|
|
518
|
+
* Both collectors need this and neither may skip it: a file that could not be
|
|
519
|
+
* read and a URL that could not be fetched are pages with no verdict, and a run
|
|
520
|
+
* that mentioned neither would report the rest of the site as though it were
|
|
521
|
+
* the site. Ten because the point is to make the problem recognisable, and two
|
|
522
|
+
* hundred failures usually have the one reason the first few show — the count
|
|
523
|
+
* that follows is what says the list was cut.
|
|
524
|
+
*/
|
|
525
|
+
function warnUnmeasured(missed, noun, reached) {
|
|
526
|
+
if (missed.length === 0) return;
|
|
527
|
+
warn(`${count(missed.length, noun)} ${missed.length === 1 ? "was" : "were"} not ${reached}, and so not audited:`);
|
|
528
|
+
for (const item of missed.slice(0, 10)) note(` ${item.location} — ${item.reason}`);
|
|
529
|
+
if (missed.length > 10) note(` …and ${missed.length - 10} more`);
|
|
530
|
+
}
|
|
531
|
+
/**
|
|
518
532
|
* Fetch the pages of a running site, reporting what happened on the way.
|
|
519
533
|
*
|
|
520
534
|
* Returns undefined when the crawl could not start, which the caller turns into
|
|
@@ -549,12 +563,11 @@ async function crawlPages(url, options) {
|
|
|
549
563
|
}
|
|
550
564
|
const found = result.discovery === "sitemap" ? "sitemap.xml and links" : "links";
|
|
551
565
|
note(`Found ${count(result.pages.length, "page")} from ${found}`);
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
}
|
|
566
|
+
const failed = result.failures.map((failure) => ({
|
|
567
|
+
location: failure.url,
|
|
568
|
+
reason: failure.reason
|
|
569
|
+
}));
|
|
570
|
+
warnUnmeasured(failed, "URL", "fetched");
|
|
558
571
|
if (result.truncated) warn(`Stopped at ${count(result.pages.length, "page")}; the site has more. Raise --max-pages to go further.`);
|
|
559
572
|
const collapsed = collapsedOnto(result);
|
|
560
573
|
if (collapsed.length > 0) {
|
|
@@ -569,10 +582,7 @@ async function crawlPages(url, options) {
|
|
|
569
582
|
completeness: {
|
|
570
583
|
discovery: result.discovery,
|
|
571
584
|
collected: result.pages.length,
|
|
572
|
-
unreachable: [...
|
|
573
|
-
location: failure.url,
|
|
574
|
-
reason: failure.reason
|
|
575
|
-
})), ...collapsed.map((redirect) => ({
|
|
585
|
+
unreachable: [...failed, ...collapsed.map((redirect) => ({
|
|
576
586
|
location: redirect.requested,
|
|
577
587
|
reason: `answered at ${redirect.landedOn} instead, so this page was not audited`
|
|
578
588
|
}))],
|
|
@@ -639,9 +649,11 @@ async function runAuditCommand(dir, options = {}) {
|
|
|
639
649
|
const { pages, origin, label, cleanup, directory, completeness: collection } = resolved;
|
|
640
650
|
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.");
|
|
641
651
|
try {
|
|
642
|
-
note(`Auditing ${count(pages.length, "page")} in ${label}${await describeEngine(pages, options)}…`);
|
|
643
652
|
const baseUrl = options.baseUrl ?? origin;
|
|
644
|
-
|
|
653
|
+
const { hits, misses, cache } = await splitByCache(pages, baseUrl, options);
|
|
654
|
+
if (misses.length === 0) note(`Nothing changed in ${label}: reusing ${count(hits.length, "page")} from the cache.`);
|
|
655
|
+
else note(`Auditing ${count(misses.length, "page")} in ${label}${await describeEngine(misses, options)}${hits.length === 0 ? "" : `, reusing ${count(hits.length, "unchanged page")}`}…`);
|
|
656
|
+
const fresh = misses.length === 0 ? [] : await runEngine$1(misses, {
|
|
645
657
|
cwd: options.cwd ?? process.cwd(),
|
|
646
658
|
...options.headers === void 0 ? {} : { headers: options.headers },
|
|
647
659
|
...baseUrl === void 0 ? {} : { baseUrl },
|
|
@@ -651,10 +663,16 @@ async function runAuditCommand(dir, options = {}) {
|
|
|
651
663
|
...options.concurrency === void 0 ? {} : { concurrency: options.concurrency },
|
|
652
664
|
...directory === void 0 ? {} : { directory }
|
|
653
665
|
});
|
|
654
|
-
if (!
|
|
666
|
+
if (!fresh) return {
|
|
655
667
|
audits: [],
|
|
656
668
|
exitCode: 2
|
|
657
669
|
};
|
|
670
|
+
for (const [index, page] of misses.entries()) {
|
|
671
|
+
const audit = fresh[index];
|
|
672
|
+
if (audit) cache?.put(page, audit);
|
|
673
|
+
}
|
|
674
|
+
await cache?.flush();
|
|
675
|
+
let audits = inCollectedOrder(pages, [...hits, ...fresh]);
|
|
658
676
|
const failOn = options.failOn ?? "serious";
|
|
659
677
|
if (options.baseline) {
|
|
660
678
|
const applied = await acceptBaseline(audits, options);
|
|
@@ -688,6 +706,67 @@ async function runAuditCommand(dir, options = {}) {
|
|
|
688
706
|
}
|
|
689
707
|
}
|
|
690
708
|
/**
|
|
709
|
+
* Which pages already have a result, and which have to be audited.
|
|
710
|
+
*
|
|
711
|
+
* Runs before the engine is imported, which is the point: a build where nothing
|
|
712
|
+
* changed produces no misses, and a run with no misses never loads jsdom. The
|
|
713
|
+
* cache itself is opened here rather than earlier so that a run given
|
|
714
|
+
* `--no-cache` does not read one at all.
|
|
715
|
+
*/
|
|
716
|
+
async function splitByCache(pages, baseUrl, options) {
|
|
717
|
+
if (options.noCache) return {
|
|
718
|
+
hits: [],
|
|
719
|
+
misses: [...pages],
|
|
720
|
+
cache: void 0
|
|
721
|
+
};
|
|
722
|
+
const { fromEntry, openCache } = await import("./cache-D4dOIuOi.js");
|
|
723
|
+
const { pageUrl } = await import("./result-CRmjjoAA.js");
|
|
724
|
+
const axe = (await import("axe-core")).default;
|
|
725
|
+
const engine = options.browser ? "browser" : "jsdom";
|
|
726
|
+
const cache = await openCache({
|
|
727
|
+
toolVersion: TOOL_VERSION,
|
|
728
|
+
axeVersion: axe.version,
|
|
729
|
+
tags: DEFAULT_TAGS,
|
|
730
|
+
engine,
|
|
731
|
+
fast: options.fast === true && options.browser !== true,
|
|
732
|
+
...baseUrl === void 0 ? {} : { baseUrl },
|
|
733
|
+
...options.timeoutMs === void 0 ? {} : { timeoutMs: options.timeoutMs },
|
|
734
|
+
...options.headers === void 0 ? {} : { headers: options.headers }
|
|
735
|
+
}, { ...options.cwd === void 0 ? {} : { cwd: options.cwd } });
|
|
736
|
+
const hits = [];
|
|
737
|
+
const misses = [];
|
|
738
|
+
for (const page of pages) {
|
|
739
|
+
const entry = cache.get(page);
|
|
740
|
+
if (entry === void 0) misses.push(page);
|
|
741
|
+
else hits.push(fromEntry(entry, page, {
|
|
742
|
+
url: pageUrl(page, baseUrl),
|
|
743
|
+
engine
|
|
744
|
+
}));
|
|
745
|
+
}
|
|
746
|
+
return {
|
|
747
|
+
hits,
|
|
748
|
+
misses,
|
|
749
|
+
cache
|
|
750
|
+
};
|
|
751
|
+
}
|
|
752
|
+
/**
|
|
753
|
+
* The audits back in the order their pages were collected in.
|
|
754
|
+
*
|
|
755
|
+
* Collection sorts by path so two runs of one build produce the same report,
|
|
756
|
+
* and splitting the pages into hits and misses would otherwise undo that: the
|
|
757
|
+
* same site would report its pages in a different order depending on which of
|
|
758
|
+
* them somebody had edited since yesterday.
|
|
759
|
+
*/
|
|
760
|
+
function inCollectedOrder(pages, audits) {
|
|
761
|
+
const byPath = new Map(audits.map((audit) => [audit.relativePath, audit]));
|
|
762
|
+
const ordered = [];
|
|
763
|
+
for (const page of pages) {
|
|
764
|
+
const audit = byPath.get(page.relativePath);
|
|
765
|
+
if (audit) ordered.push(audit);
|
|
766
|
+
}
|
|
767
|
+
return ordered;
|
|
768
|
+
}
|
|
769
|
+
/**
|
|
691
770
|
* Move the violations the baseline accounts for out of the failing set.
|
|
692
771
|
*
|
|
693
772
|
* Returns undefined when the baseline could not be read, which the caller
|
|
@@ -696,7 +775,7 @@ async function runAuditCommand(dir, options = {}) {
|
|
|
696
775
|
* would be as wrong as silently passing.
|
|
697
776
|
*/
|
|
698
777
|
async function acceptBaseline(audits, options) {
|
|
699
|
-
const { applyBaseline, BaselineError, readBaseline } = await import("./baseline-
|
|
778
|
+
const { applyBaseline, BaselineError, readBaseline } = await import("./baseline-rtnj3RHB.js");
|
|
700
779
|
try {
|
|
701
780
|
const outcome = applyBaseline(audits, await readBaseline(options.baseline, options.cwd ?? process.cwd()));
|
|
702
781
|
if (outcome.accepted > 0) note(`Baseline accepted ${outcome.accepted} violating elements`);
|
|
@@ -749,25 +828,29 @@ async function loadReview(options) {
|
|
|
749
828
|
*/
|
|
750
829
|
async function describeEngine(pages, options) {
|
|
751
830
|
if (options.browser) return " with Chromium";
|
|
752
|
-
const { plannedWorkers } = await import("./pool-
|
|
831
|
+
const { plannedWorkers } = await import("./pool-B4tEUa2S.js");
|
|
753
832
|
const workers = options.concurrency ?? plannedWorkers(pages);
|
|
754
833
|
const threads = workers > 1 ? ` across ${workers} threads` : "";
|
|
755
834
|
return options.fast ? `${threads}, skipping what this engine cannot decide` : threads;
|
|
756
835
|
}
|
|
757
836
|
/**
|
|
758
|
-
*
|
|
759
|
-
* otherwise. Colour is dropped when writing to a file, since
|
|
760
|
-
* saved report are noise.
|
|
837
|
+
* Render the chosen format and put it where it was asked for: a file under
|
|
838
|
+
* --output, stdout otherwise. Colour is dropped when writing to a file, since
|
|
839
|
+
* escape codes in a saved report are noise.
|
|
840
|
+
*
|
|
841
|
+
* One function rather than a renderer and a writer, because the renderer needed
|
|
842
|
+
* to know where the document was going anyway — which is the whole of what the
|
|
843
|
+
* split was carrying between them.
|
|
761
844
|
*/
|
|
762
845
|
async function emit(audits, dir, failOn, completeness, options, review) {
|
|
763
|
-
const body = await
|
|
846
|
+
const body = await render(audits, dir, failOn, completeness, typeof options.output === "string", options, review);
|
|
764
847
|
await emitDocument(body, options.output, options.cwd ?? process.cwd());
|
|
765
848
|
if (options.output !== void 0) note(`Report written to ${options.output}`);
|
|
766
849
|
}
|
|
767
|
-
async function
|
|
768
|
-
switch (format) {
|
|
850
|
+
async function render(audits, dir, failOn, completeness, toFile, options, review) {
|
|
851
|
+
switch (options.format ?? "console") {
|
|
769
852
|
case "json": {
|
|
770
|
-
const { buildJsonReport, serialiseJsonReport } = await import("./json-
|
|
853
|
+
const { buildJsonReport, serialiseJsonReport } = await import("./json-CrQmoTj6.js");
|
|
771
854
|
return serialiseJsonReport(buildJsonReport(audits, {
|
|
772
855
|
directory: dir,
|
|
773
856
|
...options.url === void 0 ? {} : { sourceKind: "url" },
|
|
@@ -778,7 +861,7 @@ async function renderReport(audits, dir, failOn, completeness, format, toFile, o
|
|
|
778
861
|
}));
|
|
779
862
|
}
|
|
780
863
|
case "sarif": {
|
|
781
|
-
const { buildSarifReport, serialiseSarifReport } = await import("./sarif-
|
|
864
|
+
const { buildSarifReport, serialiseSarifReport } = await import("./sarif-BNCE0PNT.js");
|
|
782
865
|
return serialiseSarifReport(buildSarifReport(audits, {
|
|
783
866
|
directory: dir,
|
|
784
867
|
completeness,
|
|
@@ -786,46 +869,52 @@ async function renderReport(audits, dir, failOn, completeness, format, toFile, o
|
|
|
786
869
|
}));
|
|
787
870
|
}
|
|
788
871
|
case "html": {
|
|
789
|
-
const { buildHtmlReport } = await import("./html-
|
|
790
|
-
const framework = await detectedFramework(options.cwd ?? process.cwd());
|
|
872
|
+
const { buildHtmlReport } = await import("./html-BIxxF3FC.js");
|
|
791
873
|
return buildHtmlReport(audits, {
|
|
792
|
-
...await
|
|
874
|
+
...await fromTheProject(audits, options),
|
|
793
875
|
directory: dir,
|
|
794
876
|
failOn,
|
|
795
877
|
completeness,
|
|
796
878
|
...review === void 0 ? {} : { review },
|
|
797
|
-
...framework === void 0 ? {} : { framework },
|
|
798
879
|
...options.baseUrl ? { baseUrl: options.baseUrl } : {}
|
|
799
880
|
});
|
|
800
881
|
}
|
|
801
|
-
case "console": {
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
...framework === void 0 ? {} : { framework },
|
|
813
|
-
...toFile ? { color: false } : {}
|
|
814
|
-
})}\n`;
|
|
815
|
-
}
|
|
882
|
+
case "console": return `${formatConsoleReport(audits, {
|
|
883
|
+
...await fromTheProject(audits, options),
|
|
884
|
+
dir,
|
|
885
|
+
failOn,
|
|
886
|
+
completeness,
|
|
887
|
+
...review === void 0 ? {} : { review },
|
|
888
|
+
...options.perPage ? { perPage: true } : {},
|
|
889
|
+
...options.manual ? { manual: true } : {},
|
|
890
|
+
...options.coverage ? { coverage: true } : {},
|
|
891
|
+
...toFile ? { color: false } : {}
|
|
892
|
+
})}\n`;
|
|
816
893
|
}
|
|
817
894
|
}
|
|
818
895
|
/**
|
|
819
|
-
*
|
|
820
|
-
*
|
|
821
|
-
*
|
|
896
|
+
* What the two human-readable reports both take from the project on disk: where
|
|
897
|
+
* each page and each failing element was written, and what built the site.
|
|
898
|
+
*
|
|
899
|
+
* Only those two ask. JSON and SARIF are read by other programs, which have the
|
|
900
|
+
* project in front of them already, and building the component index for one of
|
|
901
|
+
* them would be several hundred milliseconds spent on nobody's behalf.
|
|
822
902
|
*/
|
|
823
|
-
|
|
824
|
-
|
|
903
|
+
async function fromTheProject(audits, options) {
|
|
904
|
+
const cwd = options.cwd ?? process.cwd();
|
|
825
905
|
const { detectFramework } = await import("./frameworks-etFg_O8K.js");
|
|
826
906
|
const { readPackageJson } = await import("./project-MFrXcw1M.js");
|
|
827
|
-
|
|
907
|
+
const detected = await detectFramework(cwd, await readPackageJson(cwd));
|
|
908
|
+
return {
|
|
909
|
+
...await sourceLookups(cwd, audits),
|
|
910
|
+
...detected === void 0 ? {} : { framework: detected.framework.id }
|
|
911
|
+
};
|
|
828
912
|
}
|
|
913
|
+
/**
|
|
914
|
+
* Where a page and a failing element were written, for the two reports that say
|
|
915
|
+
* so. Best-effort: a project using no convention this recognises gets the
|
|
916
|
+
* report it always got, with no source named.
|
|
917
|
+
*/
|
|
829
918
|
async function sourceLookups(cwd, audits) {
|
|
830
919
|
const { buildRouteMap, sourceFor } = await import("./routes-CmdRUuOs.js");
|
|
831
920
|
const routes = await buildRouteMap(cwd);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
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
2
|
import { a as isoDate$1 } from "./text-BF1LHMiV.js";
|
|
3
3
|
import { t as elementFingerprint } from "./fingerprint-BjYV_0F7.js";
|
|
4
|
-
import {
|
|
4
|
+
import { s as findingElements } from "./result-B7_zVxAX.js";
|
|
5
5
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
/** Default filename, used by the CLI when no path is given. */
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { o as readBaseline, r as applyBaseline, t as BaselineError } from "./baseline-
|
|
1
|
+
import { o as readBaseline, r as applyBaseline, t as BaselineError } from "./baseline-BpE7DbDq.js";
|
|
2
2
|
export { BaselineError, applyBaseline, readBaseline };
|