eaa-kit 0.2.0 → 0.3.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 +2 -1
- package/dist/astro/index.d.ts +7 -34
- package/dist/astro/index.js +6 -26
- package/dist/audit/runners/worker.js +1 -1
- package/dist/audit-CuG2hYyo.js +2 -0
- package/dist/audit-DcL73mOC.js +635 -0
- package/dist/{baseline-CV_3lbER.js → baseline-CuKFq4IF.js} +1 -1
- package/dist/{baseline-CgBmzFTr.js → baseline-s9F3fXTN.js} +16 -26
- package/dist/cli/index.js +81 -143
- package/dist/collect-BkAQ0viT.js +123 -0
- package/dist/command-Dxpa00Ha.js +77 -0
- package/dist/component-7kEBjv_y.js +111 -0
- package/dist/{crawl-CtJbMNNb.js → crawl-Oxt2Gaqo.js} +13 -23
- package/dist/frameworks-BYa3tULg.js +243 -0
- package/dist/frameworks-DaDqrJOw.js +2 -0
- package/dist/fs-BmPtmFke.js +40 -0
- package/dist/{html-BLEuzep6.js → html-DKiI_3gs.js} +167 -86
- package/dist/{impact-DvgBjupx.js → impact-YdoOtFqm.js} +15 -1
- package/dist/index.js +2 -2
- package/dist/{init-DRKIdpK1.js → init-DiLiYvN1.js} +13 -10
- package/dist/{jsdom-BEu6Ra_2.js → jsdom-BjpF-2V-.js} +2 -7
- package/dist/jsdom-X4KYfTp8.js +3 -0
- package/dist/json-B0Y7rNjt.js +2 -0
- package/dist/{json-C9xS1PNC.js → json-Cnv9nd6U.js} +13 -21
- package/dist/{load-sCkKsvGQ.js → load-UYXLqGV9.js} +2 -8
- package/dist/manual-Vz-oX1I_.js +239 -0
- package/dist/{playwright-DWux49V3.js → playwright-DYFsGUNd.js} +79 -21
- package/dist/{pool-DixLeu8L.js → pool-BWkWZiJW.js} +4 -12
- package/dist/{project-CiyzKQud.js → project-DW08TseF.js} +37 -27
- package/dist/{render-BO0nVrrZ.js → render-DI_aCnAZ.js} +7 -15
- package/dist/{result-2aZPfM8w.js → result-DLxd2Eip.js} +38 -1
- package/dist/{routes-BxbSZKXC.js → routes-C2Cgf6Ko.js} +4 -8
- package/dist/run-BMASMmwO.d.ts +45 -0
- package/dist/run-BW6CVuND.js +36 -0
- package/dist/{sarif-eSCuI0eX.js → sarif-DB3WG7T9.js} +11 -33
- package/dist/text-BFmNtMsV.js +43 -0
- package/dist/vite/index.d.ts +39 -0
- package/dist/vite/index.js +40 -0
- package/package.json +8 -3
- package/dist/audit-CqrR9pIO.js +0 -2
- package/dist/audit-D4gju2BT.js +0 -949
- package/dist/escape-Dm1o_RAk.js +0 -21
- package/dist/jsdom-C6dIyaxN.js +0 -3
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { i as toPosix, n as isDirectory } from "./fs-BmPtmFke.js";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { glob } from "tinyglobby";
|
|
4
4
|
//#region src/audit/routes.ts
|
|
@@ -49,7 +49,7 @@ const CONVENTIONS = [
|
|
|
49
49
|
* without knowing the data behind it.
|
|
50
50
|
*/
|
|
51
51
|
function routePathFor(relativeFile, framework) {
|
|
52
|
-
let route = relativeFile
|
|
52
|
+
let route = toPosix(relativeFile);
|
|
53
53
|
if (framework === "next-app" || framework === "sveltekit") route = route.includes("/") ? route.replace(/\/[^/]+$/, "") : "";
|
|
54
54
|
else {
|
|
55
55
|
route = route.replace(/\.[^./]+$/, "");
|
|
@@ -90,11 +90,7 @@ function emittedPathsFor(route) {
|
|
|
90
90
|
async function buildRouteMap(cwd) {
|
|
91
91
|
for (const convention of CONVENTIONS) {
|
|
92
92
|
const directory = path.join(cwd, convention.dir);
|
|
93
|
-
|
|
94
|
-
if (!(await stat(directory)).isDirectory()) continue;
|
|
95
|
-
} catch {
|
|
96
|
-
continue;
|
|
97
|
-
}
|
|
93
|
+
if (!await isDirectory(directory)) continue;
|
|
98
94
|
const files = await glob([convention.pattern], {
|
|
99
95
|
cwd: directory,
|
|
100
96
|
ignore: ["**/node_modules/**"],
|
|
@@ -105,7 +101,7 @@ async function buildRouteMap(cwd) {
|
|
|
105
101
|
for (const file of files) {
|
|
106
102
|
const route = routePathFor(file, convention.framework);
|
|
107
103
|
if (route === void 0) continue;
|
|
108
|
-
const source = `${convention.dir}/${file
|
|
104
|
+
const source = `${convention.dir}/${toPosix(file)}`;
|
|
109
105
|
for (const emitted of emittedPathsFor(route)) if (!sources.has(emitted)) sources.set(emitted, source);
|
|
110
106
|
}
|
|
111
107
|
if (sources.size > 0) return {
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { t as ImpactLevel } from "./impact-EEB9ZXmC.js";
|
|
2
|
+
//#region src/cli/audit.d.ts
|
|
3
|
+
declare const OUTPUT_FORMATS: readonly ['console', 'json', 'sarif', 'html'];
|
|
4
|
+
type OutputFormat = (typeof OUTPUT_FORMATS)[number];
|
|
5
|
+
//#endregion
|
|
6
|
+
//#region src/integration/run.d.ts
|
|
7
|
+
/**
|
|
8
|
+
* What a build-time integration does once the build has written its files.
|
|
9
|
+
*
|
|
10
|
+
* Astro, Vite and Next all reach the same point by different routes: a
|
|
11
|
+
* directory exists, and the build should stop if what is in it fails the
|
|
12
|
+
* threshold. Only the hook name and the logger differ, so only those live in
|
|
13
|
+
* the integrations.
|
|
14
|
+
*/
|
|
15
|
+
interface IntegrationOptions {
|
|
16
|
+
/** Lowest impact that fails the build. Defaults to 'serious'. */
|
|
17
|
+
failOn?: ImpactLevel;
|
|
18
|
+
include?: string[];
|
|
19
|
+
exclude?: string[];
|
|
20
|
+
baseUrl?: string;
|
|
21
|
+
/** Audit in real Chromium. Needs the playwright peer. */
|
|
22
|
+
browser?: boolean;
|
|
23
|
+
concurrency?: number;
|
|
24
|
+
baseline?: string;
|
|
25
|
+
format?: OutputFormat;
|
|
26
|
+
/** Write the report here instead of the build log. */
|
|
27
|
+
output?: string;
|
|
28
|
+
/**
|
|
29
|
+
* Report without failing the build. For the week it takes to adopt this on a
|
|
30
|
+
* site that already exists — a baseline is the honest way to go green after
|
|
31
|
+
* that.
|
|
32
|
+
*/
|
|
33
|
+
failBuild?: boolean;
|
|
34
|
+
/** Skip entirely. For turning it off per environment without unwiring it. */
|
|
35
|
+
enabled?: boolean;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Thrown to fail the build. Its own class so a consumer can tell an audit
|
|
39
|
+
* failure from the build tool falling over.
|
|
40
|
+
*/
|
|
41
|
+
declare class BuildAuditError extends Error {
|
|
42
|
+
readonly name = "BuildAuditError";
|
|
43
|
+
}
|
|
44
|
+
//#endregion
|
|
45
|
+
export { IntegrationOptions as n, BuildAuditError as t };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
//#region src/integration/run.ts
|
|
2
|
+
/**
|
|
3
|
+
* Thrown to fail the build. Its own class so a consumer can tell an audit
|
|
4
|
+
* failure from the build tool falling over.
|
|
5
|
+
*/
|
|
6
|
+
var BuildAuditError = class extends Error {
|
|
7
|
+
name = "BuildAuditError";
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Audit a finished build and decide whether it may proceed.
|
|
11
|
+
*
|
|
12
|
+
* Returns normally when the build should continue, and throws BuildAuditError
|
|
13
|
+
* when it should not.
|
|
14
|
+
*/
|
|
15
|
+
async function auditBuild(directory, options, logger) {
|
|
16
|
+
const { enabled, failBuild, ...auditOptions } = options;
|
|
17
|
+
if (enabled === false) {
|
|
18
|
+
logger.info("skipped (enabled: false)");
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const { runAuditCommand } = await import("./audit-CuG2hYyo.js");
|
|
22
|
+
const { exitCode } = await runAuditCommand(directory, auditOptions);
|
|
23
|
+
if (exitCode === 0) {
|
|
24
|
+
logger.info("no violations at or above the threshold");
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const message = exitCode === 2 ? "the audit could not be completed, so this build was not checked" : "accessibility violations at or above the threshold";
|
|
28
|
+
if (failBuild === false) {
|
|
29
|
+
logger.warn(`${message} (failBuild: false, so the build continues)`);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
logger.error(message);
|
|
33
|
+
throw new BuildAuditError(`eaa-kit: ${message}`);
|
|
34
|
+
}
|
|
35
|
+
//#endregion
|
|
36
|
+
export { auditBuild as n, BuildAuditError as t };
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { o as isImpactLevel } from "./impact-YdoOtFqm.js";
|
|
2
|
+
import { a as standardsReference } from "./text-BFmNtMsV.js";
|
|
2
3
|
import { t as TOOL_VERSION } from "./version-B3v4rNoG.js";
|
|
3
4
|
import { t as elementFingerprint } from "./fingerprint-DRoneAjj.js";
|
|
5
|
+
import { a as ruleOutcomes, r as findingElements } from "./result-DLxd2Eip.js";
|
|
4
6
|
import path from "node:path";
|
|
5
7
|
//#region src/audit/report/sarif.ts
|
|
6
8
|
const SARIF_VERSION = "2.1.0";
|
|
@@ -92,29 +94,16 @@ const SUPPRESSED = [{
|
|
|
92
94
|
function toResults(finding, ruleIndex, uri, suppressions) {
|
|
93
95
|
const level = toSarifLevel(finding.impact);
|
|
94
96
|
const location = { physicalLocation: { artifactLocation: { uri } } };
|
|
95
|
-
|
|
97
|
+
return findingElements(finding).map(({ selector, html }) => ({
|
|
96
98
|
ruleId: finding.ruleId,
|
|
97
99
|
ruleIndex,
|
|
98
100
|
level,
|
|
99
101
|
kind: "fail",
|
|
100
|
-
message: { text: finding.help },
|
|
102
|
+
message: { text: selector === "" ? finding.help : `${finding.help}. Element: ${selector}` },
|
|
101
103
|
locations: [location],
|
|
102
|
-
partialFingerprints: fingerprint(finding.ruleId,
|
|
104
|
+
partialFingerprints: fingerprint(finding.ruleId, selector, html),
|
|
103
105
|
...suppressions ? { suppressions } : {}
|
|
104
|
-
}
|
|
105
|
-
return finding.nodes.map((node) => {
|
|
106
|
-
const selector = node.target.join(" ");
|
|
107
|
-
return {
|
|
108
|
-
ruleId: finding.ruleId,
|
|
109
|
-
ruleIndex,
|
|
110
|
-
level,
|
|
111
|
-
kind: "fail",
|
|
112
|
-
message: { text: `${finding.help}. Element: ${selector}` },
|
|
113
|
-
locations: [location],
|
|
114
|
-
partialFingerprints: fingerprint(finding.ruleId, selector, node.html),
|
|
115
|
-
...suppressions ? { suppressions } : {}
|
|
116
|
-
};
|
|
117
|
-
});
|
|
106
|
+
}));
|
|
118
107
|
}
|
|
119
108
|
/**
|
|
120
109
|
* Identifies an alert across runs. Deliberately excludes the file path, so that
|
|
@@ -127,25 +116,14 @@ function fingerprint(ruleId, selector, html) {
|
|
|
127
116
|
/** Every rule the run knows about, so the catalogue is complete in GitHub. */
|
|
128
117
|
function buildRules(audits) {
|
|
129
118
|
const rules = /* @__PURE__ */ new Map();
|
|
130
|
-
for (const audit of audits) {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
...audit.accepted ?? [],
|
|
134
|
-
...audit.incomplete,
|
|
135
|
-
...audit.passes,
|
|
136
|
-
...audit.inapplicable
|
|
137
|
-
];
|
|
138
|
-
for (const outcome of outcomes) {
|
|
139
|
-
if (rules.has(outcome.ruleId)) continue;
|
|
140
|
-
rules.set(outcome.ruleId, toSarifRule(outcome));
|
|
141
|
-
}
|
|
119
|
+
for (const audit of audits) for (const outcome of ruleOutcomes(audit)) {
|
|
120
|
+
if (rules.has(outcome.ruleId)) continue;
|
|
121
|
+
rules.set(outcome.ruleId, toSarifRule(outcome));
|
|
142
122
|
}
|
|
143
123
|
return [...rules.values()].sort((a, b) => a.id.localeCompare(b.id));
|
|
144
124
|
}
|
|
145
125
|
function toSarifRule(outcome) {
|
|
146
|
-
const
|
|
147
|
-
const clauses = outcome.enClauses.map((clause) => `EN 301 549 ${clause}`);
|
|
148
|
-
const references = [...criteria, ...clauses].join(", ");
|
|
126
|
+
const references = standardsReference(outcome.successCriteria, outcome.enClauses);
|
|
149
127
|
return {
|
|
150
128
|
id: outcome.ruleId,
|
|
151
129
|
shortDescription: { text: outcome.help },
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
//#region src/text.ts
|
|
2
|
+
/**
|
|
3
|
+
* Text this package writes: escaping, counting and the standards references
|
|
4
|
+
* that appear in every report and in the statement.
|
|
5
|
+
*
|
|
6
|
+
* Both documents eaa-kit produces embed text it did not write: an issue
|
|
7
|
+
* description from a config file, axe-core's help text, and — in the audit
|
|
8
|
+
* report — the markup of the element that failed, which is by definition
|
|
9
|
+
* arbitrary HTML from somebody's build. Getting the escaping wrong in the
|
|
10
|
+
* report would mean a page that fails an accessibility audit for having a stray
|
|
11
|
+
* `<script>` hands that script to whoever opens the report.
|
|
12
|
+
*/
|
|
13
|
+
/** For text nodes. Leaves quotes alone, which are fine between tags. */
|
|
14
|
+
function escapeText(value) {
|
|
15
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
16
|
+
}
|
|
17
|
+
/** For attribute values, where a quote would end the attribute. */
|
|
18
|
+
function escapeAttribute(value) {
|
|
19
|
+
return escapeText(value).replace(/"/g, """).replace(/'/g, "'");
|
|
20
|
+
}
|
|
21
|
+
/** `plural(1, 'page')` is `page`, `plural(2, 'page')` is `pages`. */
|
|
22
|
+
function plural(value, noun) {
|
|
23
|
+
return value === 1 ? noun : `${noun}s`;
|
|
24
|
+
}
|
|
25
|
+
/** `count(2, 'page')` is `2 pages`. */
|
|
26
|
+
function count(value, noun) {
|
|
27
|
+
return `${value} ${plural(value, noun)}`;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Element markup on one line, optionally bounded: one minified page must not be
|
|
31
|
+
* able to fill a report or run past a terminal.
|
|
32
|
+
*/
|
|
33
|
+
function collapse(html, max) {
|
|
34
|
+
const flat = html.replace(/\s+/g, " ").trim();
|
|
35
|
+
if (max === void 0 || flat.length <= max) return flat;
|
|
36
|
+
return `${flat.slice(0, max - 1)}…`;
|
|
37
|
+
}
|
|
38
|
+
/** `WCAG 1.1.1, EN 301 549 9.1.1.1`, in that order, or an empty string. */
|
|
39
|
+
function standardsReference(successCriteria, enClauses) {
|
|
40
|
+
return [...successCriteria.map((criterion) => `WCAG ${criterion}`), ...enClauses.map((clause) => `EN 301 549 ${clause}`)].join(", ");
|
|
41
|
+
}
|
|
42
|
+
//#endregion
|
|
43
|
+
export { standardsReference as a, escapeText as i, count as n, escapeAttribute as r, collapse as t };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { n as IntegrationOptions, t as BuildAuditError } from "../run-BMASMmwO.js";
|
|
2
|
+
//#region src/vite/index.d.ts
|
|
3
|
+
interface EaaKitPluginOptions extends IntegrationOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Directory to audit, relative to the Vite root. Defaults to the build's own
|
|
6
|
+
* `outDir`, which is the only place the plugin can know the files went.
|
|
7
|
+
*/
|
|
8
|
+
directory?: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* The parts of Vite this plugin touches, described structurally.
|
|
12
|
+
*
|
|
13
|
+
* So the published .d.ts does not reference vite, and installing eaa-kit in a
|
|
14
|
+
* project that has none costs nothing and still typechecks. The same reasoning
|
|
15
|
+
* as the Astro integration and the Playwright runner.
|
|
16
|
+
*/
|
|
17
|
+
interface ResolvedConfigLike {
|
|
18
|
+
root: string;
|
|
19
|
+
build: {
|
|
20
|
+
outDir: string;
|
|
21
|
+
};
|
|
22
|
+
logger?: {
|
|
23
|
+
info(message: string): void;
|
|
24
|
+
warn(message: string): void;
|
|
25
|
+
error(message: string): void;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
interface VitePluginLike {
|
|
29
|
+
name: string;
|
|
30
|
+
/** Build only: a dev server writes nothing to audit. */
|
|
31
|
+
apply: 'build';
|
|
32
|
+
/** Runs after the bundle is written, which is the earliest the files exist. */
|
|
33
|
+
enforce?: 'post';
|
|
34
|
+
configResolved(config: ResolvedConfigLike): void;
|
|
35
|
+
closeBundle(): Promise<void>;
|
|
36
|
+
}
|
|
37
|
+
declare function eaaKit(options?: EaaKitPluginOptions): VitePluginLike;
|
|
38
|
+
//#endregion
|
|
39
|
+
export { BuildAuditError, EaaKitPluginOptions, ResolvedConfigLike, VitePluginLike, eaaKit as default };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { n as auditBuild, t as BuildAuditError } from "../run-BW6CVuND.js";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
//#region src/vite/index.ts
|
|
4
|
+
function eaaKit(options = {}) {
|
|
5
|
+
let directory;
|
|
6
|
+
let logger;
|
|
7
|
+
return {
|
|
8
|
+
name: "eaa-kit",
|
|
9
|
+
apply: "build",
|
|
10
|
+
enforce: "post",
|
|
11
|
+
configResolved(config) {
|
|
12
|
+
directory = path.resolve(config.root, options.directory ?? config.build.outDir);
|
|
13
|
+
logger = prefixed(config.logger);
|
|
14
|
+
},
|
|
15
|
+
async closeBundle() {
|
|
16
|
+
if (directory === void 0 || logger === void 0) throw new BuildAuditError("eaa-kit: the plugin was not given a resolved Vite config");
|
|
17
|
+
await auditBuild(directory, options, logger);
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Vite's own logger where there is one, the console otherwise.
|
|
23
|
+
*
|
|
24
|
+
* Prefixed either way: a line about accessibility in the middle of a build log
|
|
25
|
+
* needs to say what produced it.
|
|
26
|
+
*/
|
|
27
|
+
function prefixed(logger) {
|
|
28
|
+
const write = (level, message) => {
|
|
29
|
+
const line = `eaa-kit: ${message}`;
|
|
30
|
+
if (logger === void 0) process.stderr.write(`${line}\n`);
|
|
31
|
+
else logger[level](line);
|
|
32
|
+
};
|
|
33
|
+
return {
|
|
34
|
+
info: (message) => write("info", message),
|
|
35
|
+
warn: (message) => write("warn", message),
|
|
36
|
+
error: (message) => write("error", message)
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
//#endregion
|
|
40
|
+
export { BuildAuditError, eaaKit as default };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eaa-kit",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "WCAG 2.2 AA auditor and EU accessibility statement generator (EAA / BFSG / BaFG). Audits a static build or a running site from the command line.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|
|
@@ -40,6 +40,10 @@
|
|
|
40
40
|
"types": "./dist/astro/index.d.ts",
|
|
41
41
|
"import": "./dist/astro/index.js"
|
|
42
42
|
},
|
|
43
|
+
"./vite": {
|
|
44
|
+
"types": "./dist/vite/index.d.ts",
|
|
45
|
+
"import": "./dist/vite/index.js"
|
|
46
|
+
},
|
|
43
47
|
"./package.json": "./package.json"
|
|
44
48
|
},
|
|
45
49
|
"files": [
|
|
@@ -54,7 +58,8 @@
|
|
|
54
58
|
"lint": "biome check .",
|
|
55
59
|
"format": "biome check --write .",
|
|
56
60
|
"smoke": "pnpm build && node dist/cli/index.js audit tests/fixtures/site --include about/** --format json && node dist/cli/index.js audit tests/fixtures/site --include \"about/**\" \"blog/**\" \"drafts/**\" \"legacy.htm\" --concurrency 2 && node dist/cli/index.js audit tests/fixtures/site --include about/** --format html && node dist/cli/index.js audit tests/fixtures/site --baseline examples/baseline.json && node -e \"import('./dist/astro/index.js').then(m => { const i = m.default(); if (i.name !== 'eaa-kit' || typeof i.hooks['astro:build:done'] !== 'function') { throw new Error('astro entry point is not an integration') } console.log('astro entry ok') })\" && node dist/cli/index.js statement --config examples/eaa.config.json && node dist/cli/index.js statement --config examples/eaa.config.json --audit examples/report.json --format html",
|
|
57
|
-
"examples": "pnpm build && node scripts/generate-examples.mjs"
|
|
61
|
+
"examples": "pnpm build && node scripts/generate-examples.mjs",
|
|
62
|
+
"test:packaged": "pnpm build && node scripts/test-packaged.mjs"
|
|
58
63
|
},
|
|
59
64
|
"peerDependencies": {
|
|
60
65
|
"playwright": ">=1.40.0",
|
package/dist/audit-CqrR9pIO.js
DELETED