eaa-kit 0.4.0 → 0.6.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 +66 -11
- package/dist/astro/index.d.ts +1 -1
- package/dist/astro/index.js +1 -1
- package/dist/audit/runners/worker.js +5 -1
- package/dist/audit-DK2StT1_.js +2 -0
- package/dist/{audit-CPoZMXGM.js → audit-e_uixhPG.js} +123 -54
- package/dist/baseline-BscHfFSW.js +2 -0
- package/dist/{baseline-DB9CZGnV.js → baseline-D2qWmUC6.js} +40 -12
- package/dist/checklist-BtgkxMEa.js +44 -0
- package/dist/cli/index.js +153 -25
- package/dist/{collect-CFM8gEVv.js → collect-Cu1q9NTB.js} +34 -3
- package/dist/command-CZKp9Wi_.js +251 -0
- package/dist/{remediation-Dtowi2EC.js → completeness-Dwq3CPB3.js} +61 -43
- package/dist/{component-C3GL1Mnu.js → component-ChisoFyY.js} +1 -1
- package/dist/{component-DKd3EHOg.js → component-DcCn3BJS.js} +1 -1
- package/dist/coverage-7bQqWNfN.js +2 -0
- package/dist/{coverage-B4IBKMO8.js → coverage-BD2_CYns.js} +46 -8
- package/dist/{crawl-BiI1Lau_.js → crawl-BHkJmUkA.js} +119 -11
- package/dist/eleventy/index.d.ts +1 -1
- package/dist/eleventy/index.js +1 -1
- package/dist/fingerprint-BjYV_0F7.js +67 -0
- package/dist/{html-BGTO3ypW.js → html-Bmh7X_sM.js} +52 -30
- package/dist/{impact-DZt2oBCP.js → impact-luGCnN8F.js} +11 -1
- package/dist/index.d.ts +187 -5
- package/dist/index.js +3 -3
- package/dist/{init-DIWDE35F.js → init-BjKYttxc.js} +10 -4
- package/dist/{jsdom-DCpGSLfW.js → jsdom-CQar6OQS.js} +12 -4
- package/dist/jsdom-DMvU2h-f.js +3 -0
- package/dist/{json-QQuFIw1W.js → json-HJDgJp09.js} +1 -1
- package/dist/{json-DjEvy1nX.js → json-PNg6BeXp.js} +13 -18
- package/dist/load-Bc_xDGAh.js +2 -0
- package/dist/{load-5wRGLvub.js → load-vZdV_fD1.js} +137 -8
- package/dist/nuxt/index.d.ts +1 -1
- package/dist/nuxt/index.js +1 -1
- package/dist/{playwright-BWniOain.js → playwright-C-miJP2b.js} +32 -7
- package/dist/{pool-BMevaLWD.js → pool-dkexq5xo.js} +52 -2
- package/dist/{render-DrvXRCEn.js → render-CtsqR0k-.js} +150 -11
- package/dist/{result-DoamKFsp.js → result-DfUru6y_.js} +60 -3
- package/dist/review-CdMK2GpQ.js +161 -0
- package/dist/review-DIbOTmOM.js +2 -0
- package/dist/{run-DB34BSOZ.js → run-D7hdVXLA.js} +1 -1
- package/dist/{run-BMASMmwO.d.ts → run-YiqyQA2b.d.ts} +17 -0
- package/dist/{sarif-SR3_lLYd.js → sarif-BKvq_AUm.js} +25 -14
- package/dist/{schema-is6CGX2D.js → schema-DJSF4K05.js} +15 -1
- package/dist/statement/templates/es.en.md +125 -0
- package/dist/statement/templates/es.es.md +127 -0
- package/dist/statement/templates/fr.en.md +128 -0
- package/dist/statement/templates/fr.fr.md +131 -0
- package/dist/statement/templates/it.en.md +127 -0
- package/dist/statement/templates/it.it.md +130 -0
- package/dist/statement/templates/nl.en.md +125 -0
- package/dist/statement/templates/nl.nl.md +127 -0
- package/dist/{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-eUjwYLxC.js +63 -0
- package/package.json +7 -3
- package/dist/audit-CpXH2Mk8.js +0 -2
- package/dist/baseline-22Y1NWxM.js +0 -2
- package/dist/command-D8l_oYbV.js +0 -77
- package/dist/fingerprint-DRoneAjj.js +0 -20
- package/dist/jsdom-4IMzv0eE.js +0 -3
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import pc from "picocolors";
|
|
4
|
+
//#region src/audit/headers.ts
|
|
5
|
+
/**
|
|
6
|
+
* Request headers for the pages this tool fetches.
|
|
7
|
+
*
|
|
8
|
+
* The two places a small agency stages work are the two this tool could not
|
|
9
|
+
* reach: a preview deployment, which every host protects by default, and a CMS
|
|
10
|
+
* staging site behind basic auth or a session cookie. `--url` sent one fixed
|
|
11
|
+
* pair of headers and had no way to add to it, so auditing either meant putting
|
|
12
|
+
* the site on the public internet first.
|
|
13
|
+
*
|
|
14
|
+
* These values are credentials. They are handled on the principle that the tool
|
|
15
|
+
* never writes one down: nothing here reaches a report, a baseline, a SARIF log
|
|
16
|
+
* or the completeness record, and the errors below name the header rather than
|
|
17
|
+
* echoing what was typed, because a malformed `--header` is exactly the case
|
|
18
|
+
* where the value is a token and the terminal is a CI log.
|
|
19
|
+
*
|
|
20
|
+
* For the same reason there is no `headers` key in `eaa.config`. That file is
|
|
21
|
+
* committed; a token in it is a token in the repository, and a tool that
|
|
22
|
+
* offered the field would be inviting that. Pass them as flags — a shell
|
|
23
|
+
* expands `--header "Authorization: Bearer $TOKEN"` — or as the GitHub Action's
|
|
24
|
+
* input, which reads from `secrets`.
|
|
25
|
+
*/
|
|
26
|
+
/** Thrown for a header this cannot parse. Never carries the value. */
|
|
27
|
+
var HeaderError = class extends Error {
|
|
28
|
+
name = "HeaderError";
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* `Name: value` into a pair.
|
|
32
|
+
*
|
|
33
|
+
* Lenient about the space after the colon and about a value containing colons,
|
|
34
|
+
* which `Authorization: Basic …` and a `Cookie` both do. Strict about the name,
|
|
35
|
+
* because a header name is a token by RFC 9110 and anything else is a typo that
|
|
36
|
+
* a server would reject in a way nobody could read.
|
|
37
|
+
*/
|
|
38
|
+
function parseHeader(input) {
|
|
39
|
+
const at = input.indexOf(":");
|
|
40
|
+
if (at <= 0) throw new HeaderError("a header must be written \"Name: value\"");
|
|
41
|
+
const name = input.slice(0, at).trim();
|
|
42
|
+
const value = input.slice(at + 1).trim();
|
|
43
|
+
if (!/^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/.test(name)) throw new HeaderError(`"${name}" is not a valid header name`);
|
|
44
|
+
if (value === "") throw new HeaderError(`the ${name} header was given no value`);
|
|
45
|
+
if (/[\r\n]/.test(value)) throw new HeaderError(`the ${name} header contains a line break`);
|
|
46
|
+
return [name, value];
|
|
47
|
+
}
|
|
48
|
+
/** Several `Name: value` strings into the record the fetchers take. */
|
|
49
|
+
function parseHeaders(inputs) {
|
|
50
|
+
const headers = {};
|
|
51
|
+
for (const input of inputs) {
|
|
52
|
+
const [name, value] = parseHeader(input);
|
|
53
|
+
headers[name] = value;
|
|
54
|
+
}
|
|
55
|
+
return headers;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* `user:password` into an `Authorization` header.
|
|
59
|
+
*
|
|
60
|
+
* Sugar for the header somebody would otherwise have to base64 by hand, which
|
|
61
|
+
* is the one credential a staging site is most likely to be behind. A password
|
|
62
|
+
* may contain colons; a username may not, which is basic auth's own rule.
|
|
63
|
+
*/
|
|
64
|
+
function basicAuth(input) {
|
|
65
|
+
if (input.indexOf(":") <= 0) throw new HeaderError("basic auth must be written \"user:password\"");
|
|
66
|
+
return ["Authorization", `Basic ${Buffer.from(input, "utf8").toString("base64")}`];
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* The headers a run will send, from whichever flags were given.
|
|
70
|
+
*
|
|
71
|
+
* `--basic-auth` first, so an explicit `--header "Authorization: …"` beats it:
|
|
72
|
+
* a typed header is the more specific thing to have asked for.
|
|
73
|
+
*/
|
|
74
|
+
function requestHeaders(options) {
|
|
75
|
+
const headers = {};
|
|
76
|
+
if (options.basicAuth !== void 0) {
|
|
77
|
+
const [name, value] = basicAuth(options.basicAuth);
|
|
78
|
+
headers[name] = value;
|
|
79
|
+
}
|
|
80
|
+
Object.assign(headers, parseHeaders(options.header ?? []));
|
|
81
|
+
return Object.keys(headers).length === 0 ? void 0 : headers;
|
|
82
|
+
}
|
|
83
|
+
//#endregion
|
|
84
|
+
//#region src/cli/command.ts
|
|
85
|
+
/**
|
|
86
|
+
* What every command does around the audit itself: say what is happening, run
|
|
87
|
+
* the engine, and put the document somewhere.
|
|
88
|
+
*
|
|
89
|
+
* `audit` and `baseline` are siblings — one reports what a run found and the
|
|
90
|
+
* other writes it down — so they take the same flags, choose between the same
|
|
91
|
+
* two engines and fail on the same setup problems. Keeping that in one place is
|
|
92
|
+
* what stops the two commands drifting into disagreeing about what a run is.
|
|
93
|
+
*/
|
|
94
|
+
/** Progress and diagnostics go to stderr, so the report can be piped away. */
|
|
95
|
+
function note(message) {
|
|
96
|
+
process.stderr.write(pc.dim(`${message}\n`));
|
|
97
|
+
}
|
|
98
|
+
function warn(message) {
|
|
99
|
+
process.stderr.write(`${pc.yellow("warning")} ${message}\n`);
|
|
100
|
+
}
|
|
101
|
+
function fail(message) {
|
|
102
|
+
process.stderr.write(`${pc.red("error")} ${message}\n`);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Advice the reader should not miss, without the `warning` prefix: nothing has
|
|
106
|
+
* gone wrong, but what happens next is theirs to get right.
|
|
107
|
+
*/
|
|
108
|
+
function advise(message) {
|
|
109
|
+
process.stderr.write(pc.yellow(`${message}\n`));
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Audit the pages with whichever engine was asked for.
|
|
113
|
+
*
|
|
114
|
+
* Returns undefined when the browser was asked for and is not usable, having
|
|
115
|
+
* already said so: Playwright missing is a setup problem with a specific fix,
|
|
116
|
+
* not a crash, and both commands turn it into exit 2.
|
|
117
|
+
*/
|
|
118
|
+
async function runEngine(pages, options) {
|
|
119
|
+
const runnerOptions = {
|
|
120
|
+
cwd: options.cwd,
|
|
121
|
+
...options.baseUrl === void 0 ? {} : { baseUrl: options.baseUrl },
|
|
122
|
+
...options.timeoutMs === void 0 ? {} : { timeoutMs: options.timeoutMs }
|
|
123
|
+
};
|
|
124
|
+
if (!options.browser) {
|
|
125
|
+
const { runPooledAudit } = await import("./pool-dkexq5xo.js");
|
|
126
|
+
return runPooledAudit(pages, {
|
|
127
|
+
...runnerOptions,
|
|
128
|
+
...options.fast ? { fast: true } : {},
|
|
129
|
+
...options.concurrency === void 0 ? {} : { concurrency: options.concurrency }
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
const { BrowserUnavailableError, runBrowserAudit } = await import("./playwright-C-miJP2b.js");
|
|
133
|
+
try {
|
|
134
|
+
return await runBrowserAudit(options.directory, pages, {
|
|
135
|
+
...runnerOptions,
|
|
136
|
+
...options.concurrency === void 0 ? {} : { concurrency: options.concurrency },
|
|
137
|
+
...options.headers === void 0 ? {} : { headers: options.headers }
|
|
138
|
+
});
|
|
139
|
+
} catch (cause) {
|
|
140
|
+
if (cause instanceof BrowserUnavailableError) {
|
|
141
|
+
fail(cause.message);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
throw cause;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Write a document to `output`, or to stdout when there is none. Parent
|
|
149
|
+
* directories are created, since a report path in CI usually names one that is
|
|
150
|
+
* not there yet.
|
|
151
|
+
*/
|
|
152
|
+
async function emitDocument(body, output, cwd) {
|
|
153
|
+
if (output === void 0) {
|
|
154
|
+
process.stdout.write(body);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
const target = path.resolve(cwd, output);
|
|
158
|
+
await mkdir(path.dirname(target), { recursive: true });
|
|
159
|
+
await writeFile(target, body, "utf8");
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Audit defaults from the project's config file.
|
|
163
|
+
*
|
|
164
|
+
* `audit` and `baseline` are run from a build script over and over with the
|
|
165
|
+
* same six flags, and the flags are the only place to say them: the config file
|
|
166
|
+
* has existed since 0.2 and served the statement alone. An `audit` block there
|
|
167
|
+
* is that list written once.
|
|
168
|
+
*
|
|
169
|
+
* Everything it returns is a default. The flags are merged over it by the
|
|
170
|
+
* caller, because the file is the project's usual answer and a flag is somebody
|
|
171
|
+
* asking for something else on this run.
|
|
172
|
+
*
|
|
173
|
+
* No config file at all is not an error, unlike for `statement`: this command
|
|
174
|
+
* has always run against projects that have never heard of one. A file that
|
|
175
|
+
* exists and cannot be read is exit 2 — it was written to be used, and running
|
|
176
|
+
* on different settings than it names would be worse than stopping.
|
|
177
|
+
*/
|
|
178
|
+
async function auditDefaults(options = {}) {
|
|
179
|
+
const { loadAuditConfig } = await import("./load-Bc_xDGAh.js");
|
|
180
|
+
const loaded = await loadAuditConfig({
|
|
181
|
+
...options.cwd ? { cwd: options.cwd } : {},
|
|
182
|
+
...options.config ? { path: options.config } : {}
|
|
183
|
+
});
|
|
184
|
+
if (!loaded?.audit) return {};
|
|
185
|
+
note(`Defaults from ${path.basename(loaded.path)}`);
|
|
186
|
+
return loaded.audit;
|
|
187
|
+
}
|
|
188
|
+
function auditInvocation(dir, defaults, flags) {
|
|
189
|
+
const { build, config: _config, header, basicAuth, ...typed } = flags;
|
|
190
|
+
const { dir: configDir, build: configBuild, headers: _fromFile, ...fromConfig } = defaults;
|
|
191
|
+
const headers = requestHeaders({
|
|
192
|
+
...header ? { header } : {},
|
|
193
|
+
...basicAuth ? { basicAuth } : {}
|
|
194
|
+
});
|
|
195
|
+
return {
|
|
196
|
+
dir: dir ?? configDir,
|
|
197
|
+
options: {
|
|
198
|
+
...fromConfig,
|
|
199
|
+
...typed,
|
|
200
|
+
...build === false || configBuild === false ? { noBuild: true } : {},
|
|
201
|
+
...headers === void 0 ? {} : { headers }
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* The same, for `baseline`, which reads the defaults that mean the same thing
|
|
207
|
+
* to it.
|
|
208
|
+
*
|
|
209
|
+
* Deliberately a subset. `output` names where the report goes for one command
|
|
210
|
+
* and where the baseline goes for the other, so carrying it across would write
|
|
211
|
+
* a baseline over the path somebody set aside for a report; `format`, `failOn`
|
|
212
|
+
* and `baseline` all describe a verdict this command does not reach.
|
|
213
|
+
*/
|
|
214
|
+
function baselineInvocation(dir, defaults, flags) {
|
|
215
|
+
const { config: _config, header, basicAuth, ...typed } = flags;
|
|
216
|
+
const headers = requestHeaders({
|
|
217
|
+
...header ? { header } : {},
|
|
218
|
+
...basicAuth ? { basicAuth } : {}
|
|
219
|
+
});
|
|
220
|
+
return {
|
|
221
|
+
dir: dir ?? defaults.dir ?? "./dist",
|
|
222
|
+
options: {
|
|
223
|
+
...baselineDefaults(defaults),
|
|
224
|
+
...typed,
|
|
225
|
+
...headers === void 0 ? {} : { headers }
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
function baselineDefaults(config) {
|
|
230
|
+
return pick(config, [
|
|
231
|
+
"include",
|
|
232
|
+
"exclude",
|
|
233
|
+
"baseUrl",
|
|
234
|
+
"url",
|
|
235
|
+
"allowRemote",
|
|
236
|
+
"ignoreRobots",
|
|
237
|
+
"sitemap",
|
|
238
|
+
"maxPages",
|
|
239
|
+
"maxDepth",
|
|
240
|
+
"browser",
|
|
241
|
+
"concurrency"
|
|
242
|
+
]);
|
|
243
|
+
}
|
|
244
|
+
/** Copies the keys that are actually set, so nothing spreads an undefined over a real value. */
|
|
245
|
+
function pick(source, keys) {
|
|
246
|
+
const out = {};
|
|
247
|
+
for (const key of keys) if (source[key] !== void 0) out[key] = source[key];
|
|
248
|
+
return out;
|
|
249
|
+
}
|
|
250
|
+
//#endregion
|
|
251
|
+
export { emitDocument as a, runEngine as c, parseHeader as d, baselineInvocation as i, warn as l, auditDefaults as n, fail as o, auditInvocation as r, note as s, advise as t, basicAuth as u };
|
|
@@ -1,45 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { t as elementFingerprint } from "./fingerprint-
|
|
3
|
-
import {
|
|
4
|
-
//#region src/audit/completeness.ts
|
|
5
|
-
/**
|
|
6
|
-
* Fold what the collector knew together with what the engine managed.
|
|
7
|
-
*
|
|
8
|
-
* The audits carry the second half: a page with an `error` was collected and
|
|
9
|
-
* then not audited, and is as unmeasured as one that was never fetched.
|
|
10
|
-
*/
|
|
11
|
-
function runCompleteness(audits, collection) {
|
|
12
|
-
const errored = audits.filter((audit) => audit.error).length;
|
|
13
|
-
return {
|
|
14
|
-
...collection,
|
|
15
|
-
audited: audits.length - errored,
|
|
16
|
-
errored,
|
|
17
|
-
complete: collection.unreachable.length === 0 && !collection.truncated && errored === 0
|
|
18
|
-
};
|
|
19
|
-
}
|
|
20
|
-
/**
|
|
21
|
-
* What was missed, as phrases both reports print.
|
|
22
|
-
*
|
|
23
|
-
* Separate clauses rather than one total, for the same reason the coverage
|
|
24
|
-
* parts are: a page that could not be fetched and a page that could not be
|
|
25
|
-
* parsed are different problems with different fixes, and summing them would
|
|
26
|
-
* name neither.
|
|
27
|
-
*/
|
|
28
|
-
function missedParts(completeness) {
|
|
29
|
-
const parts = [];
|
|
30
|
-
if (completeness.unreachable.length > 0) parts.push(`${completeness.unreachable.length} could not be reached`);
|
|
31
|
-
if (completeness.errored > 0) parts.push(`${completeness.errored} could not be audited`);
|
|
32
|
-
if (completeness.truncated) parts.push("the run stopped at its page limit");
|
|
33
|
-
return parts;
|
|
34
|
-
}
|
|
35
|
-
/** How the pages were found, in words, for the run details both reports show. */
|
|
36
|
-
function discoveryLabel(discovery) {
|
|
37
|
-
switch (discovery) {
|
|
38
|
-
case "directory": return "files in the build directory";
|
|
39
|
-
case "sitemap": return "sitemap.xml and links";
|
|
40
|
-
case "links": return "links from the entry page";
|
|
41
|
-
}
|
|
42
|
-
}
|
|
1
|
+
import { o as impactRank } from "./impact-luGCnN8F.js";
|
|
2
|
+
import { t as elementFingerprint } from "./fingerprint-BjYV_0F7.js";
|
|
3
|
+
import { o as findingElements } from "./result-DfUru6y_.js";
|
|
43
4
|
/** Whether this element looks like one component rendered on many pages. */
|
|
44
5
|
function isShared(element) {
|
|
45
6
|
return element.pages.length >= 3;
|
|
@@ -95,6 +56,23 @@ function groupIssues(audits) {
|
|
|
95
56
|
return issues;
|
|
96
57
|
}
|
|
97
58
|
/**
|
|
59
|
+
* What the grouped issues add up to: distinct elements, and the violations they
|
|
60
|
+
* account for. Both reports open their issues section with these two numbers,
|
|
61
|
+
* and say something different when they are equal.
|
|
62
|
+
*/
|
|
63
|
+
function issueTotals(issues) {
|
|
64
|
+
let elements = 0;
|
|
65
|
+
let occurrences = 0;
|
|
66
|
+
for (const issue of issues) {
|
|
67
|
+
elements += issue.elements.length;
|
|
68
|
+
occurrences += issue.occurrences;
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
elements,
|
|
72
|
+
occurrences
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
98
76
|
* Unevaluated rules folded across the site, sorted by rule id.
|
|
99
77
|
*
|
|
100
78
|
* Both reports list these once at the end rather than under every page: on a
|
|
@@ -318,4 +296,44 @@ function remediationFor(ruleId, framework) {
|
|
|
318
296
|
};
|
|
319
297
|
}
|
|
320
298
|
//#endregion
|
|
321
|
-
|
|
299
|
+
//#region src/audit/completeness.ts
|
|
300
|
+
/**
|
|
301
|
+
* Fold what the collector knew together with what the engine managed.
|
|
302
|
+
*
|
|
303
|
+
* The audits carry the second half: a page with an `error` was collected and
|
|
304
|
+
* then not audited, and is as unmeasured as one that was never fetched.
|
|
305
|
+
*/
|
|
306
|
+
function runCompleteness(audits, collection) {
|
|
307
|
+
const errored = audits.filter((audit) => audit.error).length;
|
|
308
|
+
return {
|
|
309
|
+
...collection,
|
|
310
|
+
audited: audits.length - errored,
|
|
311
|
+
errored,
|
|
312
|
+
complete: collection.unreachable.length === 0 && !collection.truncated && errored === 0
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* What was missed, as phrases both reports print.
|
|
317
|
+
*
|
|
318
|
+
* Separate clauses rather than one total, for the same reason the coverage
|
|
319
|
+
* parts are: a page that could not be fetched and a page that could not be
|
|
320
|
+
* parsed are different problems with different fixes, and summing them would
|
|
321
|
+
* name neither.
|
|
322
|
+
*/
|
|
323
|
+
function missedParts(completeness) {
|
|
324
|
+
const parts = [];
|
|
325
|
+
if (completeness.unreachable.length > 0) parts.push(`${completeness.unreachable.length} could not be reached`);
|
|
326
|
+
if (completeness.errored > 0) parts.push(`${completeness.errored} could not be audited`);
|
|
327
|
+
if (completeness.truncated) parts.push("the run stopped at its page limit");
|
|
328
|
+
return parts;
|
|
329
|
+
}
|
|
330
|
+
/** How the pages were found, in words, for the run details both reports show. */
|
|
331
|
+
function discoveryLabel(discovery) {
|
|
332
|
+
switch (discovery) {
|
|
333
|
+
case "directory": return "files in the build directory";
|
|
334
|
+
case "sitemap": return "sitemap.xml and links";
|
|
335
|
+
case "links": return "links from the entry page";
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
//#endregion
|
|
339
|
+
export { blindRules as a, isShared as c, remediationFor as i, issueTotals as l, missedParts as n, coverageParts as o, runCompleteness as r, groupIssues as s, discoveryLabel as t };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { i as toPosix } from "./fs-BmPtmFke.js";
|
|
2
|
-
import { t as collapse } from "./text-
|
|
2
|
+
import { t as collapse } from "./text-BF1LHMiV.js";
|
|
3
3
|
import { readFile } from "node:fs/promises";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { glob } from "tinyglobby";
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as componentFor, t as buildComponentIndex } from "./component-
|
|
1
|
+
import { n as componentFor, t as buildComponentIndex } from "./component-ChisoFyY.js";
|
|
2
2
|
export { buildComponentIndex, componentFor };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { d as successCriteria, n as DEFAULT_TAGS, r as ENGINE_BLIND_RULES } from "./result-DfUru6y_.js";
|
|
2
|
+
import { s as criterionReview } from "./review-CdMK2GpQ.js";
|
|
2
3
|
import axe from "axe-core";
|
|
3
4
|
//#region src/audit/manual.ts
|
|
4
5
|
/**
|
|
@@ -463,10 +464,11 @@ function blindedRules(audits) {
|
|
|
463
464
|
* the criterion is met on this site, and saying otherwise would be a coverage
|
|
464
465
|
* claim resting on an empty page.
|
|
465
466
|
*/
|
|
466
|
-
function buildCoverage(audits, tags = DEFAULT_TAGS) {
|
|
467
|
+
function buildCoverage(audits, tags = DEFAULT_TAGS, review) {
|
|
467
468
|
const byCriterion = rulesByCriterion(tags);
|
|
468
469
|
const decided = decidedRules(audits);
|
|
469
470
|
const blinded = blindedRules(audits);
|
|
471
|
+
const jsdomBlindApplies = audits.length === 0 || audits.some((audit) => audit.engine !== "browser");
|
|
470
472
|
const criteria = WCAG22_AA_CRITERIA.map((criterion) => {
|
|
471
473
|
const rules = byCriterion.get(criterion.number) ?? [];
|
|
472
474
|
if (rules.length === 0) return {
|
|
@@ -481,7 +483,7 @@ function buildCoverage(audits, tags = DEFAULT_TAGS) {
|
|
|
481
483
|
rules,
|
|
482
484
|
browserWouldAnswer: false
|
|
483
485
|
};
|
|
484
|
-
const engineBlind = rules.filter((rule) => blinded.has(rule) || ENGINE_BLIND_RULES[rule] !== void 0);
|
|
486
|
+
const engineBlind = rules.filter((rule) => blinded.has(rule) || jsdomBlindApplies && ENGINE_BLIND_RULES[rule] !== void 0);
|
|
485
487
|
if (engineBlind.length > 0) return {
|
|
486
488
|
...criterion,
|
|
487
489
|
status: "not-evaluated",
|
|
@@ -495,15 +497,36 @@ function buildCoverage(audits, tags = DEFAULT_TAGS) {
|
|
|
495
497
|
browserWouldAnswer: false
|
|
496
498
|
};
|
|
497
499
|
});
|
|
498
|
-
const
|
|
500
|
+
const reviewed = review === void 0 ? criteria : criteria.map(withReview(review));
|
|
501
|
+
const count = (status) => reviewed.filter((criterion) => criterion.status === status).length;
|
|
502
|
+
const counted = reviewed.filter((criterion) => criterion.review?.counts === true);
|
|
499
503
|
return {
|
|
500
|
-
criteria,
|
|
504
|
+
criteria: reviewed,
|
|
501
505
|
evaluated: count("evaluated"),
|
|
502
506
|
notEvaluated: count("not-evaluated"),
|
|
503
507
|
nothingToCheck: count("nothing-to-check"),
|
|
504
508
|
noAutomatedRule: count("no-automated-rule"),
|
|
505
|
-
browserWouldAnswer:
|
|
506
|
-
total: WCAG22_AA_CRITERIA.length
|
|
509
|
+
browserWouldAnswer: reviewed.filter((criterion) => criterion.browserWouldAnswer).length,
|
|
510
|
+
total: WCAG22_AA_CRITERIA.length,
|
|
511
|
+
reviewed: counted.length,
|
|
512
|
+
reviewedNotMet: counted.filter((criterion) => criterion.review?.result === "not-met").length,
|
|
513
|
+
reviewNotCounted: reviewed.filter((criterion) => criterion.review?.counts === false).length
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
/**
|
|
517
|
+
* Attach what a person recorded, without letting it move what the engine found.
|
|
518
|
+
*
|
|
519
|
+
* `status` is untouched here, deliberately and in every branch: a review adds a
|
|
520
|
+
* second kind of claim beside the run's own, and the moment it could rewrite
|
|
521
|
+
* one the report would stop being a record of what was measured.
|
|
522
|
+
*/
|
|
523
|
+
function withReview(review) {
|
|
524
|
+
return (criterion) => {
|
|
525
|
+
const recorded = criterionReview(criterion.number, review, criterion.status === "evaluated");
|
|
526
|
+
return recorded === void 0 ? criterion : {
|
|
527
|
+
...criterion,
|
|
528
|
+
review: recorded
|
|
529
|
+
};
|
|
507
530
|
};
|
|
508
531
|
}
|
|
509
532
|
/**
|
|
@@ -517,5 +540,20 @@ function coverageSummary(coverage) {
|
|
|
517
540
|
const unautomatable = coverage.noAutomatedRule;
|
|
518
541
|
return `Of the ${coverage.total} WCAG 2.2 A and AA success criteria, ${unautomatable} cannot be checked by any automated engine and need a person. This run reached a verdict on ${coverage.evaluated}.`;
|
|
519
542
|
}
|
|
543
|
+
/**
|
|
544
|
+
* What a person added to the run, in one sentence, or nothing when nobody did.
|
|
545
|
+
*
|
|
546
|
+
* Kept out of `coverageSummary` so the sentence about the engine's reach does
|
|
547
|
+
* not change shape depending on whether a review was supplied — and because a
|
|
548
|
+
* reader has to be able to tell the two claims apart at a glance: one is what
|
|
549
|
+
* an engine measured, the other is what somebody says they checked.
|
|
550
|
+
*/
|
|
551
|
+
function reviewSummary(coverage) {
|
|
552
|
+
if (coverage.reviewed === 0 && coverage.reviewNotCounted === 0) return void 0;
|
|
553
|
+
const parts = [`A person recorded a result for ${coverage.reviewed} of the criteria this run did not reach`];
|
|
554
|
+
if (coverage.reviewedNotMet > 0) parts.push(`${coverage.reviewedNotMet} of them as not met`);
|
|
555
|
+
if (coverage.reviewNotCounted > 0) parts.push(`${coverage.reviewNotCounted} further entries were not counted`);
|
|
556
|
+
return `${parts.join("; ")}. This is a claim by a person, not a measurement.`;
|
|
557
|
+
}
|
|
520
558
|
//#endregion
|
|
521
|
-
export {
|
|
559
|
+
export { rulesByCriterion as a, reviewSummary as i, buildCoverage as n, manualCheckFor as o, coverageSummary as r, understandingUrl as s, WCAG22_AA_CRITERIA as t };
|