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
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import { a as impactRank, i as impactLabel, r as countAtOrAbove } from "./impact-
|
|
2
|
-
import {
|
|
1
|
+
import { a as impactRank, i as impactLabel, r as countAtOrAbove } from "./impact-DZt2oBCP.js";
|
|
2
|
+
import { i as escapeText, n as count, o as standardsReference, r as escapeAttribute, t as collapse } from "./text-CKKpzkYM.js";
|
|
3
3
|
import { t as TOOL_VERSION } from "./version-B3v4rNoG.js";
|
|
4
|
-
import { a as
|
|
5
|
-
import {
|
|
4
|
+
import { a as isShared, i as groupIssues, n as blindRules, o as discoveryLabel, r as coverageParts, s as missedParts, t as remediationFor } from "./remediation-Dtowi2EC.js";
|
|
5
|
+
import { r as componentPath } from "./component-C3GL1Mnu.js";
|
|
6
|
+
import { i as understandingUrl, r as manualCheckFor, t as buildCoverage } from "./coverage-B4IBKMO8.js";
|
|
7
|
+
import { n as buildSummary } from "./json-DjEvy1nX.js";
|
|
6
8
|
import axe from "axe-core";
|
|
7
9
|
//#region src/audit/report/html.ts
|
|
8
10
|
/**
|
|
@@ -56,9 +58,11 @@ ${verdict(audits, failing, options)}
|
|
|
56
58
|
${scoreboard(audits)}
|
|
57
59
|
${issues(audits, options)}
|
|
58
60
|
${runDetails(audits, engine, generatedAt, options)}
|
|
61
|
+
${notMeasured(options)}
|
|
59
62
|
${summary(audits, failing, options)}
|
|
60
63
|
${pages(audits)}
|
|
61
64
|
${notEvaluated(audits)}
|
|
65
|
+
${coverageSection(audits)}
|
|
62
66
|
${footer()}
|
|
63
67
|
</main>
|
|
64
68
|
</body>
|
|
@@ -79,6 +83,8 @@ function verdict(audits, failing, options) {
|
|
|
79
83
|
if (failing > 0) return banner("fail", "Violations found", `${count(failing, "violation")} at or above ${escapeText(options.failOn)}.`);
|
|
80
84
|
const below = totalViolations(audits) - failing;
|
|
81
85
|
if (below > 0) return banner("pass", "No violations at the threshold", `${count(below, "violation")} below ${escapeText(options.failOn)}, which do not fail the run.`);
|
|
86
|
+
const completeness = options.completeness;
|
|
87
|
+
if (completeness && !completeness.complete) return banner("partial", "No violations found, but the whole site was not measured", `${escapeText(missedParts(completeness).join(", "))}. This report describes ${count(completeness.audited, "page")}, not the site.`);
|
|
82
88
|
return banner("pass", "No violations found", "Automated testing found nothing to report.");
|
|
83
89
|
}
|
|
84
90
|
function banner(kind, heading, detail) {
|
|
@@ -94,8 +100,36 @@ function runDetails(audits, engine, generatedAt, options) {
|
|
|
94
100
|
["eaa-kit", `${TOOL_VERSION} · axe-core ${axe.version}`]
|
|
95
101
|
];
|
|
96
102
|
if (options.baseUrl) rows.splice(1, 0, ["Base URL", options.baseUrl]);
|
|
103
|
+
if (options.completeness) rows.splice(2, 0, ["Pages found via", discoveryLabel(options.completeness.discovery)]);
|
|
97
104
|
return `<h2>The run</h2>\n<dl class="run">\n${rows.map(([term, value]) => ` <div><dt>${escapeText(term)}</dt><dd>${escapeText(value)}</dd></div>`).join("\n")}\n</dl>`;
|
|
98
105
|
}
|
|
106
|
+
/** Pages named individually before the rest are counted. */
|
|
107
|
+
const MAX_UNREACHABLE = 20;
|
|
108
|
+
/**
|
|
109
|
+
* The pages this run never reached, named rather than counted away.
|
|
110
|
+
*
|
|
111
|
+
* Named, because a count is not actionable: forty URLs that failed are a broken
|
|
112
|
+
* link check, a server that fell over halfway, or a section behind a login, and
|
|
113
|
+
* which of the three it was is only visible from the addresses. The reasons come
|
|
114
|
+
* straight from the crawler, so a reader can tell a 404 from a timeout.
|
|
115
|
+
*/
|
|
116
|
+
function notMeasured(options) {
|
|
117
|
+
const completeness = options.completeness;
|
|
118
|
+
if (completeness === void 0 || completeness.complete) return "";
|
|
119
|
+
const parts = ["<h2>What this run did not measure</h2>", `<p>This report covers ${count(completeness.audited, "page")}. It is not a description of
|
|
120
|
+
the whole site, and a clean result here does not extend to anything listed below.</p>`];
|
|
121
|
+
if (completeness.truncated) parts.push(`<p class="note">The run stopped at its page limit, so pages beyond ${count(completeness.collected, "page")}
|
|
122
|
+
were never fetched. Raise <code>--max-pages</code> to go further.</p>`);
|
|
123
|
+
if (completeness.errored > 0) parts.push(`<p class="note">${count(completeness.errored, "page")} could not be audited after being
|
|
124
|
+
fetched, and ${completeness.errored === 1 ? "is" : "are"} not counted anywhere in this report.</p>`);
|
|
125
|
+
if (completeness.unreachable.length > 0) {
|
|
126
|
+
const shown = completeness.unreachable.slice(0, MAX_UNREACHABLE).map((item) => ` <li><code>${escapeText(item.location)}</code> — ${escapeText(item.reason)}</li>`).join("\n");
|
|
127
|
+
const rest = completeness.unreachable.length - MAX_UNREACHABLE;
|
|
128
|
+
parts.push(`<p>${count(completeness.unreachable.length, "page")} could not be reached:</p>`, `<ul class="unreachable">\n${shown}\n</ul>`);
|
|
129
|
+
if (rest > 0) parts.push(`<p class="note">…and ${count(rest, "more page")}.</p>`);
|
|
130
|
+
}
|
|
131
|
+
return parts.join("\n");
|
|
132
|
+
}
|
|
99
133
|
/**
|
|
100
134
|
* The counts, taken from the JSON report's own tally rather than recomputed.
|
|
101
135
|
*
|
|
@@ -193,6 +227,60 @@ ${manualBlock(ruleId, finding)}
|
|
|
193
227
|
</li>`).join("\n")}
|
|
194
228
|
</ul>`;
|
|
195
229
|
}
|
|
230
|
+
/**
|
|
231
|
+
* How much of the standard this run could reach.
|
|
232
|
+
*
|
|
233
|
+
* Always included, and placed last, where a reader who has been through the
|
|
234
|
+
* findings arrives at the limits of what produced them. The counts are shown as
|
|
235
|
+
* four separate figures and never as a ratio: `no automated rule` is the
|
|
236
|
+
* majority of WCAG, and a percentage would present a limit of automated testing
|
|
237
|
+
* as a property of this site.
|
|
238
|
+
*/
|
|
239
|
+
function coverageSection(audits) {
|
|
240
|
+
const coverage = buildCoverage(audits);
|
|
241
|
+
const rows = coverage.criteria.map((criterion) => {
|
|
242
|
+
const url = understandingUrl(criterion.number);
|
|
243
|
+
const name = escapeText(`${criterion.number} ${criterion.title}`);
|
|
244
|
+
const linked = url === void 0 ? name : `<a href="${escapeAttribute(url)}">${name}</a>`;
|
|
245
|
+
return ` <tr class="${escapeAttribute(criterion.status)}">
|
|
246
|
+
<td>${linked}</td>
|
|
247
|
+
<td>${escapeText(criterion.level)}</td>
|
|
248
|
+
<td>${statusText(criterion)}</td>
|
|
249
|
+
</tr>`;
|
|
250
|
+
}).join("\n");
|
|
251
|
+
return `<h2>Coverage of WCAG 2.2 AA</h2>
|
|
252
|
+
<p>Of the ${coverage.total} success criteria at Levels A and AA,
|
|
253
|
+
<strong>${coverage.noAutomatedRule}</strong> cannot be checked by any automated engine and
|
|
254
|
+
need a person. This run reached a verdict on <strong>${coverage.evaluated}</strong>.</p>
|
|
255
|
+
<ul class="counts">
|
|
256
|
+
<li><strong>${coverage.evaluated}</strong> evaluated here</li>
|
|
257
|
+
<li><strong>${coverage.notEvaluated}</strong> not evaluated by this engine${coverage.browserWouldAnswer > 0 ? ` — a run with a real browser would answer ${coverage.browserWouldAnswer} of them` : ""}</li>
|
|
258
|
+
<li><strong>${coverage.nothingToCheck}</strong> had rules that ran and found nothing on this site to check</li>
|
|
259
|
+
<li><strong>${coverage.noAutomatedRule}</strong> have no automated rule at all</li>
|
|
260
|
+
</ul>
|
|
261
|
+
<p class="note">
|
|
262
|
+
These four are counted separately and never added together or divided into a score. Most
|
|
263
|
+
of WCAG cannot be automated, and a percentage here would present that limit of automated
|
|
264
|
+
testing as though it were a measurement of this site.
|
|
265
|
+
</p>
|
|
266
|
+
<div class="scroll">
|
|
267
|
+
<table class="coverage">
|
|
268
|
+
<thead><tr><th>Success criterion</th><th>Level</th><th>This run</th></tr></thead>
|
|
269
|
+
<tbody>
|
|
270
|
+
${rows}
|
|
271
|
+
</tbody>
|
|
272
|
+
</table>
|
|
273
|
+
</div>`;
|
|
274
|
+
}
|
|
275
|
+
function statusText(criterion) {
|
|
276
|
+
const browser = criterion.browserWouldAnswer ? " <em>(a real browser would answer this)</em>" : "";
|
|
277
|
+
switch (criterion.status) {
|
|
278
|
+
case "evaluated": return `Evaluated${browser}`;
|
|
279
|
+
case "not-evaluated": return `This engine could not evaluate it${browser}`;
|
|
280
|
+
case "nothing-to-check": return "Rules ran and found nothing on this site to check";
|
|
281
|
+
case "no-automated-rule": return "No automated rule exists; a person must check it";
|
|
282
|
+
}
|
|
283
|
+
}
|
|
196
284
|
function footer() {
|
|
197
285
|
return `<hr>
|
|
198
286
|
<footer>
|
|
@@ -242,6 +330,7 @@ li + li { margin-top: 0.5rem; }
|
|
|
242
330
|
.verdict.pass { background: #eef7ee; border-color: #216e39; }
|
|
243
331
|
.verdict.fail { background: #fdeeee; border-color: #a01b1b; }
|
|
244
332
|
.verdict.broken { background: #fdf4e3; border-color: #8a5a00; }
|
|
333
|
+
.verdict.partial { background: #fdf4e3; border-color: #8a5a00; }
|
|
245
334
|
.badge { display: inline-block; padding: 0 0.45rem; border-radius: 3px; font-size: 0.8rem;
|
|
246
335
|
font-weight: 600; border: 1px solid; }
|
|
247
336
|
.badge.critical { background: #fdeeee; border-color: #a01b1b; color: #7a1414; }
|
|
@@ -261,6 +350,17 @@ p.rule { margin-bottom: 0.25rem; }
|
|
|
261
350
|
p.standards, p.coverage, .reason, li.more, p.accepted-heading, ul.accepted { color: #4a4a4a; font-size: 0.9rem; }
|
|
262
351
|
p.coverage { margin-top: 0.5rem; }
|
|
263
352
|
ul.nodes { list-style: none; padding-left: 0; }
|
|
353
|
+
ul.unreachable { padding-left: 1.25rem; }
|
|
354
|
+
.remediation { border-left: 3px solid #c9c9c9; padding: 0.1rem 0 0.1rem 0.75rem; margin: 0.5rem 0; }
|
|
355
|
+
.remediation .why { margin: 0.3rem 0; opacity: 0.85; }
|
|
356
|
+
.remediation .fix { margin: 0.3rem 0; }
|
|
357
|
+
pre.suggested { background: #eef7ee; border-left: 3px solid #216e39; padding: 0.5rem; overflow-x: auto; }
|
|
358
|
+
.scroll { overflow-x: auto; }
|
|
359
|
+
table.coverage { border-collapse: collapse; width: 100%; font-size: 0.95em; }
|
|
360
|
+
table.coverage th, table.coverage td { text-align: left; padding: 0.3rem 0.6rem; border-bottom: 1px solid #e3e3e3; vertical-align: top; }
|
|
361
|
+
table.coverage tr.evaluated td:last-child { color: #216e39; }
|
|
362
|
+
table.coverage em { font-style: normal; opacity: 0.75; }
|
|
363
|
+
ul.unreachable li { margin-bottom: 0.2rem; overflow-wrap: anywhere; }
|
|
264
364
|
code.selector { color: #4a4a4a; }
|
|
265
365
|
p.clean { color: #216e39; }
|
|
266
366
|
p.note { font-size: 0.95rem; }
|
|
@@ -306,6 +406,11 @@ ul.page-list { margin: 0.35rem 0 0; padding-left: 1.25rem; }
|
|
|
306
406
|
.verdict.pass { background: #10240f; border-color: #7ee2a8; }
|
|
307
407
|
.verdict.fail { background: #2b1111; border-color: #ff9d9d; }
|
|
308
408
|
.verdict.broken { background: #2b2310; border-color: #ffd28a; }
|
|
409
|
+
.verdict.partial { background: #2b2310; border-color: #ffd28a; }
|
|
410
|
+
table.coverage th, table.coverage td { border-bottom-color: #333; }
|
|
411
|
+
table.coverage tr.evaluated td:last-child { color: #7ee2a8; }
|
|
412
|
+
.remediation { border-left-color: #555; }
|
|
413
|
+
pre.suggested { background: #10240f; border-left-color: #7ee2a8; }
|
|
309
414
|
.tile.critical { background: #2b1111; border-color: #ff9d9d; color: #ffc9c9; }
|
|
310
415
|
.tile.serious { background: #2b1d11; border-color: #ffb98a; color: #ffd7bd; }
|
|
311
416
|
.tile.moderate { background: #2b2610; border-color: #f0d264; color: #f5e3a4; }
|
|
@@ -377,17 +482,37 @@ function issueSection(issue, options) {
|
|
|
377
482
|
return `<li class="issue">
|
|
378
483
|
<h3><span class="badge ${impact}">${impact}</span> <code>${escapeText(issue.ruleId)}</code> ${criteria}</h3>
|
|
379
484
|
<p class="help">${escapeText(issue.help)}</p>
|
|
485
|
+
${remediationBlock(issue.ruleId, issue.elements[0]?.html, options)}
|
|
380
486
|
${issue.elements.slice(0, MAX_NODES).map((element) => issueElement(element, options)).join("\n")}
|
|
381
487
|
${issue.elements.length > MAX_NODES ? `<p class="more">…and ${count(issue.elements.length - MAX_NODES, "more element")}</p>` : ""}
|
|
382
488
|
</li>`;
|
|
383
489
|
}
|
|
490
|
+
/**
|
|
491
|
+
* What to do about the rule, beside the finding rather than behind a link.
|
|
492
|
+
*
|
|
493
|
+
* The corrected line is built from the element that actually failed. A textbook
|
|
494
|
+
* snippet is a second thing to translate before anybody can use it, and this
|
|
495
|
+
* document is read by people who did not run the audit and may not write the
|
|
496
|
+
* code either — the closer it gets to the line as it should have been, the less
|
|
497
|
+
* of that translation is left to them.
|
|
498
|
+
*/
|
|
499
|
+
function remediationBlock(ruleId, html, options) {
|
|
500
|
+
const remediation = remediationFor(ruleId, options.framework);
|
|
501
|
+
if (remediation === void 0) return "";
|
|
502
|
+
const example = html === void 0 ? void 0 : remediation.example?.(html);
|
|
503
|
+
return `<div class="remediation">
|
|
504
|
+
<p class="why">${escapeText(remediation.why)}</p>
|
|
505
|
+
<p class="fix"><strong>Fix.</strong> ${escapeText(remediation.fix)}</p>
|
|
506
|
+
${example === void 0 ? "" : `<pre class="suggested"><code>${escapeText(collapse(example, MAX_SNIPPET))}</code></pre>`}
|
|
507
|
+
</div>`;
|
|
508
|
+
}
|
|
384
509
|
function issueElement(element, options) {
|
|
385
510
|
const sources = [...new Set(element.pages.map((page) => options.sourceFor?.(page)).filter((source) => source !== void 0))];
|
|
386
511
|
const list = element.pages.map((page) => `<li>${escapeText(page)}</li>`).join("");
|
|
387
512
|
const component = options.componentFor?.(element.html);
|
|
388
513
|
return `<div class="element">
|
|
389
514
|
<pre><code>${escapeText(collapse(element.html, MAX_SNIPPET))}</code></pre>
|
|
390
|
-
${component === void 0 ? "" : `<p class="written-in">Written in <code>${escapeText(component)}</code></p>`}
|
|
515
|
+
${component === void 0 ? "" : `<p class="written-in">Written in <code>${escapeText(componentPath(component))}</code></p>`}
|
|
391
516
|
${element.selector === "" ? "" : `<p class="selector"><code>${escapeText(element.selector)}</code></p>`}
|
|
392
517
|
<p class="reach">Found on ${count(element.pages.length, "page")}.${isShared(element) ? " <strong>Identical on each — likely one shared component.</strong>" : ""}</p>
|
|
393
518
|
${sources.length > 0 ? `<p class="sources">Source: ${sources.map((source) => `<code>${escapeText(source)}</code>`).join(", ")}</p>` : ""}
|
|
@@ -43,4 +43,4 @@ function countAtOrAbove(audits, threshold) {
|
|
|
43
43
|
return total;
|
|
44
44
|
}
|
|
45
45
|
//#endregion
|
|
46
|
-
export { impactRank as a, impactLabel as i, IMPACT_LEVELS as n, isImpactLevel as o, countAtOrAbove as r, DEFAULT_FAIL_ON as t };
|
|
46
|
+
export { impactRank as a, impactLabel as i, IMPACT_LEVELS as n, isImpactLevel as o, countAtOrAbove as r, meetsThreshold as s, DEFAULT_FAIL_ON as t };
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { a as COMPLIANCE_STATUSES, c as ISSUE_REASONS, d as defineConfig, f as parseConfig, i as ASSESSMENT_METHODS, l as STATEMENT_LOCALES, n as findConfigFile, o as COUNTRIES, r as loadConfig, s as ConfigError, t as CONFIG_FILENAMES, u as configSchema } from "./load-
|
|
2
|
-
import { a as summariseAuditReport, i as readAuditReport, n as toHtmlBody, o as StatementError, r as toHtmlDocument, t as renderStatement } from "./render-
|
|
1
|
+
import { a as COMPLIANCE_STATUSES, c as ISSUE_REASONS, d as defineConfig, f as parseConfig, i as ASSESSMENT_METHODS, l as STATEMENT_LOCALES, n as findConfigFile, o as COUNTRIES, r as loadConfig, s as ConfigError, t as CONFIG_FILENAMES, u as configSchema } from "./load-5wRGLvub.js";
|
|
2
|
+
import { a as summariseAuditReport, i as readAuditReport, n as toHtmlBody, o as StatementError, r as toHtmlDocument, t as renderStatement } from "./render-DrvXRCEn.js";
|
|
3
3
|
export { ASSESSMENT_METHODS, COMPLIANCE_STATUSES, CONFIG_FILENAMES, COUNTRIES, ConfigError, ISSUE_REASONS, STATEMENT_LOCALES, StatementError, configSchema, defineConfig, findConfigFile, loadConfig, parseConfig, readAuditReport, renderStatement, summariseAuditReport, toHtmlBody, toHtmlDocument };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { o as COUNTRIES, t as CONFIG_FILENAMES } from "./load-
|
|
1
|
+
import { o as COUNTRIES, t as CONFIG_FILENAMES } from "./load-5wRGLvub.js";
|
|
2
2
|
import { t as exists } from "./fs-BmPtmFke.js";
|
|
3
|
-
import { i as note, o as warn, r as fail } from "./command-
|
|
3
|
+
import { i as note, o as warn, r as fail } from "./command-D8l_oYbV.js";
|
|
4
4
|
import { readFile, writeFile } from "node:fs/promises";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import pc from "picocolors";
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { c as runOptions, i as failedPage, l as shapeResults, o as pageUrl, r as blindRulesInScope, t as DEFAULT_TAGS } from "./result-DoamKFsp.js";
|
|
2
|
+
import axe from "axe-core";
|
|
3
|
+
import { Script } from "node:vm";
|
|
4
|
+
import { JSDOM, VirtualConsole } from "jsdom";
|
|
5
|
+
//#region src/audit/runners/jsdom.ts
|
|
6
|
+
/** Per-page ceiling; one pathological document must not stall a CI run. */
|
|
7
|
+
const DEFAULT_TIMEOUT_MS = 3e4;
|
|
8
|
+
/**
|
|
9
|
+
* Audit collected pages with axe-core inside jsdom.
|
|
10
|
+
*
|
|
11
|
+
* Pages are processed sequentially: jsdom parsing and axe-core are both
|
|
12
|
+
* CPU-bound on the main thread, so concurrency buys nothing here. A page that
|
|
13
|
+
* throws or times out is recorded with an `error` and the run continues.
|
|
14
|
+
*/
|
|
15
|
+
async function runJsdomAudit(pages, options = {}) {
|
|
16
|
+
const audits = [];
|
|
17
|
+
for (const page of pages) audits.push(await auditPage(page, options));
|
|
18
|
+
return audits;
|
|
19
|
+
}
|
|
20
|
+
async function auditPage(page, options = {}) {
|
|
21
|
+
const tags = options.tags ?? DEFAULT_TAGS;
|
|
22
|
+
const url = pageUrl(page, options.baseUrl);
|
|
23
|
+
const startedAt = Date.now();
|
|
24
|
+
const identity = {
|
|
25
|
+
relativePath: page.relativePath,
|
|
26
|
+
absolutePath: page.absolutePath,
|
|
27
|
+
url,
|
|
28
|
+
engine: "jsdom"
|
|
29
|
+
};
|
|
30
|
+
let dom;
|
|
31
|
+
try {
|
|
32
|
+
dom = createDom(page.html, url);
|
|
33
|
+
injectAxe(dom);
|
|
34
|
+
const { axe: pageAxe } = dom.window;
|
|
35
|
+
const results = await withTimeout(pageAxe.run(dom.window.document, runOptions(tags)), options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
36
|
+
return shapeResults(results, {
|
|
37
|
+
...identity,
|
|
38
|
+
durationMs: Date.now() - startedAt,
|
|
39
|
+
blind: blindRulesInScope(tags)
|
|
40
|
+
});
|
|
41
|
+
} catch (cause) {
|
|
42
|
+
return failedPage({
|
|
43
|
+
...identity,
|
|
44
|
+
durationMs: Date.now() - startedAt
|
|
45
|
+
}, cause instanceof Error ? cause.message : String(cause));
|
|
46
|
+
} finally {
|
|
47
|
+
dom?.window.close();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function createDom(html, url) {
|
|
51
|
+
const virtualConsole = new VirtualConsole();
|
|
52
|
+
virtualConsole.on("jsdomError", () => {});
|
|
53
|
+
return new JSDOM(html, {
|
|
54
|
+
url,
|
|
55
|
+
virtualConsole,
|
|
56
|
+
runScripts: "outside-only",
|
|
57
|
+
pretendToBeVisual: true
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* axe-core is 1.3 MB of source. Compiling it once and re-running the compiled
|
|
62
|
+
* script in each window's context avoids re-parsing it for every page.
|
|
63
|
+
*/
|
|
64
|
+
let axeScript;
|
|
65
|
+
function injectAxe(dom) {
|
|
66
|
+
axeScript ??= new Script(axe.source, { filename: "axe-core.js" });
|
|
67
|
+
axeScript.runInContext(dom.getInternalVMContext());
|
|
68
|
+
}
|
|
69
|
+
async function withTimeout(promise, ms) {
|
|
70
|
+
promise.catch(() => {});
|
|
71
|
+
let timer;
|
|
72
|
+
try {
|
|
73
|
+
return await Promise.race([promise, new Promise((_resolve, reject) => {
|
|
74
|
+
timer = setTimeout(() => reject(/* @__PURE__ */ new Error(`axe-core timed out after ${ms}ms`)), ms);
|
|
75
|
+
})]);
|
|
76
|
+
} finally {
|
|
77
|
+
clearTimeout(timer);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
//#endregion
|
|
81
|
+
export { runJsdomAudit as n, auditPage as t };
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { i as impactLabel, o as isImpactLevel, r as countAtOrAbove } from "./impact-
|
|
1
|
+
import { i as impactLabel, o as isImpactLevel, r as countAtOrAbove } from "./impact-DZt2oBCP.js";
|
|
2
2
|
import { t as TOOL_VERSION } from "./version-B3v4rNoG.js";
|
|
3
|
-
import {
|
|
3
|
+
import { t as elementFingerprint } from "./fingerprint-DRoneAjj.js";
|
|
4
|
+
import { s as ruleOutcomes } from "./result-DoamKFsp.js";
|
|
5
|
+
import { t as buildCoverage } from "./coverage-B4IBKMO8.js";
|
|
4
6
|
import axe from "axe-core";
|
|
5
7
|
/**
|
|
6
8
|
* Build the machine-readable report.
|
|
@@ -30,6 +32,8 @@ function buildJsonReport(audits, options) {
|
|
|
30
32
|
baseUrl: options.baseUrl ?? null
|
|
31
33
|
},
|
|
32
34
|
summary: buildSummary(audits, options.failOn),
|
|
35
|
+
completeness: options.completeness,
|
|
36
|
+
coverage: buildCoverage(audits),
|
|
33
37
|
rules: buildRuleIndex(audits),
|
|
34
38
|
pages: audits.map(toJsonPage)
|
|
35
39
|
};
|
|
@@ -115,7 +119,8 @@ function toJsonFinding(finding) {
|
|
|
115
119
|
nodes: finding.nodes.map((node) => ({
|
|
116
120
|
html: node.html,
|
|
117
121
|
target: node.target,
|
|
118
|
-
failureSummary: node.failureSummary ?? null
|
|
122
|
+
failureSummary: node.failureSummary ?? null,
|
|
123
|
+
fingerprint: elementFingerprint(finding.ruleId, node.target.join(" "), node.html)
|
|
119
124
|
}))
|
|
120
125
|
};
|
|
121
126
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { r as serialiseJsonReport, t as buildJsonReport } from "./json-
|
|
1
|
+
import { r as serialiseJsonReport, t as buildJsonReport } from "./json-DjEvy1nX.js";
|
|
2
2
|
export { buildJsonReport, serialiseJsonReport };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { _ as
|
|
1
|
+
import { _ as url, a as isoDate, d as pipe, g as union, h as transform, i as enumeration, l as object, m as string, p as safeParse, r as email, t as array, u as optional, v as withDefault } from "./schema-is6CGX2D.js";
|
|
2
2
|
import { r as isFile } from "./fs-BmPtmFke.js";
|
|
3
3
|
import { readFile } from "node:fs/promises";
|
|
4
4
|
import path from "node:path";
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { n as IntegrationOptions, t as BuildAuditError } from "../run-BMASMmwO.js";
|
|
2
|
+
//#region src/nuxt/index.d.ts
|
|
3
|
+
/** The part of the Nitro instance this reads, handed over by `nitro:init`. */
|
|
4
|
+
interface NitroLike {
|
|
5
|
+
options: {
|
|
6
|
+
output?: {
|
|
7
|
+
publicDir?: string;
|
|
8
|
+
};
|
|
9
|
+
/** True for `nuxt generate`; absent for a server build. */
|
|
10
|
+
static?: boolean;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
interface NuxtOptionsLike {
|
|
14
|
+
rootDir?: string;
|
|
15
|
+
}
|
|
16
|
+
interface NuxtLike {
|
|
17
|
+
options: NuxtOptionsLike;
|
|
18
|
+
hook(name: 'nitro:init', handler: (nitro: NitroLike) => void): void;
|
|
19
|
+
hook(name: 'close', handler: () => Promise<void>): void;
|
|
20
|
+
}
|
|
21
|
+
interface EaaKitNuxtOptions extends IntegrationOptions {
|
|
22
|
+
/** Directory to audit. Defaults to the one Nitro says it wrote. */
|
|
23
|
+
directory?: string;
|
|
24
|
+
/**
|
|
25
|
+
* Let a server build pass without auditing anything.
|
|
26
|
+
*
|
|
27
|
+
* Off by default: `nuxt build` writes no browsable HTML, and a silent pass
|
|
28
|
+
* over a directory with no pages in it is indistinguishable from a clean
|
|
29
|
+
* site.
|
|
30
|
+
*/
|
|
31
|
+
allowServerBuild?: boolean;
|
|
32
|
+
}
|
|
33
|
+
declare function eaaKitModule(options?: EaaKitNuxtOptions, nuxt?: NuxtLike): void;
|
|
34
|
+
//#endregion
|
|
35
|
+
export { BuildAuditError, EaaKitNuxtOptions, NitroLike, NuxtLike, NuxtOptionsLike, eaaKitModule as default };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { n as auditBuild, r as stderrLogger, t as BuildAuditError } from "../run-DB34BSOZ.js";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
//#region src/nuxt/index.ts
|
|
4
|
+
function eaaKitModule(options = {}, nuxt) {
|
|
5
|
+
if (nuxt === void 0) return;
|
|
6
|
+
let publicDir;
|
|
7
|
+
let prerendered = false;
|
|
8
|
+
nuxt.hook("nitro:init", (nitro) => {
|
|
9
|
+
publicDir = nitro.options.output?.publicDir;
|
|
10
|
+
prerendered = nitro.options.static === true;
|
|
11
|
+
});
|
|
12
|
+
nuxt.hook("close", async () => {
|
|
13
|
+
const directory = options.directory ?? publicDir;
|
|
14
|
+
if (options.directory === void 0 && !prerendered) {
|
|
15
|
+
if (options.allowServerBuild === true) return;
|
|
16
|
+
throw new BuildAuditError("eaa-kit: this was a server build, so no pages were written to disk and there was nothing to audit. Run `nuxt generate` to prerender them, audit the running site with `eaa-kit audit --url`, or set allowServerBuild to skip this.");
|
|
17
|
+
}
|
|
18
|
+
if (directory === void 0) throw new BuildAuditError("eaa-kit: Nitro reported no public directory, so there is no path to audit. Pass `directory` to the module to name one.");
|
|
19
|
+
await auditBuild(path.resolve(nuxt.options.rootDir ?? process.cwd(), directory), options, stderrLogger());
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
//#endregion
|
|
23
|
+
export { BuildAuditError, eaaKitModule as default };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { r as isFile } from "./fs-BmPtmFke.js";
|
|
2
|
-
import {
|
|
2
|
+
import { c as runOptions, i as failedPage, l as shapeResults, o as pageUrl, t as DEFAULT_TAGS } from "./result-DoamKFsp.js";
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import { stat } from "node:fs/promises";
|
|
5
5
|
import path from "node:path";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { r as isFile } from "./fs-BmPtmFke.js";
|
|
2
|
-
import { i as
|
|
2
|
+
import { i as failedPage, o as pageUrl } from "./result-DoamKFsp.js";
|
|
3
3
|
import { Worker } from "node:worker_threads";
|
|
4
4
|
import { availableParallelism } from "node:os";
|
|
5
5
|
//#region src/audit/runners/pool.ts
|
|
@@ -96,7 +96,7 @@ async function runPooledAudit(pages, options = {}) {
|
|
|
96
96
|
return runWorkers(pages, runnerOptions, Math.min(workers, pages.length), entry);
|
|
97
97
|
}
|
|
98
98
|
async function auditHere(pages, options) {
|
|
99
|
-
const { runJsdomAudit } = await import("./jsdom-
|
|
99
|
+
const { runJsdomAudit } = await import("./jsdom-4IMzv0eE.js");
|
|
100
100
|
return runJsdomAudit(pages, options);
|
|
101
101
|
}
|
|
102
102
|
async function runWorkers(pages, options, count, entry) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { n as isDirectory, t as exists } from "./fs-BmPtmFke.js";
|
|
2
|
-
import { r as candidateOutputs } from "./frameworks-
|
|
2
|
+
import { r as candidateOutputs } from "./frameworks-B4ClIJgE.js";
|
|
3
3
|
import { readFile } from "node:fs/promises";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { glob } from "tinyglobby";
|
|
@@ -226,6 +226,12 @@ async function autoDetectSource(cwd, options = {}) {
|
|
|
226
226
|
};
|
|
227
227
|
}
|
|
228
228
|
const pkg = await readPackageJson(cwd);
|
|
229
|
+
const { detectFramework } = await import("./frameworks-etFg_O8K.js");
|
|
230
|
+
const detected = await detectFramework(cwd, pkg);
|
|
231
|
+
if (detected !== void 0 && detected.framework.outputs.length === 0) {
|
|
232
|
+
step(`${detected.framework.name} renders on a server and writes no HTML to disk`);
|
|
233
|
+
return { steps };
|
|
234
|
+
}
|
|
229
235
|
if (pkg === void 0) return void 0;
|
|
230
236
|
const scripts = pkg.scripts ?? {};
|
|
231
237
|
if (options.noBuild) return void 0;
|
|
@@ -265,4 +271,4 @@ async function autoDetectSource(cwd, options = {}) {
|
|
|
265
271
|
};
|
|
266
272
|
}
|
|
267
273
|
//#endregion
|
|
268
|
-
export {
|
|
274
|
+
export { runScript as a, readPackageJson as i, detectPackageManager as n, startServer as o, findBuildOutput as r, autoDetectSource as t };
|