eaa-kit 0.2.1 → 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.
Files changed (56) hide show
  1. package/README.md +51 -4
  2. package/dist/astro/index.d.ts +7 -34
  3. package/dist/astro/index.js +6 -26
  4. package/dist/audit/runners/worker.js +1 -1
  5. package/dist/audit-CPoZMXGM.js +779 -0
  6. package/dist/audit-CpXH2Mk8.js +2 -0
  7. package/dist/{baseline-CV_3lbER.js → baseline-22Y1NWxM.js} +1 -1
  8. package/dist/{baseline-CgBmzFTr.js → baseline-DB9CZGnV.js} +17 -27
  9. package/dist/cli/index.js +336 -142
  10. package/dist/collect-CFM8gEVv.js +134 -0
  11. package/dist/command-D8l_oYbV.js +77 -0
  12. package/dist/component-C3GL1Mnu.js +146 -0
  13. package/dist/component-DKd3EHOg.js +2 -0
  14. package/dist/coverage-B4IBKMO8.js +521 -0
  15. package/dist/{crawl-CtJbMNNb.js → crawl-BiI1Lau_.js} +13 -23
  16. package/dist/eleventy/index.d.ts +28 -0
  17. package/dist/eleventy/index.js +19 -0
  18. package/dist/frameworks-B4ClIJgE.js +314 -0
  19. package/dist/frameworks-etFg_O8K.js +2 -0
  20. package/dist/fs-BmPtmFke.js +40 -0
  21. package/dist/html-BGTO3ypW.js +543 -0
  22. package/dist/{impact-DvgBjupx.js → impact-DZt2oBCP.js} +15 -1
  23. package/dist/index.js +2 -2
  24. package/dist/{init-DRKIdpK1.js → init-DIWDE35F.js} +13 -10
  25. package/dist/jsdom-4IMzv0eE.js +3 -0
  26. package/dist/jsdom-DCpGSLfW.js +81 -0
  27. package/dist/{json-C9xS1PNC.js → json-DjEvy1nX.js} +19 -22
  28. package/dist/json-QQuFIw1W.js +2 -0
  29. package/dist/{load-sCkKsvGQ.js → load-5wRGLvub.js} +3 -9
  30. package/dist/nuxt/index.d.ts +35 -0
  31. package/dist/nuxt/index.js +23 -0
  32. package/dist/{playwright-DSRnXmcd.js → playwright-BWniOain.js} +70 -23
  33. package/dist/{pool-DixLeu8L.js → pool-BMevaLWD.js} +4 -12
  34. package/dist/{project-CufCqIE2.js → project-CzOnkLH6.js} +14 -21
  35. package/dist/project-MFrXcw1M.js +2 -0
  36. package/dist/remediation-Dtowi2EC.js +321 -0
  37. package/dist/{render-BO0nVrrZ.js → render-DrvXRCEn.js} +8 -16
  38. package/dist/{result-2aZPfM8w.js → result-DoamKFsp.js} +115 -1
  39. package/dist/routes-CmdRUuOs.js +265 -0
  40. package/dist/run-BMASMmwO.d.ts +45 -0
  41. package/dist/run-DB34BSOZ.js +53 -0
  42. package/dist/{sarif-eSCuI0eX.js → sarif-SR3_lLYd.js} +22 -36
  43. package/dist/{schema-CMZ8ItGk.js → schema-is6CGX2D.js} +4 -1
  44. package/dist/text-CKKpzkYM.js +43 -0
  45. package/dist/vite/index.d.ts +39 -0
  46. package/dist/vite/index.js +40 -0
  47. package/dist/webpack/index.d.ts +33 -0
  48. package/dist/webpack/index.js +20 -0
  49. package/package.json +36 -9
  50. package/dist/audit-B2dIKpJ5.js +0 -2
  51. package/dist/audit-DXpKkXsC.js +0 -950
  52. package/dist/escape-Dm1o_RAk.js +0 -21
  53. package/dist/html-BLEuzep6.js +0 -337
  54. package/dist/jsdom-BEu6Ra_2.js +0 -163
  55. package/dist/jsdom-C6dIyaxN.js +0 -3
  56. package/dist/routes-BxbSZKXC.js +0 -123
@@ -0,0 +1,265 @@
1
+ import { i as toPosix, n as isDirectory } from "./fs-BmPtmFke.js";
2
+ import { i as detectFramework } from "./frameworks-B4ClIJgE.js";
3
+ import { i as readPackageJson } from "./project-CzOnkLH6.js";
4
+ import path from "node:path";
5
+ import { glob } from "tinyglobby";
6
+ //#region src/audit/routes.ts
7
+ /**
8
+ * A path segment that serves more than one page, or none.
9
+ *
10
+ * Dynamic segments are refused rather than resolved: `[slug]` stands for every
11
+ * post there is, and picking one would name a file that did not produce the
12
+ * page in front of the reader. Private and slot folders contribute no route at
13
+ * all.
14
+ */
15
+ function classifyBracketSegment(segment) {
16
+ if (/^\(.*\)$/.test(segment)) return "skip";
17
+ if (/^[[(]|^_|^@/.test(segment)) return "refuse";
18
+ return "route";
19
+ }
20
+ /** Drop the extension, and fold an index file into the directory it sits in. */
21
+ function withoutExtension(file) {
22
+ const route = file.replace(/\.[^./]+$/, "").replace(/\/index$/, "");
23
+ return route === "index" ? "" : route;
24
+ }
25
+ /** The directory a route file sits in, for routers that put the route there. */
26
+ function containingDirectory(file) {
27
+ return file.includes("/") ? file.replace(/\/[^/]+$/, "") : "";
28
+ }
29
+ /** Keep the segments that are routes, refusing the file if any is dynamic. */
30
+ function joinSegments(route) {
31
+ const kept = [];
32
+ for (const segment of route.split("/").filter((part) => part !== "")) {
33
+ const kind = classifyBracketSegment(segment);
34
+ if (kind === "refuse") return void 0;
35
+ if (kind === "skip") continue;
36
+ kept.push(segment);
37
+ }
38
+ return kept.join("/");
39
+ }
40
+ /** `pages/kontakt.tsx` serves `/kontakt`; the filename is the route. */
41
+ function fileNamedRoute(file) {
42
+ return joinSegments(withoutExtension(file));
43
+ }
44
+ /** `app/kontakt/page.tsx` serves `/kontakt`; the directory is the route. */
45
+ function directoryNamedRoute(file) {
46
+ return joinSegments(containingDirectory(file));
47
+ }
48
+ /**
49
+ * A documentation tree whose directory layout is the URL layout.
50
+ *
51
+ * No dynamic segments to worry about — these are files an author wrote, one per
52
+ * page — so the only work is folding the index file into its directory.
53
+ */
54
+ function mirroredRoute(file) {
55
+ return withoutExtension(file);
56
+ }
57
+ /**
58
+ * Remix and React Router flat routes: `blog.post.tsx` serves `/blog/post`.
59
+ *
60
+ * The dot is the separator, `_index` is a directory's own page, and a trailing
61
+ * underscore on a segment escapes a parent layout without changing the URL. A
62
+ * `$` segment is dynamic and refused, as `[slug]` is elsewhere.
63
+ */
64
+ function remixRoute(file) {
65
+ const withoutRouteFile = file.replace(/\/route\.[^./]+$/, "");
66
+ const flat = withoutRouteFile === file ? withoutExtension(file) : toPosix(withoutRouteFile);
67
+ const leaf = flat.includes("/") ? flat.split("/").pop() : flat;
68
+ if (leaf === "") return void 0;
69
+ const kept = [];
70
+ for (const raw of leaf.split(".")) {
71
+ const segment = raw.replace(/_$/, "");
72
+ if (segment === "") continue;
73
+ if (segment === "_index") continue;
74
+ if (segment.startsWith("$")) return void 0;
75
+ if (segment.includes("[")) return void 0;
76
+ kept.push(segment);
77
+ }
78
+ return kept.join("/");
79
+ }
80
+ /**
81
+ * Hugo content: `content/posts/hello.md` serves `/posts/hello/`.
82
+ *
83
+ * `_index.md` is a section's own page rather than a page called `_index`, which
84
+ * is the one place Hugo's leading underscore does not mean "private".
85
+ */
86
+ function hugoRoute(file) {
87
+ const withoutIndex = file.replace(/(^|\/)_index\.[^./]+$/, "");
88
+ if (withoutIndex !== file) return toPosix(withoutIndex);
89
+ return withoutExtension(file);
90
+ }
91
+ /**
92
+ * Conventions, most specific first.
93
+ *
94
+ * Order still matters where the registry knows nothing about a project, which
95
+ * is the case for a plain directory of HTML and for anything the registry has
96
+ * not heard of.
97
+ */
98
+ const CONVENTIONS = [
99
+ {
100
+ framework: "next-app",
101
+ ids: ["next"],
102
+ dir: "app",
103
+ pattern: "**/page.{tsx,ts,jsx,js,mdx}",
104
+ toRoute: directoryNamedRoute
105
+ },
106
+ {
107
+ framework: "next-app",
108
+ ids: ["next"],
109
+ dir: "src/app",
110
+ pattern: "**/page.{tsx,ts,jsx,js,mdx}",
111
+ toRoute: directoryNamedRoute
112
+ },
113
+ {
114
+ framework: "remix",
115
+ ids: ["remix"],
116
+ dir: "app/routes",
117
+ pattern: "**/*.{tsx,ts,jsx,js,mdx}",
118
+ toRoute: remixRoute
119
+ },
120
+ {
121
+ framework: "starlight",
122
+ ids: ["astro"],
123
+ dir: "src/content/docs",
124
+ pattern: "**/*.{md,mdx,mdoc}",
125
+ toRoute: mirroredRoute
126
+ },
127
+ {
128
+ framework: "astro",
129
+ ids: ["astro"],
130
+ dir: "src/pages",
131
+ pattern: "**/*.{astro,md,mdx,html}",
132
+ toRoute: fileNamedRoute
133
+ },
134
+ {
135
+ framework: "gatsby",
136
+ ids: ["gatsby"],
137
+ dir: "src/pages",
138
+ pattern: "**/*.{tsx,ts,jsx,js,md,mdx}",
139
+ toRoute: fileNamedRoute
140
+ },
141
+ {
142
+ framework: "next-pages",
143
+ ids: ["next"],
144
+ dir: "pages",
145
+ pattern: "**/*.{tsx,ts,jsx,js,mdx}",
146
+ toRoute: fileNamedRoute
147
+ },
148
+ {
149
+ framework: "next-pages",
150
+ ids: ["next"],
151
+ dir: "src/pages",
152
+ pattern: "**/*.{tsx,ts,jsx,js,mdx}",
153
+ toRoute: fileNamedRoute
154
+ },
155
+ {
156
+ framework: "nuxt",
157
+ ids: ["nuxt"],
158
+ dir: "pages",
159
+ pattern: "**/*.vue",
160
+ toRoute: fileNamedRoute
161
+ },
162
+ {
163
+ framework: "sveltekit",
164
+ ids: ["sveltekit"],
165
+ dir: "src/routes",
166
+ pattern: "**/+page.{svelte,ts,js}",
167
+ toRoute: directoryNamedRoute
168
+ },
169
+ {
170
+ framework: "docusaurus",
171
+ ids: ["docusaurus"],
172
+ dir: "docs",
173
+ pattern: "**/*.{md,mdx}",
174
+ toRoute: (file) => {
175
+ const route = mirroredRoute(file);
176
+ return route === void 0 ? void 0 : route === "" ? "docs" : `docs/${route}`;
177
+ }
178
+ },
179
+ {
180
+ framework: "vitepress",
181
+ ids: ["vitepress"],
182
+ dir: "docs",
183
+ pattern: "**/*.md",
184
+ toRoute: mirroredRoute
185
+ },
186
+ {
187
+ framework: "hugo",
188
+ ids: ["hugo"],
189
+ dir: "content",
190
+ pattern: "**/*.{md,html}",
191
+ toRoute: hugoRoute
192
+ }
193
+ ];
194
+ /** Every page path a build could have emitted for one route. */
195
+ function emittedPathsFor(route) {
196
+ if (route === "") return [
197
+ "/",
198
+ "index.html",
199
+ "index.htm"
200
+ ];
201
+ return [
202
+ route,
203
+ `${route}/`,
204
+ `${route}.html`,
205
+ `${route}/index.html`
206
+ ];
207
+ }
208
+ /**
209
+ * Build a page-to-source map by reading the project's route files.
210
+ *
211
+ * Returns undefined when the project uses no convention this understands, which
212
+ * is not a failure — most builds are not framework projects.
213
+ */
214
+ async function buildRouteMap(cwd, pkg) {
215
+ for (const convention of await orderedConventions(cwd, pkg)) {
216
+ const map = await mapConvention(cwd, convention);
217
+ if (map !== void 0) return map;
218
+ }
219
+ }
220
+ /**
221
+ * Conventions to try, the detected framework's first.
222
+ *
223
+ * Without this, `src/pages` is claimed by whichever convention is declared
224
+ * earliest, so a Gatsby project was reported as `next-pages`. The mapping
225
+ * happened to be right and the name was wrong, which is the kind of detail a
226
+ * reader notices and stops trusting the rest of the report over.
227
+ */
228
+ async function orderedConventions(cwd, pkg) {
229
+ const detected = await detectFramework(cwd, pkg ?? await readPackageJson(cwd));
230
+ if (detected === void 0) return [...CONVENTIONS];
231
+ const id = detected.framework.id;
232
+ const mine = CONVENTIONS.filter((convention) => convention.ids.includes(id));
233
+ const rest = CONVENTIONS.filter((convention) => !convention.ids.includes(id));
234
+ return [...mine, ...rest];
235
+ }
236
+ /** Read one convention's directory, or undefined when it has nothing to say. */
237
+ async function mapConvention(cwd, convention) {
238
+ const directory = path.join(cwd, convention.dir);
239
+ if (!await isDirectory(directory)) return void 0;
240
+ const files = await glob([convention.pattern], {
241
+ cwd: directory,
242
+ ignore: ["**/node_modules/**"],
243
+ onlyFiles: true
244
+ });
245
+ if (files.length === 0) return void 0;
246
+ const sources = /* @__PURE__ */ new Map();
247
+ for (const file of files) {
248
+ const route = convention.toRoute(toPosix(file));
249
+ if (route === void 0) continue;
250
+ const source = `${convention.dir}/${toPosix(file)}`;
251
+ for (const emitted of emittedPathsFor(route)) if (!sources.has(emitted)) sources.set(emitted, source);
252
+ }
253
+ if (sources.size === 0) return void 0;
254
+ return {
255
+ framework: convention.framework,
256
+ sources
257
+ };
258
+ }
259
+ /** The source file for an audited page, if the map knows one. */
260
+ function sourceFor(map, pagePath) {
261
+ if (map === void 0) return void 0;
262
+ return map.sources.get(pagePath) ?? map.sources.get(pagePath.replace(/^\//, ""));
263
+ }
264
+ //#endregion
265
+ export { buildRouteMap, sourceFor };
@@ -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,53 @@
1
+ //#region src/integration/run.ts
2
+ /**
3
+ * Where an integration writes when its host offers no logger of its own.
4
+ *
5
+ * stderr rather than stdout, so a line about accessibility never lands in the
6
+ * middle of a report being piped to a file, and prefixed either way: a message
7
+ * in somebody's build log has to say what produced it.
8
+ */
9
+ function stderrLogger() {
10
+ const write = (message) => {
11
+ process.stderr.write(`eaa-kit: ${message}\n`);
12
+ };
13
+ return {
14
+ info: write,
15
+ warn: write,
16
+ error: write
17
+ };
18
+ }
19
+ /**
20
+ * Thrown to fail the build. Its own class so a consumer can tell an audit
21
+ * failure from the build tool falling over.
22
+ */
23
+ var BuildAuditError = class extends Error {
24
+ name = "BuildAuditError";
25
+ };
26
+ /**
27
+ * Audit a finished build and decide whether it may proceed.
28
+ *
29
+ * Returns normally when the build should continue, and throws BuildAuditError
30
+ * when it should not.
31
+ */
32
+ async function auditBuild(directory, options, logger) {
33
+ const { enabled, failBuild, ...auditOptions } = options;
34
+ if (enabled === false) {
35
+ logger.info("skipped (enabled: false)");
36
+ return;
37
+ }
38
+ const { runAuditCommand } = await import("./audit-CpXH2Mk8.js");
39
+ const { exitCode } = await runAuditCommand(directory, auditOptions);
40
+ if (exitCode === 0) {
41
+ logger.info("no violations at or above the threshold");
42
+ return;
43
+ }
44
+ const message = exitCode === 2 ? "the audit could not be completed, so this build was not checked" : "accessibility violations at or above the threshold";
45
+ if (failBuild === false) {
46
+ logger.warn(`${message} (failBuild: false, so the build continues)`);
47
+ return;
48
+ }
49
+ logger.error(message);
50
+ throw new BuildAuditError(`eaa-kit: ${message}`);
51
+ }
52
+ //#endregion
53
+ export { auditBuild as n, stderrLogger as r, BuildAuditError as t };
@@ -1,6 +1,8 @@
1
- import { i as isImpactLevel } from "./impact-DvgBjupx.js";
1
+ import { o as isImpactLevel } from "./impact-DZt2oBCP.js";
2
+ import { o as standardsReference } from "./text-CKKpzkYM.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 findingElements, s as ruleOutcomes } from "./result-DoamKFsp.js";
4
6
  import path from "node:path";
5
7
  //#region src/audit/report/sarif.ts
6
8
  const SARIF_VERSION = "2.1.0";
@@ -73,7 +75,7 @@ function buildSarifReport(audits, options) {
73
75
  toolExecutionNotifications: notifications
74
76
  }],
75
77
  results,
76
- properties: summaryProperties(audits)
78
+ properties: summaryProperties(audits, options.completeness)
77
79
  }]
78
80
  };
79
81
  }
@@ -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
- if (finding.nodes.length === 0) return [{
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
- const outcomes = [
132
- ...audit.violations,
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 criteria = outcome.successCriteria.map((criterion) => `WCAG ${criterion}`);
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 },
@@ -162,7 +140,7 @@ function toSarifRule(outcome) {
162
140
  * Coverage that has no place in `results` but should not vanish: a SARIF log
163
141
  * with no results must not be mistaken for "everything was checked".
164
142
  */
165
- function summaryProperties(audits) {
143
+ function summaryProperties(audits, completeness) {
166
144
  let needsReview = 0;
167
145
  let notEvaluated = 0;
168
146
  const notEvaluatedRules = /* @__PURE__ */ new Set();
@@ -175,7 +153,15 @@ function summaryProperties(audits) {
175
153
  pages: audits.length,
176
154
  needsReview,
177
155
  notEvaluated,
178
- notEvaluatedRules: [...notEvaluatedRules].sort()
156
+ notEvaluatedRules: [...notEvaluatedRules].sort(),
157
+ ...completeness ? {
158
+ complete: completeness.complete,
159
+ discovery: completeness.discovery,
160
+ pagesAudited: completeness.audited,
161
+ pagesErrored: completeness.errored,
162
+ pagesUnreachable: completeness.unreachable.length,
163
+ truncated: completeness.truncated
164
+ } : {}
179
165
  };
180
166
  }
181
167
  /**
@@ -64,6 +64,9 @@ function string(options = {}) {
64
64
  return value;
65
65
  } };
66
66
  }
67
+ function boolean() {
68
+ return { read: (value, path, issues) => typeof value === "boolean" ? value : fail(issues, path, "expected true or false") };
69
+ }
67
70
  function number() {
68
71
  return { read: (value, path, issues) => typeof value === "number" && Number.isFinite(value) ? value : fail(issues, path, "expected a number") };
69
72
  }
@@ -189,4 +192,4 @@ function email() {
189
192
  } };
190
193
  }
191
194
  //#endregion
192
- export { withDefault as _, isoDateTime as a, object as c, record as d, safeParse as f, url as g, union as h, isoDate as i, optional as l, transform as m, email as n, nullable as o, string as p, enumeration as r, number as s, array as t, pipe as u };
195
+ export { url as _, isoDate as a, number as c, pipe as d, record as f, union as g, transform as h, enumeration as i, object as l, string as m, boolean as n, isoDateTime as o, safeParse as p, email as r, nullable as s, array as t, optional as u, withDefault as v };
@@ -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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
16
+ }
17
+ /** For attribute values, where a quote would end the attribute. */
18
+ function escapeAttribute(value) {
19
+ return escapeText(value).replace(/"/g, "&quot;").replace(/'/g, "&#39;");
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 { plural as a, escapeText as i, count as n, standardsReference as o, 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-DB34BSOZ.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 };
@@ -0,0 +1,33 @@
1
+ import { n as IntegrationOptions, t as BuildAuditError } from "../run-BMASMmwO.js";
2
+ //#region src/webpack/index.d.ts
3
+ /** The part of a webpack compilation this reads. */
4
+ interface CompilationLike {
5
+ /** Non-empty when the build itself failed; there is nothing to audit then. */
6
+ errors: readonly unknown[];
7
+ }
8
+ /** The part of the webpack compiler this plugin touches. */
9
+ interface CompilerLike {
10
+ options: {
11
+ output?: {
12
+ path?: string;
13
+ };
14
+ mode?: string;
15
+ };
16
+ watchMode?: boolean;
17
+ hooks: {
18
+ afterEmit: {
19
+ tapPromise(name: string, handler: (compilation: CompilationLike) => Promise<void>): void;
20
+ };
21
+ };
22
+ }
23
+ interface EaaKitWebpackOptions extends IntegrationOptions {
24
+ /** Directory to audit. Defaults to webpack's own `output.path`. */
25
+ directory?: string;
26
+ }
27
+ declare class EaaKitWebpackPlugin {
28
+ private readonly options;
29
+ constructor(options?: EaaKitWebpackOptions);
30
+ apply(compiler: CompilerLike): void;
31
+ }
32
+ //#endregion
33
+ export { BuildAuditError, CompilationLike, CompilerLike, EaaKitWebpackOptions, EaaKitWebpackPlugin as default };
@@ -0,0 +1,20 @@
1
+ import { n as auditBuild, r as stderrLogger, t as BuildAuditError } from "../run-DB34BSOZ.js";
2
+ import path from "node:path";
3
+ //#region src/webpack/index.ts
4
+ var EaaKitWebpackPlugin = class {
5
+ options;
6
+ constructor(options = {}) {
7
+ this.options = options;
8
+ }
9
+ apply(compiler) {
10
+ compiler.hooks.afterEmit.tapPromise("eaa-kit", async (compilation) => {
11
+ if (compiler.watchMode === true) return;
12
+ if (compilation.errors.length > 0) return;
13
+ const output = this.options.directory ?? compiler.options.output?.path;
14
+ if (output === void 0) throw new BuildAuditError("eaa-kit: webpack has no output.path, so there is no directory to audit. Set one, or pass `directory` to the plugin.");
15
+ await auditBuild(path.resolve(output), this.options, stderrLogger());
16
+ });
17
+ }
18
+ };
19
+ //#endregion
20
+ export { BuildAuditError, EaaKitWebpackPlugin as default };