eaa-kit 0.3.0 → 0.4.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 +51 -5
- package/dist/astro/index.js +1 -1
- package/dist/audit/runners/worker.js +1 -1
- package/dist/{audit-DcL73mOC.js → audit-CPoZMXGM.js} +180 -36
- package/dist/audit-CpXH2Mk8.js +2 -0
- package/dist/{baseline-CuKFq4IF.js → baseline-22Y1NWxM.js} +1 -1
- package/dist/{baseline-s9F3fXTN.js → baseline-DB9CZGnV.js} +2 -2
- package/dist/cli/index.js +267 -10
- package/dist/{collect-BkAQ0viT.js → collect-CFM8gEVv.js} +21 -10
- package/dist/{command-Dxpa00Ha.js → command-D8l_oYbV.js} +2 -2
- package/dist/{component-7kEBjv_y.js → component-C3GL1Mnu.js} +41 -6
- package/dist/component-DKd3EHOg.js +2 -0
- package/dist/coverage-B4IBKMO8.js +521 -0
- package/dist/{crawl-Oxt2Gaqo.js → crawl-BiI1Lau_.js} +2 -2
- package/dist/eleventy/index.d.ts +28 -0
- package/dist/eleventy/index.js +19 -0
- package/dist/{frameworks-BYa3tULg.js → frameworks-B4ClIJgE.js} +71 -0
- package/dist/{frameworks-DaDqrJOw.js → frameworks-etFg_O8K.js} +1 -1
- package/dist/{html-DKiI_3gs.js → html-BGTO3ypW.js} +130 -5
- package/dist/{impact-YdoOtFqm.js → impact-DZt2oBCP.js} +1 -1
- package/dist/index.js +2 -2
- package/dist/{init-DiLiYvN1.js → init-DIWDE35F.js} +2 -2
- package/dist/jsdom-4IMzv0eE.js +3 -0
- package/dist/jsdom-DCpGSLfW.js +81 -0
- package/dist/{json-Cnv9nd6U.js → json-DjEvy1nX.js} +8 -3
- package/dist/{json-B0Y7rNjt.js → json-QQuFIw1W.js} +1 -1
- package/dist/{load-UYXLqGV9.js → load-5wRGLvub.js} +1 -1
- package/dist/nuxt/index.d.ts +35 -0
- package/dist/nuxt/index.js +23 -0
- package/dist/{playwright-DYFsGUNd.js → playwright-BWniOain.js} +1 -1
- package/dist/{pool-BWkWZiJW.js → pool-BMevaLWD.js} +2 -2
- package/dist/{project-DW08TseF.js → project-CzOnkLH6.js} +8 -2
- package/dist/project-MFrXcw1M.js +2 -0
- package/dist/remediation-Dtowi2EC.js +321 -0
- package/dist/{render-DI_aCnAZ.js → render-DrvXRCEn.js} +3 -3
- package/dist/{result-DLxd2Eip.js → result-DoamKFsp.js} +78 -1
- package/dist/routes-CmdRUuOs.js +265 -0
- package/dist/{run-BW6CVuND.js → run-DB34BSOZ.js} +19 -2
- package/dist/{sarif-DB3WG7T9.js → sarif-SR3_lLYd.js} +14 -6
- package/dist/{schema-CMZ8ItGk.js → schema-is6CGX2D.js} +4 -1
- package/dist/{text-BFmNtMsV.js → text-CKKpzkYM.js} +1 -1
- package/dist/vite/index.js +1 -1
- package/dist/webpack/index.d.ts +33 -0
- package/dist/webpack/index.js +20 -0
- package/package.json +30 -8
- package/dist/audit-CuG2hYyo.js +0 -2
- package/dist/jsdom-BjpF-2V-.js +0 -158
- package/dist/jsdom-X4KYfTp8.js +0 -3
- package/dist/manual-Vz-oX1I_.js +0 -239
- package/dist/routes-C2Cgf6Ko.js +0 -119
package/README.md
CHANGED
|
@@ -9,6 +9,7 @@ in Austria.
|
|
|
9
9
|
|
|
10
10
|
```bash
|
|
11
11
|
npx eaa-kit audit # WCAG 2.2 AA report; finds your build itself
|
|
12
|
+
npx eaa-kit diff a.json b.json # what a change made worse, and what it fixed
|
|
12
13
|
npx eaa-kit init # write an eaa.config.json
|
|
13
14
|
npx eaa-kit statement # Barrierefreiheitserklärung from that config
|
|
14
15
|
```
|
|
@@ -62,13 +63,54 @@ eaa-kit baseline ./dist
|
|
|
62
63
|
eaa-kit audit ./dist --baseline eaa-baseline.json
|
|
63
64
|
```
|
|
64
65
|
|
|
66
|
+
**Says what a change did.** Two reports, and the difference between them: what is new, what
|
|
67
|
+
was fixed, and what the later run never looked at — which is kept apart, because a
|
|
68
|
+
violation missing from a run that stopped early was not fixed by anybody.
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
eaa-kit diff before.json after.json
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**Says what to do about it.** Not "images must have alternative text" and a link — who the
|
|
75
|
+
barrier stops, what to change, and the corrected form of *your* markup, in your framework's
|
|
76
|
+
idiom where that differs. Deterministic and offline: no model, no API key, nothing that
|
|
77
|
+
could invent a fix that looks right and is not.
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
✗ image-alt critical, WCAG 1.1.1
|
|
81
|
+
A screen reader announces this image by its filename, or skips it entirely.
|
|
82
|
+
Fix: Add alt text describing what the image conveys…
|
|
83
|
+
→ <img src="/assets/logo.svg" alt="What this image shows">
|
|
84
|
+
written in src/components/Header.astro:12
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
**Says how much of WCAG it could reach.** WCAG 2.2 has 55 success criteria at Levels A and
|
|
88
|
+
AA. axe-core has rules touching 23 of them. Every run says so, and `--coverage` lists all
|
|
89
|
+
55 with what this run reached on each — never as a percentage, because most of WCAG cannot
|
|
90
|
+
be automated and a score would present that as a fact about your site.
|
|
91
|
+
|
|
92
|
+
**Says what it did not measure.** A crawl that stopped at its page limit, or could not
|
|
93
|
+
fetch forty URLs, no longer produces a report that looks like a complete one.
|
|
94
|
+
|
|
65
95
|
**Reports in four shapes**: a console report for whoever ran it, JSON for other tools,
|
|
66
96
|
SARIF for GitHub code scanning, and a self-contained HTML page for the client whose site
|
|
67
97
|
it is.
|
|
68
98
|
|
|
69
|
-
**Runs in your build
|
|
70
|
-
|
|
71
|
-
|
|
99
|
+
**Runs in your build**: a [Vite plugin](docs/integrations.md#vite-plugin) covering SvelteKit
|
|
100
|
+
and Remix too, and integrations for
|
|
101
|
+
[Astro](docs/integrations.md#astro-integration),
|
|
102
|
+
[Nuxt](docs/integrations.md#nuxt-module),
|
|
103
|
+
[Eleventy](docs/integrations.md#eleventy-plugin) and
|
|
104
|
+
[webpack](docs/integrations.md#webpack-plugin) — or the bundled
|
|
105
|
+
[GitHub Action](docs/integrations.md#github-actions).
|
|
106
|
+
|
|
107
|
+
**Audits what a CMS serves.** WordPress, TYPO3, Craft, Laravel, Symfony, Rails and Django
|
|
108
|
+
write no HTML to disk, so eaa-kit names the command that gets the site running and audits
|
|
109
|
+
that instead — following the site's own sitemap, wherever it keeps it.
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
eaa-kit audit --url http://localhost:8000 --sitemap /sitemap_index.xml
|
|
113
|
+
```
|
|
72
114
|
|
|
73
115
|
## Documentation
|
|
74
116
|
|
|
@@ -77,8 +119,10 @@ or the bundled [GitHub Action](docs/integrations.md#github-actions).
|
|
|
77
119
|
| [Auditing a build](docs/audit.md) | The `audit` command, both engines, exit codes, and what an automated run can and cannot tell you |
|
|
78
120
|
| [The statement command](docs/statement.md) | The config file, the three countries, and filling a statement from audit results |
|
|
79
121
|
| [Baselines](docs/baseline.md) | Adopting the tool on a site that already has violations |
|
|
122
|
+
| [Comparing two runs](docs/reports.md#comparing-two-runs) | The `diff` command, and what it refuses to call fixed |
|
|
123
|
+
| [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 |
|
|
80
124
|
| [Report formats](docs/reports.md) | The JSON contract, SARIF, and the HTML report |
|
|
81
|
-
| [Integrations](docs/integrations.md) |
|
|
125
|
+
| [Integrations](docs/integrations.md) | The build plugins, the GitHub Action, and the two builders that get a command instead |
|
|
82
126
|
|
|
83
127
|
Complete generated output for every format is checked in under [examples/](examples).
|
|
84
128
|
|
|
@@ -90,7 +134,9 @@ The two things worth knowing before you rely on any of it:
|
|
|
90
134
|
text is accurate, whether a page makes sense in reading order, or whether a form can
|
|
91
135
|
actually be completed with a screen reader. A clean report means nothing was found by this
|
|
92
136
|
engine, which is not the same as a site being accessible — and none of the four output
|
|
93
|
-
formats will say otherwise on your behalf.
|
|
137
|
+
formats will say otherwise on your behalf. This is not a disclaimer the tool leaves you to
|
|
138
|
+
take on faith: it counts it. Of the 55 WCAG 2.2 A and AA criteria, 34 have no automated
|
|
139
|
+
rule at all, and every report says so.
|
|
94
140
|
|
|
95
141
|
**The browserless engine cannot decide everything.** jsdom has no layout, so rules that
|
|
96
142
|
depend on rendering — colour contrast, target size, computed overflow — cannot be
|
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-DB34BSOZ.js";
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
3
|
//#region src/astro/index.ts
|
|
4
4
|
var AstroAuditError = class extends Error {
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import { a as impactRank, i as impactLabel, r as countAtOrAbove } from "./impact-
|
|
2
|
-
import { n as count, t as collapse } from "./text-
|
|
3
|
-
import { a as
|
|
4
|
-
import {
|
|
5
|
-
import { n as
|
|
1
|
+
import { a as impactRank, i as impactLabel, r as countAtOrAbove } from "./impact-DZt2oBCP.js";
|
|
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-Dtowi2EC.js";
|
|
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-B4IBKMO8.js";
|
|
6
|
+
import { a as runEngine, i as note, n as emitDocument, o as warn, r as fail, t as advise } from "./command-D8l_oYbV.js";
|
|
7
|
+
import { n as collectPages, r as emptyDirectoryHint, t as BuildDirectoryError } from "./collect-CFM8gEVv.js";
|
|
6
8
|
import path from "node:path";
|
|
7
9
|
import pc from "picocolors";
|
|
8
10
|
//#region src/audit/report/console.ts
|
|
@@ -47,6 +49,9 @@ function context(options) {
|
|
|
47
49
|
sourceFor: options.sourceFor ?? (() => void 0),
|
|
48
50
|
componentFor: options.componentFor ?? (() => void 0),
|
|
49
51
|
manual: options.manual ?? false,
|
|
52
|
+
coverage: options.coverage ?? false,
|
|
53
|
+
framework: options.framework,
|
|
54
|
+
completeness: options.completeness,
|
|
50
55
|
c,
|
|
51
56
|
symbol: (kind) => {
|
|
52
57
|
switch (kind) {
|
|
@@ -182,9 +187,11 @@ function summary(audits, ctx) {
|
|
|
182
187
|
const elementCount = audits.reduce((total, audit) => total + audit.violations.reduce((sum, finding) => sum + finding.nodes.length, 0), 0);
|
|
183
188
|
const reviewCount = countRules(audits, "needs-review");
|
|
184
189
|
const pages = count(audits.length, "page");
|
|
185
|
-
const lines = [line(ctx, "Summary", ctx.c.bold)];
|
|
186
|
-
if (ruleCount === 0)
|
|
187
|
-
|
|
190
|
+
const lines = [line(ctx, "Summary", ctx.c.bold), ...completenessLines(ctx)];
|
|
191
|
+
if (ruleCount === 0) {
|
|
192
|
+
const clean = ctx.completeness && !ctx.completeness.complete ? ` No violations across the ${pages} that were audited.` : ` No violations across ${pages}.`;
|
|
193
|
+
lines.push(line(ctx, clean, ctx.c.green));
|
|
194
|
+
} else {
|
|
188
195
|
lines.push(render(ctx, [{
|
|
189
196
|
text: ` ${count(ruleCount, "violation")} on ${withViolations.length} of ${pages}`,
|
|
190
197
|
paint: ctx.c.red
|
|
@@ -202,6 +209,73 @@ function summary(audits, ctx) {
|
|
|
202
209
|
const accepted = audits.reduce((total, audit) => total + (audit.accepted ?? []).reduce((sum, finding) => sum + finding.nodes.length, 0), 0);
|
|
203
210
|
if (accepted > 0) lines.push(line(ctx, ` ${count(accepted, "element")} accepted by the baseline, not counted above`, ctx.c.dim));
|
|
204
211
|
lines.push(...blindSection(audits, ctx));
|
|
212
|
+
lines.push(...coverageSection(audits, ctx));
|
|
213
|
+
return lines;
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* How much of the standard this run could reach.
|
|
217
|
+
*
|
|
218
|
+
* One line by default. It is the sentence that stops a clean report reading as
|
|
219
|
+
* a clean site: most of WCAG cannot be checked by any automated engine, and
|
|
220
|
+
* until this was printed the report said so only in prose, in the footer, where
|
|
221
|
+
* it could be read as boilerplate.
|
|
222
|
+
*/
|
|
223
|
+
function coverageSection(audits, ctx) {
|
|
224
|
+
const coverage = buildCoverage(audits);
|
|
225
|
+
const lines = ["", ...wrap(coverageSummary(coverage), ctx.width - 2).map((text) => line(ctx, ` ${text}`, ctx.c.dim))];
|
|
226
|
+
if (coverage.browserWouldAnswer > 0) {
|
|
227
|
+
const verb = coverage.browserWouldAnswer === 1 ? "criterion" : "criteria";
|
|
228
|
+
lines.push(line(ctx, ` --browser would answer ${coverage.browserWouldAnswer} more ${verb}.`, ctx.c.dim));
|
|
229
|
+
}
|
|
230
|
+
if (!ctx.coverage) {
|
|
231
|
+
lines.push(line(ctx, " Run with --coverage for the criterion-by-criterion breakdown.", ctx.c.dim));
|
|
232
|
+
return lines;
|
|
233
|
+
}
|
|
234
|
+
lines.push("", line(ctx, "Coverage", ctx.c.bold));
|
|
235
|
+
for (const criterion of coverage.criteria) {
|
|
236
|
+
const paint = criterion.status === "evaluated" ? ctx.c.green : ctx.c.dim;
|
|
237
|
+
const note = criterion.browserWouldAnswer ? " (--browser would answer this)" : "";
|
|
238
|
+
lines.push(render(ctx, [
|
|
239
|
+
{
|
|
240
|
+
text: ` ${criterion.number} `,
|
|
241
|
+
paint: ctx.c.bold
|
|
242
|
+
},
|
|
243
|
+
{ text: `${criterion.title} (${criterion.level}) — ` },
|
|
244
|
+
{
|
|
245
|
+
text: `${STATUS_WORDS[criterion.status]}${note}`,
|
|
246
|
+
paint
|
|
247
|
+
}
|
|
248
|
+
]));
|
|
249
|
+
}
|
|
250
|
+
return lines;
|
|
251
|
+
}
|
|
252
|
+
/** What each outcome is called, in words rather than a symbol. */
|
|
253
|
+
const STATUS_WORDS = {
|
|
254
|
+
evaluated: "evaluated here",
|
|
255
|
+
"not-evaluated": "this engine could not evaluate it",
|
|
256
|
+
"nothing-to-check": "rules ran and found nothing on this site to check",
|
|
257
|
+
"no-automated-rule": "no automated rule exists; a person must check it"
|
|
258
|
+
};
|
|
259
|
+
/**
|
|
260
|
+
* What the run never looked at.
|
|
261
|
+
*
|
|
262
|
+
* Printed before the counts rather than after them, because it changes how they
|
|
263
|
+
* read: "no violations" means one thing over a whole site and another over the
|
|
264
|
+
* twelve pages of it a crawl managed to fetch before it hit its limit.
|
|
265
|
+
*
|
|
266
|
+
* Pages that errored are left to the summary's own line, which already names
|
|
267
|
+
* them; repeating the number here would read as twice as many.
|
|
268
|
+
*/
|
|
269
|
+
function completenessLines(ctx) {
|
|
270
|
+
const completeness = ctx.completeness;
|
|
271
|
+
if (completeness === void 0 || completeness.complete) return [];
|
|
272
|
+
const lines = [];
|
|
273
|
+
if (completeness.unreachable.length > 0) {
|
|
274
|
+
const noun = plural(completeness.unreachable.length, "page");
|
|
275
|
+
lines.push(line(ctx, ` ${completeness.unreachable.length} ${noun} could not be reached, and were not audited`, ctx.c.yellow));
|
|
276
|
+
}
|
|
277
|
+
if (completeness.truncated) lines.push(line(ctx, " The run stopped at its page limit; the site has more", ctx.c.yellow));
|
|
278
|
+
if (lines.length > 0) lines.push(line(ctx, " This report describes what was audited, not the whole site.", ctx.c.dim));
|
|
205
279
|
return lines;
|
|
206
280
|
}
|
|
207
281
|
/**
|
|
@@ -296,6 +370,7 @@ function issuesSection(audits, ctx) {
|
|
|
296
370
|
}] : []
|
|
297
371
|
]));
|
|
298
372
|
lines.push(line(ctx, ` ${issue.help}`, ctx.c.dim));
|
|
373
|
+
lines.push(...remediationLines(issue.ruleId, issue.elements[0]?.html, ctx));
|
|
299
374
|
for (const element of issue.elements.slice(0, ctx.maxNodes)) {
|
|
300
375
|
lines.push(line(ctx, ` ${collapse(element.html)}`));
|
|
301
376
|
lines.push(...whereLines(element, ctx));
|
|
@@ -305,6 +380,27 @@ function issuesSection(audits, ctx) {
|
|
|
305
380
|
}
|
|
306
381
|
return lines;
|
|
307
382
|
}
|
|
383
|
+
/**
|
|
384
|
+
* What to do about the rule, under the finding rather than in a link.
|
|
385
|
+
*
|
|
386
|
+
* axe-core's help text says what is wrong; this says who it stops and what to
|
|
387
|
+
* change. The corrected line is built from the element that actually failed,
|
|
388
|
+
* because a textbook snippet is a second thing to translate before anybody can
|
|
389
|
+
* use it.
|
|
390
|
+
*/
|
|
391
|
+
function remediationLines(ruleId, html, ctx) {
|
|
392
|
+
const remediation = remediationFor(ruleId, ctx.framework);
|
|
393
|
+
if (remediation === void 0) return [];
|
|
394
|
+
const indent = " ";
|
|
395
|
+
const width = ctx.width - 6 - 2;
|
|
396
|
+
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}`))];
|
|
397
|
+
const example = html === void 0 ? void 0 : remediation.example?.(html);
|
|
398
|
+
if (example !== void 0) lines.push(render(ctx, [{
|
|
399
|
+
text: `${indent}→ `,
|
|
400
|
+
paint: ctx.c.green
|
|
401
|
+
}, { text: collapse(example) }]));
|
|
402
|
+
return lines;
|
|
403
|
+
}
|
|
308
404
|
/** Where one element appears, and what that says about where the fix goes. */
|
|
309
405
|
function whereLines(element, ctx) {
|
|
310
406
|
const shown = element.pages.slice(0, ctx.maxNodes);
|
|
@@ -313,7 +409,7 @@ function whereLines(element, ctx) {
|
|
|
313
409
|
const lines = component === void 0 ? [] : [render(ctx, [{
|
|
314
410
|
text: " written in ",
|
|
315
411
|
paint: ctx.c.dim
|
|
316
|
-
}, { text: component }])];
|
|
412
|
+
}, { text: componentPath(component) }])];
|
|
317
413
|
lines.push(line(ctx, ` on ${count(element.pages.length, "page")}:`, ctx.c.dim));
|
|
318
414
|
for (const page of shown) {
|
|
319
415
|
const source = ctx.sourceFor(page);
|
|
@@ -364,16 +460,22 @@ async function resolvePages(directory, options = {}) {
|
|
|
364
460
|
return {
|
|
365
461
|
pages: crawled.pages,
|
|
366
462
|
origin: crawled.origin,
|
|
367
|
-
label: options.url
|
|
463
|
+
label: options.url,
|
|
464
|
+
completeness: crawled.completeness
|
|
368
465
|
};
|
|
369
466
|
}
|
|
370
467
|
const cwd = options.cwd ?? process.cwd();
|
|
371
468
|
const shown = options.label ?? directory;
|
|
469
|
+
const unreachable = [];
|
|
372
470
|
let pages;
|
|
373
471
|
try {
|
|
374
472
|
pages = await collectPages(directory, {
|
|
375
473
|
...options.include ? { include: options.include } : {},
|
|
376
|
-
...options.exclude ? { exclude: options.exclude } : {}
|
|
474
|
+
...options.exclude ? { exclude: options.exclude } : {},
|
|
475
|
+
onUnreadable: (relativePath, reason) => unreachable.push({
|
|
476
|
+
location: relativePath,
|
|
477
|
+
reason
|
|
478
|
+
})
|
|
377
479
|
});
|
|
378
480
|
} catch (cause) {
|
|
379
481
|
if (!(cause instanceof BuildDirectoryError)) throw cause;
|
|
@@ -385,9 +487,21 @@ async function resolvePages(directory, options = {}) {
|
|
|
385
487
|
warn(await emptyDirectoryHint(shown, cwd));
|
|
386
488
|
return;
|
|
387
489
|
}
|
|
490
|
+
if (unreachable.length > 0) {
|
|
491
|
+
const verb = unreachable.length === 1 ? "file was" : "files were";
|
|
492
|
+
warn(`${unreachable.length} ${verb} not readable, and so not audited:`);
|
|
493
|
+
for (const file of unreachable.slice(0, 10)) note(` ${file.location} — ${file.reason}`);
|
|
494
|
+
if (unreachable.length > 10) note(` …and ${unreachable.length - 10} more`);
|
|
495
|
+
}
|
|
388
496
|
return {
|
|
389
497
|
pages,
|
|
390
|
-
label: shown
|
|
498
|
+
label: shown,
|
|
499
|
+
completeness: {
|
|
500
|
+
discovery: "directory",
|
|
501
|
+
collected: pages.length,
|
|
502
|
+
unreachable,
|
|
503
|
+
truncated: false
|
|
504
|
+
}
|
|
391
505
|
};
|
|
392
506
|
}
|
|
393
507
|
/**
|
|
@@ -397,7 +511,7 @@ async function resolvePages(directory, options = {}) {
|
|
|
397
511
|
* exit 2 — a run that reached no verdict, not a clean one.
|
|
398
512
|
*/
|
|
399
513
|
async function crawlPages(url, options) {
|
|
400
|
-
const { crawlSite, CrawlError, parseEntryUrl } = await import("./crawl-
|
|
514
|
+
const { crawlSite, CrawlError, parseEntryUrl } = await import("./crawl-BiI1Lau_.js");
|
|
401
515
|
let entry;
|
|
402
516
|
try {
|
|
403
517
|
entry = parseEntryUrl(url, options.allowRemote ?? false);
|
|
@@ -412,6 +526,7 @@ async function crawlPages(url, options) {
|
|
|
412
526
|
const result = await crawlSite(entry, {
|
|
413
527
|
...options.allowRemote ? { allowRemote: true } : {},
|
|
414
528
|
...options.ignoreRobots ? { ignoreRobots: true } : {},
|
|
529
|
+
...options.sitemap === void 0 ? {} : { sitemap: options.sitemap },
|
|
415
530
|
...options.maxPages === void 0 ? {} : { maxPages: options.maxPages },
|
|
416
531
|
...options.maxDepth === void 0 ? {} : { maxDepth: options.maxDepth },
|
|
417
532
|
...options.timeoutMs === void 0 ? {} : { timeoutMs: options.timeoutMs }
|
|
@@ -432,7 +547,16 @@ async function crawlPages(url, options) {
|
|
|
432
547
|
if (result.truncated) warn(`Stopped at ${count(result.pages.length, "page")}; the site has more. Raise --max-pages to go further.`);
|
|
433
548
|
return {
|
|
434
549
|
pages: result.pages,
|
|
435
|
-
origin: result.origin
|
|
550
|
+
origin: result.origin,
|
|
551
|
+
completeness: {
|
|
552
|
+
discovery: result.discovery,
|
|
553
|
+
collected: result.pages.length,
|
|
554
|
+
unreachable: result.failures.map((failure) => ({
|
|
555
|
+
location: failure.url,
|
|
556
|
+
reason: failure.reason
|
|
557
|
+
})),
|
|
558
|
+
truncated: result.truncated
|
|
559
|
+
}
|
|
436
560
|
};
|
|
437
561
|
}
|
|
438
562
|
/**
|
|
@@ -444,7 +568,7 @@ async function crawlPages(url, options) {
|
|
|
444
568
|
*/
|
|
445
569
|
async function resolveAutomatically(options) {
|
|
446
570
|
const cwd = options.cwd ?? process.cwd();
|
|
447
|
-
const { autoDetectSource } = await import("./project-
|
|
571
|
+
const { autoDetectSource } = await import("./project-MFrXcw1M.js");
|
|
448
572
|
const detected = await autoDetectSource(cwd, {
|
|
449
573
|
...options.noBuild ? { noBuild: true } : {},
|
|
450
574
|
onStep: note
|
|
@@ -490,7 +614,7 @@ async function runAuditCommand(dir, options = {}) {
|
|
|
490
614
|
audits: [],
|
|
491
615
|
exitCode: 2
|
|
492
616
|
};
|
|
493
|
-
const { pages, origin, label, cleanup } = resolved;
|
|
617
|
+
const { pages, origin, label, cleanup, completeness: collection } = resolved;
|
|
494
618
|
try {
|
|
495
619
|
note(`Auditing ${count(pages.length, "page")} in ${label}${await describeEngine(pages, options)}…`);
|
|
496
620
|
const baseUrl = options.baseUrl ?? origin;
|
|
@@ -515,7 +639,8 @@ async function runAuditCommand(dir, options = {}) {
|
|
|
515
639
|
};
|
|
516
640
|
audits = applied;
|
|
517
641
|
}
|
|
518
|
-
|
|
642
|
+
const completeness = runCompleteness(audits, collection);
|
|
643
|
+
await emit(audits, label, failOn, completeness, options);
|
|
519
644
|
const unaudited = audits.filter((audit) => audit.error);
|
|
520
645
|
if (unaudited.length > 0) {
|
|
521
646
|
fail(`${unaudited.length} of ${audits.length} pages could not be audited`);
|
|
@@ -541,7 +666,7 @@ async function runAuditCommand(dir, options = {}) {
|
|
|
541
666
|
* would be as wrong as silently passing.
|
|
542
667
|
*/
|
|
543
668
|
async function acceptBaseline(audits, options) {
|
|
544
|
-
const { applyBaseline, BaselineError, readBaseline } = await import("./baseline-
|
|
669
|
+
const { applyBaseline, BaselineError, readBaseline } = await import("./baseline-22Y1NWxM.js");
|
|
545
670
|
try {
|
|
546
671
|
const outcome = applyBaseline(audits, await readBaseline(options.baseline, options.cwd ?? process.cwd()));
|
|
547
672
|
if (outcome.accepted > 0) note(`Baseline accepted ${outcome.accepted} violating elements`);
|
|
@@ -568,7 +693,7 @@ async function acceptBaseline(audits, options) {
|
|
|
568
693
|
*/
|
|
569
694
|
async function describeEngine(pages, options) {
|
|
570
695
|
if (options.browser) return " with Chromium";
|
|
571
|
-
const { plannedWorkers } = await import("./pool-
|
|
696
|
+
const { plannedWorkers } = await import("./pool-BMevaLWD.js");
|
|
572
697
|
const workers = options.concurrency ?? plannedWorkers(pages);
|
|
573
698
|
return workers > 1 ? ` across ${workers} threads` : "";
|
|
574
699
|
}
|
|
@@ -577,43 +702,56 @@ async function describeEngine(pages, options) {
|
|
|
577
702
|
* otherwise. Colour is dropped when writing to a file, since escape codes in a
|
|
578
703
|
* saved report are noise.
|
|
579
704
|
*/
|
|
580
|
-
async function emit(audits, dir, failOn, options) {
|
|
581
|
-
const body = await renderReport(audits, dir, failOn, options.format ?? "console", typeof options.output === "string", options);
|
|
705
|
+
async function emit(audits, dir, failOn, completeness, options) {
|
|
706
|
+
const body = await renderReport(audits, dir, failOn, completeness, options.format ?? "console", typeof options.output === "string", options);
|
|
582
707
|
await emitDocument(body, options.output, options.cwd ?? process.cwd());
|
|
583
708
|
if (options.output !== void 0) note(`Report written to ${options.output}`);
|
|
584
709
|
}
|
|
585
|
-
async function renderReport(audits, dir, failOn, format, toFile, options) {
|
|
710
|
+
async function renderReport(audits, dir, failOn, completeness, format, toFile, options) {
|
|
586
711
|
switch (format) {
|
|
587
712
|
case "json": {
|
|
588
|
-
const { buildJsonReport, serialiseJsonReport } = await import("./json-
|
|
713
|
+
const { buildJsonReport, serialiseJsonReport } = await import("./json-QQuFIw1W.js");
|
|
589
714
|
return serialiseJsonReport(buildJsonReport(audits, {
|
|
590
715
|
directory: dir,
|
|
591
716
|
...options.url === void 0 ? {} : { sourceKind: "url" },
|
|
592
717
|
failOn,
|
|
718
|
+
completeness,
|
|
593
719
|
...options.baseUrl ? { baseUrl: options.baseUrl } : {}
|
|
594
720
|
}));
|
|
595
721
|
}
|
|
596
722
|
case "sarif": {
|
|
597
|
-
const { buildSarifReport, serialiseSarifReport } = await import("./sarif-
|
|
598
|
-
return serialiseSarifReport(buildSarifReport(audits, {
|
|
723
|
+
const { buildSarifReport, serialiseSarifReport } = await import("./sarif-SR3_lLYd.js");
|
|
724
|
+
return serialiseSarifReport(buildSarifReport(audits, {
|
|
725
|
+
directory: dir,
|
|
726
|
+
completeness
|
|
727
|
+
}));
|
|
599
728
|
}
|
|
600
729
|
case "html": {
|
|
601
|
-
const { buildHtmlReport } = await import("./html-
|
|
730
|
+
const { buildHtmlReport } = await import("./html-BGTO3ypW.js");
|
|
731
|
+
const framework = await detectedFramework(options.cwd ?? process.cwd());
|
|
602
732
|
return buildHtmlReport(audits, {
|
|
603
733
|
...await sourceLookups(options.cwd ?? process.cwd()),
|
|
604
734
|
directory: dir,
|
|
605
735
|
failOn,
|
|
736
|
+
completeness,
|
|
737
|
+
...framework === void 0 ? {} : { framework },
|
|
606
738
|
...options.baseUrl ? { baseUrl: options.baseUrl } : {}
|
|
607
739
|
});
|
|
608
740
|
}
|
|
609
|
-
case "console":
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
741
|
+
case "console": {
|
|
742
|
+
const framework = await detectedFramework(options.cwd ?? process.cwd());
|
|
743
|
+
return `${formatConsoleReport(audits, {
|
|
744
|
+
...await sourceLookups(options.cwd ?? process.cwd()),
|
|
745
|
+
dir,
|
|
746
|
+
failOn,
|
|
747
|
+
completeness,
|
|
748
|
+
...options.perPage ? { perPage: true } : {},
|
|
749
|
+
...options.manual ? { manual: true } : {},
|
|
750
|
+
...options.coverage ? { coverage: true } : {},
|
|
751
|
+
...framework === void 0 ? {} : { framework },
|
|
752
|
+
...toFile ? { color: false } : {}
|
|
753
|
+
})}\n`;
|
|
754
|
+
}
|
|
617
755
|
}
|
|
618
756
|
}
|
|
619
757
|
/**
|
|
@@ -621,9 +759,15 @@ async function renderReport(audits, dir, failOn, format, toFile, options) {
|
|
|
621
759
|
* so. Best-effort: a project using no convention this recognises gets the
|
|
622
760
|
* report it always got, with no source named.
|
|
623
761
|
*/
|
|
762
|
+
/** The registry id of whatever built this project, for framework-shaped advice. */
|
|
763
|
+
async function detectedFramework(cwd) {
|
|
764
|
+
const { detectFramework } = await import("./frameworks-etFg_O8K.js");
|
|
765
|
+
const { readPackageJson } = await import("./project-MFrXcw1M.js");
|
|
766
|
+
return (await detectFramework(cwd, await readPackageJson(cwd)))?.framework.id;
|
|
767
|
+
}
|
|
624
768
|
async function sourceLookups(cwd) {
|
|
625
|
-
const { buildRouteMap, sourceFor } = await import("./routes-
|
|
626
|
-
const { buildComponentIndex, componentFor } = await import("./component-
|
|
769
|
+
const { buildRouteMap, sourceFor } = await import("./routes-CmdRUuOs.js");
|
|
770
|
+
const { buildComponentIndex, componentFor } = await import("./component-DKd3EHOg.js");
|
|
627
771
|
const routes = await buildRouteMap(cwd);
|
|
628
772
|
const components = await buildComponentIndex(cwd);
|
|
629
773
|
return {
|
|
@@ -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-DB9CZGnV.js";
|
|
2
2
|
export { BaselineError, applyBaseline, readBaseline };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { a as isoDate$1, c as number, l as object, m as string, p as safeParse, s as nullable, t as array, u as optional, v as withDefault } from "./schema-is6CGX2D.js";
|
|
2
2
|
import { t as elementFingerprint } from "./fingerprint-DRoneAjj.js";
|
|
3
|
-
import {
|
|
3
|
+
import { a as findingElements } from "./result-DoamKFsp.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. */
|