eaa-kit 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -5
- package/dist/astro/index.d.ts +1 -1
- package/dist/astro/index.js +1 -1
- package/dist/audit/runners/worker.js +1 -1
- package/dist/audit-Bwm1xBdJ.js +2 -0
- package/dist/{audit-B282A3EA.js → audit-DRjAY2iY.js} +243 -107
- package/dist/{baseline-CuAS2lMk.js → baseline-BpE7DbDq.js} +36 -9
- package/dist/baseline-rtnj3RHB.js +2 -0
- package/dist/cache-D4dOIuOi.js +219 -0
- package/dist/checklist-DDd9jewi.js +44 -0
- package/dist/cli/index.js +130 -17
- package/dist/{command-C3D7JWn6.js → command-3r4fht5f.js} +102 -10
- package/dist/{component-C3GL1Mnu.js → component-ChisoFyY.js} +1 -1
- package/dist/{component-DKd3EHOg.js → component-DcCn3BJS.js} +1 -1
- package/dist/{coverage-B_Y6l-Ra.js → coverage-cb5kPp48.js} +45 -290
- package/dist/coverage-n1X9CkiG.js +3 -0
- package/dist/{crawl-BUWPgaGW.js → crawl-BHkJmUkA.js} +42 -8
- package/dist/criteria-CnPJKGeb.js +286 -0
- package/dist/eleventy/index.d.ts +1 -1
- package/dist/eleventy/index.js +1 -1
- package/dist/{html-C4vktg0A.js → html-BIxxF3FC.js} +54 -30
- package/dist/{impact-DZt2oBCP.js → impact-luGCnN8F.js} +11 -1
- package/dist/index.d.ts +87 -1
- package/dist/index.js +3 -3
- package/dist/{init-CW7LfGT5.js → init-DrFfzjvm.js} +3 -3
- package/dist/jsdom-BUYuVJJm.js +3 -0
- package/dist/{jsdom-B--cEH-G.js → jsdom-CsW_xVcA.js} +11 -12
- package/dist/{json-DROX33kh.js → json-Bus6YhxB.js} +13 -17
- package/dist/{json-D_Mnnft5.js → json-CrQmoTj6.js} +1 -1
- package/dist/{load-yAR4wzez.js → load-BaWMa-G_.js} +11 -2
- package/dist/load-n62iXDq_.js +2 -0
- package/dist/nuxt/index.d.ts +1 -1
- package/dist/nuxt/index.js +1 -1
- package/dist/{playwright-BojtYVUa.js → playwright-ukCr_5o3.js} +3 -2
- package/dist/{pool-BO25OIez.js → pool-B4tEUa2S.js} +6 -2
- package/dist/{remediation-CMBIrnpN.js → remediation-C0klJblu.js} +35 -4
- package/dist/{render-DbGOVmhx.js → render-C1sUZZI_.js} +145 -6
- package/dist/{result-BWcYXeRs.js → result-B7_zVxAX.js} +24 -1
- package/dist/result-CRmjjoAA.js +2 -0
- package/dist/review-CdMK2GpQ.js +161 -0
- package/dist/review-DIbOTmOM.js +2 -0
- package/dist/{run-CtcEUhbe.d.ts → run-BK2blxMI.d.ts} +18 -0
- package/dist/{run-C2nKFcb-.js → run-DrS0Komf.js} +6 -2
- package/dist/{sarif-B-UBcVu8.js → sarif-BNCE0PNT.js} +24 -12
- package/dist/statement/templates/at.de.md +14 -0
- package/dist/statement/templates/at.en.md +14 -0
- package/dist/statement/templates/ch.de.md +14 -0
- package/dist/statement/templates/ch.en.md +14 -0
- package/dist/statement/templates/de.de.md +14 -0
- package/dist/statement/templates/de.en.md +14 -0
- package/dist/statement/templates/es.en.md +14 -0
- package/dist/statement/templates/es.es.md +15 -0
- package/dist/statement/templates/fr.en.md +14 -0
- package/dist/statement/templates/fr.fr.md +15 -0
- package/dist/statement/templates/it.en.md +14 -0
- package/dist/statement/templates/it.it.md +15 -0
- package/dist/statement/templates/nl.en.md +14 -0
- package/dist/statement/templates/nl.nl.md +15 -0
- package/dist/{text-CKKpzkYM.js → text-BF1LHMiV.js} +22 -2
- package/dist/vite/index.d.ts +1 -1
- package/dist/vite/index.js +1 -1
- package/dist/webpack/index.d.ts +1 -1
- package/dist/webpack/index.js +1 -1
- package/dist/worksheet-BIxMtbUd.js +64 -0
- package/package.json +3 -2
- package/dist/audit-DyHPmGeD.js +0 -2
- package/dist/baseline-0u4df12v.js +0 -2
- package/dist/jsdom-22Bkt65v.js +0 -3
- package/dist/load-CFq2VQtT.js +0 -2
|
@@ -1,9 +1,13 @@
|
|
|
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 { 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";
|
|
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";
|
|
8
|
+
import { r as componentPath } from "./component-ChisoFyY.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";
|
|
7
11
|
import { i as holdsHtml, n as collectPages, r as emptyDirectoryHint, t as BuildDirectoryError } from "./collect-Cu1q9NTB.js";
|
|
8
12
|
import path from "node:path";
|
|
9
13
|
import pc from "picocolors";
|
|
@@ -51,6 +55,7 @@ function context(options) {
|
|
|
51
55
|
manual: options.manual ?? false,
|
|
52
56
|
coverage: options.coverage ?? false,
|
|
53
57
|
framework: options.framework,
|
|
58
|
+
review: options.review,
|
|
54
59
|
completeness: options.completeness,
|
|
55
60
|
c,
|
|
56
61
|
symbol: (kind) => {
|
|
@@ -73,7 +78,7 @@ function supportsUnicode() {
|
|
|
73
78
|
* Joins segments into one line, trimming the group to the terminal width.
|
|
74
79
|
* Colour codes are applied after trimming, so they never count towards it.
|
|
75
80
|
*/
|
|
76
|
-
function render(ctx, segments) {
|
|
81
|
+
function render$1(ctx, segments) {
|
|
77
82
|
let remaining = ctx.width;
|
|
78
83
|
const parts = [];
|
|
79
84
|
for (const segment of segments) {
|
|
@@ -86,14 +91,14 @@ function render(ctx, segments) {
|
|
|
86
91
|
}
|
|
87
92
|
/** One line from a single segment, which is what most of this report is. */
|
|
88
93
|
function line(ctx, text, paint) {
|
|
89
|
-
return render(ctx, paint === void 0 ? [{ text }] : [{
|
|
94
|
+
return render$1(ctx, paint === void 0 ? [{ text }] : [{
|
|
90
95
|
text,
|
|
91
96
|
paint
|
|
92
97
|
}]);
|
|
93
98
|
}
|
|
94
99
|
function headerLines(audits, ctx) {
|
|
95
|
-
const engineLabel = (audits
|
|
96
|
-
return [render(ctx, [{
|
|
100
|
+
const engineLabel = runEngine(audits) === "browser" ? "chromium" : "jsdom (browserless)";
|
|
101
|
+
return [render$1(ctx, [{
|
|
97
102
|
text: "eaa-kit audit",
|
|
98
103
|
paint: ctx.c.bold
|
|
99
104
|
}, {
|
|
@@ -114,14 +119,14 @@ function legendLines(ctx) {
|
|
|
114
119
|
function pageSection(audit, ctx) {
|
|
115
120
|
const lines = [line(ctx, audit.relativePath, ctx.c.underline)];
|
|
116
121
|
if (audit.error) {
|
|
117
|
-
lines.push(render(ctx, [{
|
|
122
|
+
lines.push(render$1(ctx, [{
|
|
118
123
|
text: ` ${ctx.symbol("error")} not audited: `,
|
|
119
124
|
paint: ctx.c.red
|
|
120
125
|
}, { text: audit.error }]), "");
|
|
121
126
|
return lines;
|
|
122
127
|
}
|
|
123
|
-
for (const finding of
|
|
124
|
-
for (const finding of audit.incomplete.filter((item) => item.reason === "needs-review")) lines.push(render(ctx, [
|
|
128
|
+
for (const finding of [...audit.violations].sort(byImpactThenRule)) lines.push(...violationLines(finding, ctx));
|
|
129
|
+
for (const finding of audit.incomplete.filter((item) => item.reason === "needs-review")) lines.push(render$1(ctx, [
|
|
125
130
|
{
|
|
126
131
|
text: ` ${ctx.symbol("review")} `,
|
|
127
132
|
paint: ctx.c.yellow
|
|
@@ -137,7 +142,7 @@ function pageSection(audit, ctx) {
|
|
|
137
142
|
]));
|
|
138
143
|
if (audit.violations.length === 0) {
|
|
139
144
|
const clean = (audit.accepted ?? []).length === 0;
|
|
140
|
-
lines.push(render(ctx, [{
|
|
145
|
+
lines.push(render$1(ctx, [{
|
|
141
146
|
text: ` ${ctx.symbol("clean")} `,
|
|
142
147
|
paint: clean ? ctx.c.green : ctx.c.dim
|
|
143
148
|
}, {
|
|
@@ -145,7 +150,7 @@ function pageSection(audit, ctx) {
|
|
|
145
150
|
paint: clean ? ctx.c.green : ctx.c.dim
|
|
146
151
|
}]));
|
|
147
152
|
}
|
|
148
|
-
for (const finding of
|
|
153
|
+
for (const finding of [...audit.accepted ?? []].sort(byImpactThenRule)) {
|
|
149
154
|
const elements = count(finding.nodes.length, "element");
|
|
150
155
|
lines.push(line(ctx, ` · ${finding.ruleId} accepted by the baseline (${elements})`, ctx.c.dim));
|
|
151
156
|
}
|
|
@@ -157,8 +162,8 @@ function coverageLine(audit, ctx) {
|
|
|
157
162
|
return line(ctx, ` ${coverageParts(audit).join(" · ")}`, ctx.c.dim);
|
|
158
163
|
}
|
|
159
164
|
function violationLines(finding, ctx) {
|
|
160
|
-
const impact = finding.impact
|
|
161
|
-
const lines = [render(ctx, [
|
|
165
|
+
const impact = impactLabel(finding.impact);
|
|
166
|
+
const lines = [render$1(ctx, [
|
|
162
167
|
{
|
|
163
168
|
text: ` ${ctx.symbol("violation")} `,
|
|
164
169
|
paint: ctx.c.red
|
|
@@ -181,35 +186,31 @@ function violationLines(finding, ctx) {
|
|
|
181
186
|
return lines;
|
|
182
187
|
}
|
|
183
188
|
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);
|
|
189
|
+
const totals = buildSummary(audits, ctx.failOn);
|
|
188
190
|
const reviewCount = countRules(audits, "needs-review");
|
|
189
191
|
const pages = count(audits.length, "page");
|
|
190
|
-
const audited =
|
|
192
|
+
const audited = totals.pages - totals.pagesNotAudited;
|
|
191
193
|
const lines = [line(ctx, "Summary", ctx.c.bold), ...completenessLines(ctx)];
|
|
192
|
-
if (
|
|
193
|
-
else if (
|
|
194
|
-
const clean =
|
|
194
|
+
if (totals.violations === 0 && audited === 0 && totals.pagesNotAudited > 0) lines.push(line(ctx, " Nothing was audited: no page could be read.", ctx.c.red));
|
|
195
|
+
else if (totals.violations === 0) {
|
|
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}.`;
|
|
195
197
|
lines.push(line(ctx, clean, ctx.c.green));
|
|
196
198
|
} else {
|
|
197
|
-
lines.push(render(ctx, [{
|
|
198
|
-
text: ` ${count(
|
|
199
|
+
lines.push(render$1(ctx, [{
|
|
200
|
+
text: ` ${count(totals.violations, "violation")} on ${totals.pagesWithViolations} of ${count(audited, "page")}`,
|
|
199
201
|
paint: ctx.c.red
|
|
200
202
|
}, {
|
|
201
|
-
text: ` (${count(
|
|
203
|
+
text: ` (${count(totals.violatingElements, "element")})`,
|
|
202
204
|
paint: ctx.c.dim
|
|
203
205
|
}]));
|
|
204
|
-
lines.push(thresholdLine(
|
|
206
|
+
lines.push(thresholdLine(totals.failing, ctx));
|
|
205
207
|
}
|
|
206
208
|
if (reviewCount > 0) {
|
|
207
209
|
const verb = reviewCount === 1 ? "needs" : "need";
|
|
208
210
|
lines.push(line(ctx, ` ${count(reviewCount, "rule")} ${verb} manual review`, ctx.c.yellow));
|
|
209
211
|
}
|
|
210
|
-
if (
|
|
211
|
-
|
|
212
|
-
if (accepted > 0) lines.push(line(ctx, ` ${count(accepted, "element")} accepted by the baseline, not counted above`, ctx.c.dim));
|
|
212
|
+
if (totals.pagesNotAudited > 0) lines.push(line(ctx, ` ${count(totals.pagesNotAudited, "page")} could not be audited`, ctx.c.red));
|
|
213
|
+
if (totals.accepted > 0) lines.push(line(ctx, ` ${count(totals.accepted, "element")} accepted by the baseline, not counted above`, ctx.c.dim));
|
|
213
214
|
lines.push(...blindSection(audits, ctx));
|
|
214
215
|
lines.push(...coverageSection(audits, ctx));
|
|
215
216
|
return lines;
|
|
@@ -223,12 +224,14 @@ function summary(audits, ctx) {
|
|
|
223
224
|
* it could be read as boilerplate.
|
|
224
225
|
*/
|
|
225
226
|
function coverageSection(audits, ctx) {
|
|
226
|
-
const coverage = buildCoverage(audits);
|
|
227
|
+
const coverage = buildCoverage(audits, void 0, ctx.review);
|
|
227
228
|
const lines = ["", ...wrap(coverageSummary(coverage), ctx.width - 2).map((text) => line(ctx, ` ${text}`, ctx.c.dim))];
|
|
228
229
|
if (coverage.browserWouldAnswer > 0) {
|
|
229
230
|
const verb = coverage.browserWouldAnswer === 1 ? "criterion" : "criteria";
|
|
230
231
|
lines.push(line(ctx, ` --browser would answer ${coverage.browserWouldAnswer} more ${verb}.`, ctx.c.dim));
|
|
231
232
|
}
|
|
233
|
+
const review = reviewSummary(coverage);
|
|
234
|
+
if (review !== void 0) lines.push(...wrap(review, ctx.width - 2).map((text) => line(ctx, ` ${text}`, ctx.c.dim)));
|
|
232
235
|
if (!ctx.coverage) {
|
|
233
236
|
lines.push(line(ctx, " Run with --coverage for the criterion-by-criterion breakdown.", ctx.c.dim));
|
|
234
237
|
return lines;
|
|
@@ -237,7 +240,7 @@ function coverageSection(audits, ctx) {
|
|
|
237
240
|
for (const criterion of coverage.criteria) {
|
|
238
241
|
const paint = criterion.status === "evaluated" ? ctx.c.green : ctx.c.dim;
|
|
239
242
|
const note = criterion.browserWouldAnswer ? " (--browser would answer this)" : "";
|
|
240
|
-
lines.push(render(ctx, [
|
|
243
|
+
lines.push(render$1(ctx, [
|
|
241
244
|
{
|
|
242
245
|
text: ` ${criterion.number} `,
|
|
243
246
|
paint: ctx.c.bold
|
|
@@ -248,6 +251,8 @@ function coverageSection(audits, ctx) {
|
|
|
248
251
|
paint
|
|
249
252
|
}
|
|
250
253
|
]));
|
|
254
|
+
const recorded = criterion.review;
|
|
255
|
+
if (recorded !== void 0) lines.push(line(ctx, ` ${reviewSentence(recorded)}`, ctx.c.dim));
|
|
251
256
|
}
|
|
252
257
|
return lines;
|
|
253
258
|
}
|
|
@@ -270,8 +275,10 @@ const STATUS_WORDS = {
|
|
|
270
275
|
*/
|
|
271
276
|
function completenessLines(ctx) {
|
|
272
277
|
const completeness = ctx.completeness;
|
|
273
|
-
if (completeness === void 0
|
|
274
|
-
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;
|
|
275
282
|
if (completeness.unreachable.length > 0) {
|
|
276
283
|
const noun = plural(completeness.unreachable.length, "page");
|
|
277
284
|
const verb = completeness.unreachable.length === 1 ? "was" : "were";
|
|
@@ -285,9 +292,8 @@ function completenessLines(ctx) {
|
|
|
285
292
|
* Why the run passed or failed. Without this, a build that exits 0 while the
|
|
286
293
|
* report lists violations looks like a bug rather than a threshold choice.
|
|
287
294
|
*/
|
|
288
|
-
function thresholdLine(
|
|
295
|
+
function thresholdLine(failing, ctx) {
|
|
289
296
|
const failOn = ctx.failOn;
|
|
290
|
-
const failing = countAtOrAbove(audits, failOn);
|
|
291
297
|
if (failing === 0) return line(ctx, ` none at or above ${failOn} (--fail-on ${failOn}), so this run passes`, ctx.c.green);
|
|
292
298
|
return line(ctx, ` ${failing} at or above ${failOn} (--fail-on ${failOn})`, ctx.c.red);
|
|
293
299
|
}
|
|
@@ -306,7 +312,7 @@ function blindSection(audits, ctx) {
|
|
|
306
312
|
line(ctx, " They are never reported as passing.", ctx.c.dim)
|
|
307
313
|
];
|
|
308
314
|
for (const { ruleId, pages, finding } of blind) {
|
|
309
|
-
lines.push(render(ctx, [
|
|
315
|
+
lines.push(render$1(ctx, [
|
|
310
316
|
{ text: ` ${ctx.symbol("blind")} ` },
|
|
311
317
|
{ text: ruleId },
|
|
312
318
|
{
|
|
@@ -332,9 +338,6 @@ function countRules(audits, reason) {
|
|
|
332
338
|
for (const audit of audits) for (const finding of audit.incomplete) if (finding.reason === reason) ruleIds.add(finding.ruleId);
|
|
333
339
|
return ruleIds.size;
|
|
334
340
|
}
|
|
335
|
-
function sortByImpact(findings) {
|
|
336
|
-
return [...findings].sort((a, b) => impactRank(a.impact) - impactRank(b.impact) || a.ruleId.localeCompare(b.ruleId));
|
|
337
|
-
}
|
|
338
341
|
function criteria(finding) {
|
|
339
342
|
return finding.successCriteria.length > 0 ? `, WCAG ${finding.successCriteria.join(" ")}` : "";
|
|
340
343
|
}
|
|
@@ -350,14 +353,13 @@ function criteria(finding) {
|
|
|
350
353
|
function issuesSection(audits, ctx) {
|
|
351
354
|
const issues = groupIssues(audits);
|
|
352
355
|
if (issues.length === 0) return [];
|
|
353
|
-
const elements
|
|
354
|
-
const occurrences = issues.reduce((total, issue) => total + issue.occurrences, 0);
|
|
356
|
+
const { elements, occurrences } = issueTotals(issues);
|
|
355
357
|
const lines = [line(ctx, "Issues", ctx.c.bold)];
|
|
356
358
|
const intro = occurrences === elements ? ` ${count(elements, "distinct element")} to fix.` : ` ${count(occurrences, "violation")} across the site come from ${count(elements, "distinct element")}.`;
|
|
357
359
|
lines.push(line(ctx, intro, ctx.c.dim));
|
|
358
360
|
for (const issue of issues) {
|
|
359
361
|
lines.push("");
|
|
360
|
-
lines.push(render(ctx, [
|
|
362
|
+
lines.push(render$1(ctx, [
|
|
361
363
|
{ text: ` ${ctx.symbol("violation")} ` },
|
|
362
364
|
{
|
|
363
365
|
text: issue.ruleId,
|
|
@@ -398,7 +400,7 @@ function remediationLines(ruleId, html, ctx) {
|
|
|
398
400
|
const width = ctx.width - 6 - 2;
|
|
399
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}`))];
|
|
400
402
|
const example = html === void 0 ? void 0 : remediation.example?.(html);
|
|
401
|
-
if (example !== void 0) lines.push(render(ctx, [{
|
|
403
|
+
if (example !== void 0) lines.push(render$1(ctx, [{
|
|
402
404
|
text: `${indent}→ `,
|
|
403
405
|
paint: ctx.c.green
|
|
404
406
|
}, { text: collapse(example) }]));
|
|
@@ -409,14 +411,14 @@ function whereLines(element, ctx) {
|
|
|
409
411
|
const shown = element.pages.slice(0, ctx.maxNodes);
|
|
410
412
|
const rest = element.pages.length - shown.length;
|
|
411
413
|
const component = ctx.componentFor(element.html);
|
|
412
|
-
const lines = component === void 0 ? [] : [render(ctx, [{
|
|
414
|
+
const lines = component === void 0 ? [] : [render$1(ctx, [{
|
|
413
415
|
text: " written in ",
|
|
414
416
|
paint: ctx.c.dim
|
|
415
417
|
}, { text: componentPath(component) }])];
|
|
416
418
|
lines.push(line(ctx, ` on ${count(element.pages.length, "page")}:`, ctx.c.dim));
|
|
417
419
|
for (const page of shown) {
|
|
418
420
|
const source = ctx.sourceFor(page);
|
|
419
|
-
lines.push(render(ctx, [{
|
|
421
|
+
lines.push(render$1(ctx, [{
|
|
420
422
|
text: ` ${page}`,
|
|
421
423
|
paint: ctx.c.dim
|
|
422
424
|
}, ...source === void 0 ? [] : [{
|
|
@@ -497,12 +499,7 @@ async function resolvePages(directory, options = {}) {
|
|
|
497
499
|
warn(await emptyDirectoryHint(shown, cwd));
|
|
498
500
|
return;
|
|
499
501
|
}
|
|
500
|
-
|
|
501
|
-
const verb = unreachable.length === 1 ? "file was" : "files were";
|
|
502
|
-
warn(`${unreachable.length} ${verb} not readable, and so not audited:`);
|
|
503
|
-
for (const file of unreachable.slice(0, 10)) note(` ${file.location} — ${file.reason}`);
|
|
504
|
-
if (unreachable.length > 10) note(` …and ${unreachable.length - 10} more`);
|
|
505
|
-
}
|
|
502
|
+
warnUnmeasured(unreachable, "file", "readable");
|
|
506
503
|
return {
|
|
507
504
|
pages,
|
|
508
505
|
directory,
|
|
@@ -516,13 +513,29 @@ async function resolvePages(directory, options = {}) {
|
|
|
516
513
|
};
|
|
517
514
|
}
|
|
518
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
|
+
/**
|
|
519
532
|
* Fetch the pages of a running site, reporting what happened on the way.
|
|
520
533
|
*
|
|
521
534
|
* Returns undefined when the crawl could not start, which the caller turns into
|
|
522
535
|
* exit 2 — a run that reached no verdict, not a clean one.
|
|
523
536
|
*/
|
|
524
537
|
async function crawlPages(url, options) {
|
|
525
|
-
const { crawlSite, CrawlError, parseEntryUrl } = await import("./crawl-
|
|
538
|
+
const { collapsedOnto, crawlSite, CrawlError, parseEntryUrl } = await import("./crawl-BHkJmUkA.js");
|
|
526
539
|
let entry;
|
|
527
540
|
try {
|
|
528
541
|
entry = parseEntryUrl(url, options.allowRemote ?? false);
|
|
@@ -540,7 +553,8 @@ async function crawlPages(url, options) {
|
|
|
540
553
|
...options.sitemap === void 0 ? {} : { sitemap: options.sitemap },
|
|
541
554
|
...options.maxPages === void 0 ? {} : { maxPages: options.maxPages },
|
|
542
555
|
...options.maxDepth === void 0 ? {} : { maxDepth: options.maxDepth },
|
|
543
|
-
...options.timeoutMs === void 0 ? {} : { timeoutMs: options.timeoutMs }
|
|
556
|
+
...options.timeoutMs === void 0 ? {} : { timeoutMs: options.timeoutMs },
|
|
557
|
+
...options.headers === void 0 ? {} : { headers: options.headers }
|
|
544
558
|
});
|
|
545
559
|
if (result.pages.length === 0 && result.failures.length > 0) {
|
|
546
560
|
fail(`Could not fetch ${entry.href} (${result.failures[0]?.reason})`);
|
|
@@ -549,23 +563,29 @@ 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.`);
|
|
572
|
+
const collapsed = collapsedOnto(result);
|
|
573
|
+
if (collapsed.length > 0) {
|
|
574
|
+
const landedOn = collapsed[0]?.landedOn ?? "";
|
|
575
|
+
warn(`Every page requested answered at ${landedOn}, which is not where it was asked.`);
|
|
576
|
+
note(" A sign-in page in front of the site looks like this.");
|
|
577
|
+
note(" If it is behind one, pass --basic-auth user:password or --header \"Cookie: …\".");
|
|
578
|
+
}
|
|
559
579
|
return {
|
|
560
580
|
pages: result.pages,
|
|
561
581
|
origin: result.origin,
|
|
562
582
|
completeness: {
|
|
563
583
|
discovery: result.discovery,
|
|
564
584
|
collected: result.pages.length,
|
|
565
|
-
unreachable:
|
|
566
|
-
location:
|
|
567
|
-
reason:
|
|
568
|
-
})),
|
|
585
|
+
unreachable: [...failed, ...collapsed.map((redirect) => ({
|
|
586
|
+
location: redirect.requested,
|
|
587
|
+
reason: `answered at ${redirect.landedOn} instead, so this page was not audited`
|
|
588
|
+
}))],
|
|
569
589
|
truncated: result.truncated
|
|
570
590
|
}
|
|
571
591
|
};
|
|
@@ -627,11 +647,15 @@ async function runAuditCommand(dir, options = {}) {
|
|
|
627
647
|
exitCode: 2
|
|
628
648
|
};
|
|
629
649
|
const { pages, origin, label, cleanup, directory, completeness: collection } = resolved;
|
|
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.");
|
|
630
651
|
try {
|
|
631
|
-
note(`Auditing ${count(pages.length, "page")} in ${label}${await describeEngine(pages, options)}…`);
|
|
632
652
|
const baseUrl = options.baseUrl ?? origin;
|
|
633
|
-
|
|
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, {
|
|
634
657
|
cwd: options.cwd ?? process.cwd(),
|
|
658
|
+
...options.headers === void 0 ? {} : { headers: options.headers },
|
|
635
659
|
...baseUrl === void 0 ? {} : { baseUrl },
|
|
636
660
|
...options.timeoutMs === void 0 ? {} : { timeoutMs: options.timeoutMs },
|
|
637
661
|
...options.browser ? { browser: true } : {},
|
|
@@ -639,10 +663,16 @@ async function runAuditCommand(dir, options = {}) {
|
|
|
639
663
|
...options.concurrency === void 0 ? {} : { concurrency: options.concurrency },
|
|
640
664
|
...directory === void 0 ? {} : { directory }
|
|
641
665
|
});
|
|
642
|
-
if (!
|
|
666
|
+
if (!fresh) return {
|
|
643
667
|
audits: [],
|
|
644
668
|
exitCode: 2
|
|
645
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]);
|
|
646
676
|
const failOn = options.failOn ?? "serious";
|
|
647
677
|
if (options.baseline) {
|
|
648
678
|
const applied = await acceptBaseline(audits, options);
|
|
@@ -653,7 +683,12 @@ async function runAuditCommand(dir, options = {}) {
|
|
|
653
683
|
audits = applied;
|
|
654
684
|
}
|
|
655
685
|
const completeness = runCompleteness(audits, collection);
|
|
656
|
-
|
|
686
|
+
const review = await loadReview(options);
|
|
687
|
+
if (review === FAILED) return {
|
|
688
|
+
audits,
|
|
689
|
+
exitCode: 2
|
|
690
|
+
};
|
|
691
|
+
await emit(audits, label, failOn, completeness, options, review);
|
|
657
692
|
const unaudited = audits.filter((audit) => audit.error);
|
|
658
693
|
if (unaudited.length > 0) {
|
|
659
694
|
fail(`${unaudited.length} of ${audits.length} pages could not be audited`);
|
|
@@ -671,6 +706,67 @@ async function runAuditCommand(dir, options = {}) {
|
|
|
671
706
|
}
|
|
672
707
|
}
|
|
673
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
|
+
/**
|
|
674
770
|
* Move the violations the baseline accounts for out of the failing set.
|
|
675
771
|
*
|
|
676
772
|
* Returns undefined when the baseline could not be read, which the caller
|
|
@@ -679,7 +775,7 @@ async function runAuditCommand(dir, options = {}) {
|
|
|
679
775
|
* would be as wrong as silently passing.
|
|
680
776
|
*/
|
|
681
777
|
async function acceptBaseline(audits, options) {
|
|
682
|
-
const { applyBaseline, BaselineError, readBaseline } = await import("./baseline-
|
|
778
|
+
const { applyBaseline, BaselineError, readBaseline } = await import("./baseline-rtnj3RHB.js");
|
|
683
779
|
try {
|
|
684
780
|
const outcome = applyBaseline(audits, await readBaseline(options.baseline, options.cwd ?? process.cwd()));
|
|
685
781
|
if (outcome.accepted > 0) note(`Baseline accepted ${outcome.accepted} violating elements`);
|
|
@@ -697,6 +793,32 @@ async function acceptBaseline(audits, options) {
|
|
|
697
793
|
throw cause;
|
|
698
794
|
}
|
|
699
795
|
}
|
|
796
|
+
/** Distinguishes "no review asked for" from "the review could not be read". */
|
|
797
|
+
const FAILED = Symbol("review-failed");
|
|
798
|
+
/**
|
|
799
|
+
* Read the review record, when one was asked for.
|
|
800
|
+
*
|
|
801
|
+
* Returns undefined when no `--review` was given, which is the ordinary case,
|
|
802
|
+
* and the sentinel when one was given and could not be read.
|
|
803
|
+
*/
|
|
804
|
+
async function loadReview(options) {
|
|
805
|
+
if (options.review === void 0) return void 0;
|
|
806
|
+
const { answeredCount, readReview, ReviewError } = await import("./review-DIbOTmOM.js");
|
|
807
|
+
try {
|
|
808
|
+
const record = await readReview(options.review, options.cwd ?? process.cwd());
|
|
809
|
+
note(`Review record: ${count(answeredCount(record), "criterion")} answered`);
|
|
810
|
+
return {
|
|
811
|
+
record,
|
|
812
|
+
...options.reviewMaxAge === void 0 ? {} : { maxAgeDays: options.reviewMaxAge }
|
|
813
|
+
};
|
|
814
|
+
} catch (cause) {
|
|
815
|
+
if (cause instanceof ReviewError) {
|
|
816
|
+
fail(cause.message);
|
|
817
|
+
return FAILED;
|
|
818
|
+
}
|
|
819
|
+
throw cause;
|
|
820
|
+
}
|
|
821
|
+
}
|
|
700
822
|
/**
|
|
701
823
|
* What the progress line says about the engine.
|
|
702
824
|
*
|
|
@@ -706,79 +828,93 @@ async function acceptBaseline(audits, options) {
|
|
|
706
828
|
*/
|
|
707
829
|
async function describeEngine(pages, options) {
|
|
708
830
|
if (options.browser) return " with Chromium";
|
|
709
|
-
const { plannedWorkers } = await import("./pool-
|
|
831
|
+
const { plannedWorkers } = await import("./pool-B4tEUa2S.js");
|
|
710
832
|
const workers = options.concurrency ?? plannedWorkers(pages);
|
|
711
833
|
const threads = workers > 1 ? ` across ${workers} threads` : "";
|
|
712
834
|
return options.fast ? `${threads}, skipping what this engine cannot decide` : threads;
|
|
713
835
|
}
|
|
714
836
|
/**
|
|
715
|
-
*
|
|
716
|
-
* otherwise. Colour is dropped when writing to a file, since
|
|
717
|
-
* 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.
|
|
718
844
|
*/
|
|
719
|
-
async function emit(audits, dir, failOn, completeness, options) {
|
|
720
|
-
const body = await
|
|
845
|
+
async function emit(audits, dir, failOn, completeness, options, review) {
|
|
846
|
+
const body = await render(audits, dir, failOn, completeness, typeof options.output === "string", options, review);
|
|
721
847
|
await emitDocument(body, options.output, options.cwd ?? process.cwd());
|
|
722
848
|
if (options.output !== void 0) note(`Report written to ${options.output}`);
|
|
723
849
|
}
|
|
724
|
-
async function
|
|
725
|
-
switch (format) {
|
|
850
|
+
async function render(audits, dir, failOn, completeness, toFile, options, review) {
|
|
851
|
+
switch (options.format ?? "console") {
|
|
726
852
|
case "json": {
|
|
727
|
-
const { buildJsonReport, serialiseJsonReport } = await import("./json-
|
|
853
|
+
const { buildJsonReport, serialiseJsonReport } = await import("./json-CrQmoTj6.js");
|
|
728
854
|
return serialiseJsonReport(buildJsonReport(audits, {
|
|
729
855
|
directory: dir,
|
|
730
856
|
...options.url === void 0 ? {} : { sourceKind: "url" },
|
|
731
857
|
failOn,
|
|
732
858
|
completeness,
|
|
859
|
+
...review === void 0 ? {} : { review },
|
|
733
860
|
...options.baseUrl ? { baseUrl: options.baseUrl } : {}
|
|
734
861
|
}));
|
|
735
862
|
}
|
|
736
863
|
case "sarif": {
|
|
737
|
-
const { buildSarifReport, serialiseSarifReport } = await import("./sarif-
|
|
864
|
+
const { buildSarifReport, serialiseSarifReport } = await import("./sarif-BNCE0PNT.js");
|
|
738
865
|
return serialiseSarifReport(buildSarifReport(audits, {
|
|
739
866
|
directory: dir,
|
|
740
|
-
completeness
|
|
867
|
+
completeness,
|
|
868
|
+
...review === void 0 ? {} : { review }
|
|
741
869
|
}));
|
|
742
870
|
}
|
|
743
871
|
case "html": {
|
|
744
|
-
const { buildHtmlReport } = await import("./html-
|
|
745
|
-
const framework = await detectedFramework(options.cwd ?? process.cwd());
|
|
872
|
+
const { buildHtmlReport } = await import("./html-BIxxF3FC.js");
|
|
746
873
|
return buildHtmlReport(audits, {
|
|
747
|
-
...await
|
|
874
|
+
...await fromTheProject(audits, options),
|
|
748
875
|
directory: dir,
|
|
749
876
|
failOn,
|
|
750
877
|
completeness,
|
|
751
|
-
...
|
|
878
|
+
...review === void 0 ? {} : { review },
|
|
752
879
|
...options.baseUrl ? { baseUrl: options.baseUrl } : {}
|
|
753
880
|
});
|
|
754
881
|
}
|
|
755
|
-
case "console": {
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
...toFile ? { color: false } : {}
|
|
767
|
-
})}\n`;
|
|
768
|
-
}
|
|
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`;
|
|
769
893
|
}
|
|
770
894
|
}
|
|
771
895
|
/**
|
|
772
|
-
*
|
|
773
|
-
*
|
|
774
|
-
*
|
|
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.
|
|
775
902
|
*/
|
|
776
|
-
|
|
777
|
-
|
|
903
|
+
async function fromTheProject(audits, options) {
|
|
904
|
+
const cwd = options.cwd ?? process.cwd();
|
|
778
905
|
const { detectFramework } = await import("./frameworks-etFg_O8K.js");
|
|
779
906
|
const { readPackageJson } = await import("./project-MFrXcw1M.js");
|
|
780
|
-
|
|
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
|
+
};
|
|
781
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
|
+
*/
|
|
782
918
|
async function sourceLookups(cwd, audits) {
|
|
783
919
|
const { buildRouteMap, sourceFor } = await import("./routes-CmdRUuOs.js");
|
|
784
920
|
const routes = await buildRouteMap(cwd);
|
|
@@ -786,7 +922,7 @@ async function sourceLookups(cwd, audits) {
|
|
|
786
922
|
sourceFor: (page) => sourceFor(routes, page),
|
|
787
923
|
componentFor: () => void 0
|
|
788
924
|
};
|
|
789
|
-
const { buildComponentIndex, componentFor } = await import("./component-
|
|
925
|
+
const { buildComponentIndex, componentFor } = await import("./component-DcCn3BJS.js");
|
|
790
926
|
const components = await buildComponentIndex(cwd);
|
|
791
927
|
return {
|
|
792
928
|
sourceFor: (page) => sourceFor(routes, page),
|