eaa-kit 0.3.0 → 0.5.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 (66) hide show
  1. package/README.md +82 -14
  2. package/dist/astro/index.d.ts +1 -1
  3. package/dist/astro/index.js +1 -1
  4. package/dist/audit/runners/worker.js +5 -1
  5. package/dist/{audit-DcL73mOC.js → audit-B282A3EA.js} +207 -41
  6. package/dist/audit-DyHPmGeD.js +2 -0
  7. package/dist/{baseline-CuKFq4IF.js → baseline-0u4df12v.js} +1 -1
  8. package/dist/{baseline-s9F3fXTN.js → baseline-CuAS2lMk.js} +6 -5
  9. package/dist/cli/index.js +306 -18
  10. package/dist/{collect-BkAQ0viT.js → collect-Cu1q9NTB.js} +53 -11
  11. package/dist/command-C3D7JWn6.js +160 -0
  12. package/dist/{component-7kEBjv_y.js → component-C3GL1Mnu.js} +41 -6
  13. package/dist/component-DKd3EHOg.js +2 -0
  14. package/dist/coverage-B_Y6l-Ra.js +522 -0
  15. package/dist/{crawl-Oxt2Gaqo.js → crawl-BUWPgaGW.js} +83 -9
  16. package/dist/eleventy/index.d.ts +28 -0
  17. package/dist/eleventy/index.js +19 -0
  18. package/dist/fingerprint-BjYV_0F7.js +67 -0
  19. package/dist/{frameworks-BYa3tULg.js → frameworks-B4ClIJgE.js} +71 -0
  20. package/dist/{frameworks-DaDqrJOw.js → frameworks-etFg_O8K.js} +1 -1
  21. package/dist/{html-DKiI_3gs.js → html-C4vktg0A.js} +130 -5
  22. package/dist/{impact-YdoOtFqm.js → impact-DZt2oBCP.js} +1 -1
  23. package/dist/index.d.ts +119 -4
  24. package/dist/index.js +2 -2
  25. package/dist/{init-DiLiYvN1.js → init-CW7LfGT5.js} +10 -4
  26. package/dist/jsdom-22Bkt65v.js +3 -0
  27. package/dist/jsdom-B--cEH-G.js +89 -0
  28. package/dist/{json-Cnv9nd6U.js → json-DROX33kh.js} +9 -4
  29. package/dist/{json-B0Y7rNjt.js → json-D_Mnnft5.js} +1 -1
  30. package/dist/load-CFq2VQtT.js +2 -0
  31. package/dist/{load-UYXLqGV9.js → load-yAR4wzez.js} +133 -8
  32. package/dist/nuxt/index.d.ts +35 -0
  33. package/dist/nuxt/index.js +23 -0
  34. package/dist/{playwright-DYFsGUNd.js → playwright-BojtYVUa.js} +30 -6
  35. package/dist/{pool-BWkWZiJW.js → pool-BO25OIez.js} +52 -2
  36. package/dist/{project-DW08TseF.js → project-CzOnkLH6.js} +8 -2
  37. package/dist/project-MFrXcw1M.js +2 -0
  38. package/dist/remediation-CMBIrnpN.js +321 -0
  39. package/dist/{render-DI_aCnAZ.js → render-DbGOVmhx.js} +48 -10
  40. package/dist/{result-DLxd2Eip.js → result-BWcYXeRs.js} +114 -3
  41. package/dist/routes-CmdRUuOs.js +265 -0
  42. package/dist/{run-BW6CVuND.js → run-C2nKFcb-.js} +19 -2
  43. package/dist/{run-BMASMmwO.d.ts → run-CtcEUhbe.d.ts} +7 -0
  44. package/dist/{sarif-DB3WG7T9.js → sarif-B-UBcVu8.js} +16 -8
  45. package/dist/{schema-CMZ8ItGk.js → schema-DJSF4K05.js} +18 -1
  46. package/dist/statement/templates/es.en.md +125 -0
  47. package/dist/statement/templates/es.es.md +127 -0
  48. package/dist/statement/templates/fr.en.md +128 -0
  49. package/dist/statement/templates/fr.fr.md +131 -0
  50. package/dist/statement/templates/it.en.md +127 -0
  51. package/dist/statement/templates/it.it.md +130 -0
  52. package/dist/statement/templates/nl.en.md +125 -0
  53. package/dist/statement/templates/nl.nl.md +127 -0
  54. package/dist/{text-BFmNtMsV.js → text-CKKpzkYM.js} +1 -1
  55. package/dist/vite/index.d.ts +1 -1
  56. package/dist/vite/index.js +1 -1
  57. package/dist/webpack/index.d.ts +33 -0
  58. package/dist/webpack/index.js +20 -0
  59. package/package.json +35 -9
  60. package/dist/audit-CuG2hYyo.js +0 -2
  61. package/dist/command-Dxpa00Ha.js +0 -77
  62. package/dist/fingerprint-DRoneAjj.js +0 -20
  63. package/dist/jsdom-BjpF-2V-.js +0 -158
  64. package/dist/jsdom-X4KYfTp8.js +0 -3
  65. package/dist/manual-Vz-oX1I_.js +0 -239
  66. package/dist/routes-C2Cgf6Ko.js +0 -119
package/dist/index.d.ts CHANGED
@@ -60,10 +60,17 @@ type DefaultedSchema<T> = Schema<T> & {
60
60
  //#endregion
61
61
  //#region src/config/define.d.ts
62
62
  /** Countries with their own supervisory body and statute text. */
63
- declare const COUNTRIES: readonly ['AT', 'DE', 'CH'];
63
+ declare const COUNTRIES: readonly ['AT', 'DE', 'CH', 'ES', 'FR', 'IT', 'NL'];
64
64
  type Country = (typeof COUNTRIES)[number];
65
- /** Languages a statement can be rendered in. */
66
- declare const STATEMENT_LOCALES: readonly ['de', 'en'];
65
+ /**
66
+ * Languages a statement can be rendered in.
67
+ *
68
+ * Not every country has every one: a statement is a document under a particular
69
+ * legal regime, not a translation of a document under another, so each country
70
+ * has the language it is published in and English. `renderStatement` says which
71
+ * ones a country has when asked for one it does not.
72
+ */
73
+ declare const STATEMENT_LOCALES: readonly ['de', 'en', 'es', 'fr', 'it', 'nl'];
67
74
  type StatementLocale = (typeof STATEMENT_LOCALES)[number];
68
75
  /**
69
76
  * Wording follows the EU model statement: fully, partially, or not conformant
@@ -81,6 +88,61 @@ type AssessmentMethod = (typeof ASSESSMENT_METHODS)[number];
81
88
  */
82
89
  declare const ISSUE_REASONS: readonly ['disproportionate-burden', 'out-of-scope', 'fix-planned'];
83
90
  type IssueReason = (typeof ISSUE_REASONS)[number];
91
+ /**
92
+ * Defaults for `eaa-kit audit` and `eaa-kit baseline`, so a project says once
93
+ * what every invocation would otherwise repeat.
94
+ *
95
+ * Every field is optional and every one is a default: a flag actually typed on
96
+ * the command line wins, because the file is the project's usual answer and the
97
+ * flag is somebody asking for something else right now.
98
+ *
99
+ * `baseline` reads the subset that means the same thing to it. `output`,
100
+ * `format`, `failOn` and `baseline` are audit-only on purpose — a baseline
101
+ * written to the report's path would overwrite the report, and a threshold for
102
+ * failing a run means nothing to a command that records what it finds.
103
+ */
104
+ declare const auditSchema: Schema<ObjectOf<{
105
+ /** Build directory. The positional argument wins over it. */
106
+ dir: OptionalSchema<string>;
107
+ include: OptionalSchema<string[]>;
108
+ exclude: OptionalSchema<string[]>;
109
+ /** Audit pages under their real site URL instead of file://. */
110
+ baseUrl: OptionalSchema<string>;
111
+ /** Audit a running site instead of a directory. */
112
+ url: OptionalSchema<string>;
113
+ /** Crawl a host that is not loopback. Off unless a project says otherwise. */
114
+ allowRemote: OptionalSchema<boolean>;
115
+ ignoreRobots: OptionalSchema<boolean>;
116
+ /** Where the site lists its pages, when that is not /sitemap.xml. */
117
+ sitemap: OptionalSchema<string>;
118
+ maxPages: OptionalSchema<number>;
119
+ /** 0 audits the entry page alone. */
120
+ maxDepth: OptionalSchema<number>;
121
+ /** Lowest impact that exits 1. */
122
+ failOn: OptionalSchema<"critical" | "minor" | "moderate" | "serious">;
123
+ format: OptionalSchema<"console" | "html" | "json" | "sarif">;
124
+ /** Write the report here instead of stdout. */
125
+ output: OptionalSchema<string>;
126
+ /** Audit in real Chromium. Needs the playwright peer. */
127
+ browser: OptionalSchema<boolean>;
128
+ /** Skip the rules the browserless engine cannot decide. No effect with `browser`. */
129
+ fast: OptionalSchema<boolean>;
130
+ concurrency: OptionalSchema<number>;
131
+ /** Path to a baseline; violations it accounts for do not fail the run. */
132
+ baseline: OptionalSchema<string>;
133
+ /** List every page and its result under the issues. */
134
+ perPage: OptionalSchema<boolean>;
135
+ /** Print the manual check for each rule the engine could not evaluate. */
136
+ manual: OptionalSchema<boolean>;
137
+ /** List every WCAG 2.2 A/AA criterion and what the run reached on it. */
138
+ coverage: OptionalSchema<boolean>;
139
+ /**
140
+ * False is `--no-build`: never run the project's build or start its server to
141
+ * find something to audit. Written in the positive because that is the state
142
+ * being described, and because a config file has no flags to negate.
143
+ */
144
+ build: OptionalSchema<boolean>;
145
+ }>>;
84
146
  declare const configSchema: Schema<ObjectOf<{
85
147
  site: Schema<ObjectOf<{
86
148
  name: Schema<string>;
@@ -132,7 +194,50 @@ declare const configSchema: Schema<ObjectOf<{
132
194
  }>>;
133
195
  enforcement: Schema<ObjectOf<{
134
196
  /** Drives which supervisory body and statute the template names. */
135
- country: Schema<"AT" | "CH" | "DE">;
197
+ country: Schema<"AT" | "CH" | "DE" | "ES" | "FR" | "IT" | "NL">;
198
+ }>>;
199
+ /** Defaults for the audit commands. Nothing here reaches the statement. */
200
+ audit: OptionalSchema<ObjectOf<{
201
+ /** Build directory. The positional argument wins over it. */
202
+ dir: OptionalSchema<string>;
203
+ include: OptionalSchema<string[]>;
204
+ exclude: OptionalSchema<string[]>;
205
+ /** Audit pages under their real site URL instead of file://. */
206
+ baseUrl: OptionalSchema<string>;
207
+ /** Audit a running site instead of a directory. */
208
+ url: OptionalSchema<string>;
209
+ /** Crawl a host that is not loopback. Off unless a project says otherwise. */
210
+ allowRemote: OptionalSchema<boolean>;
211
+ ignoreRobots: OptionalSchema<boolean>;
212
+ /** Where the site lists its pages, when that is not /sitemap.xml. */
213
+ sitemap: OptionalSchema<string>;
214
+ maxPages: OptionalSchema<number>;
215
+ /** 0 audits the entry page alone. */
216
+ maxDepth: OptionalSchema<number>;
217
+ /** Lowest impact that exits 1. */
218
+ failOn: OptionalSchema<"critical" | "minor" | "moderate" | "serious">;
219
+ format: OptionalSchema<"console" | "html" | "json" | "sarif">;
220
+ /** Write the report here instead of stdout. */
221
+ output: OptionalSchema<string>;
222
+ /** Audit in real Chromium. Needs the playwright peer. */
223
+ browser: OptionalSchema<boolean>;
224
+ /** Skip the rules the browserless engine cannot decide. No effect with `browser`. */
225
+ fast: OptionalSchema<boolean>;
226
+ concurrency: OptionalSchema<number>;
227
+ /** Path to a baseline; violations it accounts for do not fail the run. */
228
+ baseline: OptionalSchema<string>;
229
+ /** List every page and its result under the issues. */
230
+ perPage: OptionalSchema<boolean>;
231
+ /** Print the manual check for each rule the engine could not evaluate. */
232
+ manual: OptionalSchema<boolean>;
233
+ /** List every WCAG 2.2 A/AA criterion and what the run reached on it. */
234
+ coverage: OptionalSchema<boolean>;
235
+ /**
236
+ * False is `--no-build`: never run the project's build or start its server to
237
+ * find something to audit. Written in the positive because that is the state
238
+ * being described, and because a config file has no flags to negate.
239
+ */
240
+ build: OptionalSchema<boolean>;
136
241
  }>>;
137
242
  }>>;
138
243
  /**
@@ -167,6 +272,7 @@ interface EaaConfigInput {
167
272
  enforcement: {
168
273
  country: Country;
169
274
  };
275
+ audit?: AuditConfig;
170
276
  }
171
277
  /** One barrier, as written in a config file. */
172
278
  interface KnownIssueInput {
@@ -178,6 +284,15 @@ interface KnownIssueInput {
178
284
  }
179
285
  type EaaConfig = Infer<typeof configSchema>;
180
286
  type KnownIssue = EaaConfig['compliance']['knownIssues'][number];
287
+ /**
288
+ * The `audit` block, as written and as parsed — every field is optional.
289
+ *
290
+ * `undefined` is mapped out of the value types rather than left in them:
291
+ * `s.object` never writes a key it did not parse, so an absent field is an
292
+ * absent key, and the commands spread this over their own options where a
293
+ * present-but-undefined key would overwrite a real value.
294
+ */
295
+ type AuditConfig = { [K in keyof Infer<typeof auditSchema>]?: Exclude<Infer<typeof auditSchema>[K], undefined>; };
181
296
  /**
182
297
  * Identity function that gives `eaa.config.ts` its types. Deliberately does not
183
298
  * validate: a config file is loaded and checked in one place, so that an error
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-UYXLqGV9.js";
2
- import { a as summariseAuditReport, i as readAuditReport, n as toHtmlBody, o as StatementError, r as toHtmlDocument, t as renderStatement } from "./render-DI_aCnAZ.js";
1
+ import { a as ASSESSMENT_METHODS, c as ConfigError, d as configSchema, f as defineConfig, i as loadConfig, l as ISSUE_REASONS, n as findConfigFile, o as COMPLIANCE_STATUSES, p as parseConfig, s as COUNTRIES, t as CONFIG_FILENAMES, u as STATEMENT_LOCALES } from "./load-yAR4wzez.js";
2
+ import { a as summariseAuditReport, i as readAuditReport, n as toHtmlBody, o as StatementError, r as toHtmlDocument, t as renderStatement } from "./render-DbGOVmhx.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,7 @@
1
- import { o as COUNTRIES, t as CONFIG_FILENAMES } from "./load-UYXLqGV9.js";
1
+ import { t as DEFAULT_FAIL_ON } from "./impact-DZt2oBCP.js";
2
+ import { s as COUNTRIES, t as CONFIG_FILENAMES } from "./load-yAR4wzez.js";
2
3
  import { t as exists } from "./fs-BmPtmFke.js";
3
- import { i as note, o as warn, r as fail } from "./command-Dxpa00Ha.js";
4
+ import { l as warn, o as fail, s as note } from "./command-C3D7JWn6.js";
4
5
  import { readFile, writeFile } from "node:fs/promises";
5
6
  import path from "node:path";
6
7
  import pc from "picocolors";
@@ -9,7 +10,11 @@ import { createInterface } from "node:readline/promises";
9
10
  const COUNTRY_LOCALES = {
10
11
  AT: "de-AT",
11
12
  DE: "de-DE",
12
- CH: "de-CH"
13
+ CH: "de-CH",
14
+ ES: "es-ES",
15
+ FR: "fr-FR",
16
+ IT: "it-IT",
17
+ NL: "nl-NL"
13
18
  };
14
19
  /**
15
20
  * Everything the project already says about itself.
@@ -83,7 +88,8 @@ async function runInitCommand(options = {}) {
83
88
  assessmentMethod: "self-assessment",
84
89
  knownIssues: []
85
90
  },
86
- enforcement: { country }
91
+ enforcement: { country },
92
+ audit: { failOn: DEFAULT_FAIL_ON }
87
93
  };
88
94
  try {
89
95
  await writeFile(target, `${JSON.stringify(config, null, 2)}\n`, "utf8");
@@ -0,0 +1,3 @@
1
+ import "./result-BWcYXeRs.js";
2
+ import { n as runJsdomAudit } from "./jsdom-B--cEH-G.js";
3
+ export { runJsdomAudit };
@@ -0,0 +1,89 @@
1
+ import { a as failedPage, i as blindRulesInScope, l as runOptions, n as DEFAULT_TAGS, s as pageUrl, t as DEFAULT_PAGE_TIMEOUT_MS, u as shapeResults } from "./result-BWcYXeRs.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
+ /**
7
+ * Per-page ceiling.
8
+ *
9
+ * A soft one, and the limit is worth stating: this races axe-core against a
10
+ * timer, so it only fires where the work yields to the event loop. Neither
11
+ * jsdom's parse nor axe-core's walk of the tree does, so a document pathological
12
+ * enough to hold the thread runs past this unimpeded. The hard ceiling is the
13
+ * worker pool's, which terminates the thread; see the note on `runWorkers`.
14
+ */
15
+ const DEFAULT_TIMEOUT_MS = DEFAULT_PAGE_TIMEOUT_MS;
16
+ /**
17
+ * Audit collected pages with axe-core inside jsdom.
18
+ *
19
+ * Pages are processed sequentially: jsdom parsing and axe-core are both
20
+ * CPU-bound on the main thread, so concurrency buys nothing here. A page that
21
+ * throws or times out is recorded with an `error` and the run continues.
22
+ */
23
+ async function runJsdomAudit(pages, options = {}) {
24
+ const audits = [];
25
+ for (const page of pages) audits.push(await auditPage(page, options));
26
+ return audits;
27
+ }
28
+ async function auditPage(page, options = {}) {
29
+ const tags = options.tags ?? DEFAULT_TAGS;
30
+ const url = pageUrl(page, options.baseUrl);
31
+ const startedAt = Date.now();
32
+ const identity = {
33
+ relativePath: page.relativePath,
34
+ absolutePath: page.absolutePath,
35
+ url,
36
+ engine: "jsdom"
37
+ };
38
+ let dom;
39
+ try {
40
+ dom = createDom(page.html, url);
41
+ injectAxe(dom);
42
+ const { axe: pageAxe } = dom.window;
43
+ const results = await withTimeout(pageAxe.run(dom.window.document, runOptions(tags, { skipBlindRules: options.fast === true })), options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
44
+ return shapeResults(results, {
45
+ ...identity,
46
+ durationMs: Date.now() - startedAt,
47
+ blind: blindRulesInScope(tags)
48
+ });
49
+ } catch (cause) {
50
+ return failedPage({
51
+ ...identity,
52
+ durationMs: Date.now() - startedAt
53
+ }, cause instanceof Error ? cause.message : String(cause));
54
+ } finally {
55
+ dom?.window.close();
56
+ }
57
+ }
58
+ function createDom(html, url) {
59
+ const virtualConsole = new VirtualConsole();
60
+ virtualConsole.on("jsdomError", () => {});
61
+ return new JSDOM(html, {
62
+ url,
63
+ virtualConsole,
64
+ runScripts: "outside-only",
65
+ pretendToBeVisual: true
66
+ });
67
+ }
68
+ /**
69
+ * axe-core is 1.3 MB of source. Compiling it once and re-running the compiled
70
+ * script in each window's context avoids re-parsing it for every page.
71
+ */
72
+ let axeScript;
73
+ function injectAxe(dom) {
74
+ axeScript ??= new Script(axe.source, { filename: "axe-core.js" });
75
+ axeScript.runInContext(dom.getInternalVMContext());
76
+ }
77
+ async function withTimeout(promise, ms) {
78
+ promise.catch(() => {});
79
+ let timer;
80
+ try {
81
+ return await Promise.race([promise, new Promise((_resolve, reject) => {
82
+ timer = setTimeout(() => reject(/* @__PURE__ */ new Error(`axe-core timed out after ${ms}ms`)), ms);
83
+ })]);
84
+ } finally {
85
+ clearTimeout(timer);
86
+ }
87
+ }
88
+ //#endregion
89
+ export { runJsdomAudit as n, auditPage as t };
@@ -1,6 +1,8 @@
1
- import { i as impactLabel, o as isImpactLevel, r as countAtOrAbove } from "./impact-YdoOtFqm.js";
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 { a as ruleOutcomes } from "./result-DLxd2Eip.js";
3
+ import { t as elementFingerprint } from "./fingerprint-BjYV_0F7.js";
4
+ import { c as ruleOutcomes } from "./result-BWcYXeRs.js";
5
+ import { t as buildCoverage } from "./coverage-B_Y6l-Ra.js";
4
6
  import axe from "axe-core";
5
7
  /**
6
8
  * Build the machine-readable report.
@@ -15,7 +17,7 @@ import axe from "axe-core";
15
17
  function buildJsonReport(audits, options) {
16
18
  const generatedAt = (options.now ?? /* @__PURE__ */ new Date()).toISOString();
17
19
  return {
18
- schemaVersion: 1,
20
+ schemaVersion: 2,
19
21
  tool: {
20
22
  name: "eaa-kit",
21
23
  version: TOOL_VERSION,
@@ -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-Cnv9nd6U.js";
1
+ import { r as serialiseJsonReport, t as buildJsonReport } from "./json-DROX33kh.js";
2
2
  export { buildJsonReport, serialiseJsonReport };
@@ -0,0 +1,2 @@
1
+ import { r as loadAuditConfig } from "./load-yAR4wzez.js";
2
+ export { loadAuditConfig };
@@ -1,4 +1,5 @@
1
- import { _ as withDefault, c as object, f as safeParse, g as url, h as union, i as isoDate, l as optional, m as transform, n as email, p as string, r as enumeration, t as array, u as pipe } from "./schema-CMZ8ItGk.js";
1
+ import { n as IMPACT_LEVELS } from "./impact-DZt2oBCP.js";
2
+ import { _ as union, a as integer, d as optional, f as pipe, g as transform, h as string, i as enumeration, m as safeParse, n as boolean, o as isoDate, r as email, t as array, u as object, v as url, y as withDefault } from "./schema-DJSF4K05.js";
2
3
  import { r as isFile } from "./fs-BmPtmFke.js";
3
4
  import { readFile } from "node:fs/promises";
4
5
  import path from "node:path";
@@ -8,10 +9,28 @@ import { pathToFileURL } from "node:url";
8
9
  const COUNTRIES = [
9
10
  "AT",
10
11
  "DE",
11
- "CH"
12
+ "CH",
13
+ "ES",
14
+ "FR",
15
+ "IT",
16
+ "NL"
17
+ ];
18
+ /**
19
+ * Languages a statement can be rendered in.
20
+ *
21
+ * Not every country has every one: a statement is a document under a particular
22
+ * legal regime, not a translation of a document under another, so each country
23
+ * has the language it is published in and English. `renderStatement` says which
24
+ * ones a country has when asked for one it does not.
25
+ */
26
+ const STATEMENT_LOCALES = [
27
+ "de",
28
+ "en",
29
+ "es",
30
+ "fr",
31
+ "it",
32
+ "nl"
12
33
  ];
13
- /** Languages a statement can be rendered in. */
14
- const STATEMENT_LOCALES = ["de", "en"];
15
34
  /**
16
35
  * Wording follows the EU model statement: fully, partially, or not conformant
17
36
  * with the standard. "partially-compliant" is the honest answer for most sites
@@ -50,6 +69,66 @@ const knownIssueObject = object({
50
69
  * rather than a union that callers have to narrow before reading `remedyBy`.
51
70
  */
52
71
  const knownIssueSchema = union([pipe(transform(string({ min: 1 }), (description) => ({ description })), knownIssueObject), knownIssueObject], "expected a description, or an object with one");
72
+ /**
73
+ * Defaults for `eaa-kit audit` and `eaa-kit baseline`, so a project says once
74
+ * what every invocation would otherwise repeat.
75
+ *
76
+ * Every field is optional and every one is a default: a flag actually typed on
77
+ * the command line wins, because the file is the project's usual answer and the
78
+ * flag is somebody asking for something else right now.
79
+ *
80
+ * `baseline` reads the subset that means the same thing to it. `output`,
81
+ * `format`, `failOn` and `baseline` are audit-only on purpose — a baseline
82
+ * written to the report's path would overwrite the report, and a threshold for
83
+ * failing a run means nothing to a command that records what it finds.
84
+ */
85
+ const auditSchema = object({
86
+ /** Build directory. The positional argument wins over it. */
87
+ dir: optional(string({ min: 1 })),
88
+ include: optional(array(string({ min: 1 }))),
89
+ exclude: optional(array(string({ min: 1 }))),
90
+ /** Audit pages under their real site URL instead of file://. */
91
+ baseUrl: optional(url()),
92
+ /** Audit a running site instead of a directory. */
93
+ url: optional(url()),
94
+ /** Crawl a host that is not loopback. Off unless a project says otherwise. */
95
+ allowRemote: optional(boolean()),
96
+ ignoreRobots: optional(boolean()),
97
+ /** Where the site lists its pages, when that is not /sitemap.xml. */
98
+ sitemap: optional(string({ min: 1 })),
99
+ maxPages: optional(integer({ min: 1 })),
100
+ /** 0 audits the entry page alone. */
101
+ maxDepth: optional(integer({ min: 0 })),
102
+ /** Lowest impact that exits 1. */
103
+ failOn: optional(enumeration(IMPACT_LEVELS)),
104
+ format: optional(enumeration([
105
+ "console",
106
+ "json",
107
+ "sarif",
108
+ "html"
109
+ ])),
110
+ /** Write the report here instead of stdout. */
111
+ output: optional(string({ min: 1 })),
112
+ /** Audit in real Chromium. Needs the playwright peer. */
113
+ browser: optional(boolean()),
114
+ /** Skip the rules the browserless engine cannot decide. No effect with `browser`. */
115
+ fast: optional(boolean()),
116
+ concurrency: optional(integer({ min: 1 })),
117
+ /** Path to a baseline; violations it accounts for do not fail the run. */
118
+ baseline: optional(string({ min: 1 })),
119
+ /** List every page and its result under the issues. */
120
+ perPage: optional(boolean()),
121
+ /** Print the manual check for each rule the engine could not evaluate. */
122
+ manual: optional(boolean()),
123
+ /** List every WCAG 2.2 A/AA criterion and what the run reached on it. */
124
+ coverage: optional(boolean()),
125
+ /**
126
+ * False is `--no-build`: never run the project's build or start its server to
127
+ * find something to audit. Written in the positive because that is the state
128
+ * being described, and because a config file has no flags to negate.
129
+ */
130
+ build: optional(boolean())
131
+ });
53
132
  const configSchema = object({
54
133
  site: object({
55
134
  name: string({ min: 1 }),
@@ -91,9 +170,20 @@ const configSchema = object({
91
170
  }),
92
171
  enforcement: object({
93
172
  /** Drives which supervisory body and statute the template names. */
94
- country: enumeration(COUNTRIES) })
173
+ country: enumeration(COUNTRIES) }),
174
+ /** Defaults for the audit commands. Nothing here reaches the statement. */
175
+ audit: optional(auditSchema)
95
176
  });
96
177
  /**
178
+ * The `audit` block on its own.
179
+ *
180
+ * A project that only wants audit defaults should not have to write a complete
181
+ * statement config to get them, and `s.object` drops the keys it does not know,
182
+ * so the same file satisfies both readers: `statement` demands the whole
183
+ * document, `audit` reads this and ignores the rest.
184
+ */
185
+ const auditConfigSchema = object({ audit: optional(auditSchema) });
186
+ /**
97
187
  * Identity function that gives `eaa.config.ts` its types. Deliberately does not
98
188
  * validate: a config file is loaded and checked in one place, so that an error
99
189
  * points at the file rather than at wherever the module happened to be
@@ -112,7 +202,20 @@ var ConfigError = class extends Error {
112
202
  };
113
203
  /** Validate an already-loaded config object. */
114
204
  function parseConfig(value, source = "config") {
115
- const result = safeParse(configSchema, value);
205
+ return parse(configSchema, value, source);
206
+ }
207
+ /**
208
+ * Read only the `audit` block, ignoring whatever else the file holds.
209
+ *
210
+ * Returns undefined where there is no block, which is the common case: most
211
+ * config files exist for the statement alone, and finding one is not a reason
212
+ * to change how an audit runs.
213
+ */
214
+ function parseAuditConfig(value, source = "config") {
215
+ return parse(auditConfigSchema, value, source).audit;
216
+ }
217
+ function parse(schema, value, source) {
218
+ const result = safeParse(schema, value);
116
219
  if (result.success) return result.data;
117
220
  const issues = result.error.issues.map((issue) => {
118
221
  const path = issue.path.join(".");
@@ -145,10 +248,32 @@ async function loadConfig(options = {}) {
145
248
  if (!file) throw new ConfigError(`No config file found in ${cwd} or its parent directories`, CONFIG_FILENAMES.map((name) => `looked for ${name}`));
146
249
  if (!await isFile(file)) throw new ConfigError(`Config file not found: ${file}`);
147
250
  return {
148
- config: parseConfig(file.endsWith(".json") ? await importJson(file) : await importModule(file), path.basename(file)),
251
+ config: parseConfig(await readConfigFile(file), path.basename(file)),
252
+ path: file
253
+ };
254
+ }
255
+ /**
256
+ * Find the config file and read its `audit` block, for the commands that take
257
+ * defaults from it.
258
+ *
259
+ * Returns undefined when there is no config file at all. That is not an error
260
+ * here as it is for `statement`: `eaa-kit audit` has always run against a
261
+ * project that has never heard of a config file, and it must keep doing so. An
262
+ * explicit path that is not there is still an error, because somebody named it.
263
+ */
264
+ async function loadAuditConfig(options = {}) {
265
+ const cwd = path.resolve(options.cwd ?? process.cwd());
266
+ const file = options.path ? path.resolve(cwd, options.path) : await findConfigFile(cwd);
267
+ if (!file) return void 0;
268
+ if (!await isFile(file)) throw new ConfigError(`Config file not found: ${file}`);
269
+ return {
270
+ audit: parseAuditConfig(await readConfigFile(file), path.basename(file)),
149
271
  path: file
150
272
  };
151
273
  }
274
+ function readConfigFile(file) {
275
+ return file.endsWith(".json") ? importJson(file) : importModule(file);
276
+ }
152
277
  /** Walks up from `cwd`, so the CLI works from a subdirectory of the project. */
153
278
  async function findConfigFile(cwd) {
154
279
  let directory = path.resolve(cwd);
@@ -183,4 +308,4 @@ async function importModule(file) {
183
308
  return module.default;
184
309
  }
185
310
  //#endregion
186
- export { COMPLIANCE_STATUSES as a, ISSUE_REASONS as c, defineConfig as d, parseConfig as f, ASSESSMENT_METHODS as i, STATEMENT_LOCALES as l, findConfigFile as n, COUNTRIES as o, loadConfig as r, ConfigError as s, CONFIG_FILENAMES as t, configSchema as u };
311
+ export { ASSESSMENT_METHODS as a, ConfigError as c, configSchema as d, defineConfig as f, loadConfig as i, ISSUE_REASONS as l, findConfigFile as n, COMPLIANCE_STATUSES as o, parseConfig as p, loadAuditConfig as r, COUNTRIES as s, CONFIG_FILENAMES as t, STATEMENT_LOCALES as u };
@@ -0,0 +1,35 @@
1
+ import { n as IntegrationOptions, t as BuildAuditError } from "../run-CtcEUhbe.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-C2nKFcb-.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 { i as pageUrl, n as failedPage, o as runOptions, s as shapeResults, t as DEFAULT_TAGS } from "./result-DLxd2Eip.js";
2
+ import { a as failedPage, l as runOptions, n as DEFAULT_TAGS, s as pageUrl, u as shapeResults } from "./result-BWcYXeRs.js";
3
3
  import { createRequire } from "node:module";
4
4
  import { stat } from "node:fs/promises";
5
5
  import path from "node:path";
@@ -122,6 +122,21 @@ const DEFAULT_VIEWPORT = {
122
122
  height: 720
123
123
  };
124
124
  /**
125
+ * Pages open at once.
126
+ *
127
+ * This runner used to audit strictly one page at a time while the browserless
128
+ * one had a whole measured worker pool — which had it backwards, since the
129
+ * browser is the slow engine: it fetches the stylesheets and images a real
130
+ * visitor would, and spends most of a page waiting on them rather than on the
131
+ * CPU. Waiting on four at once is close to free.
132
+ *
133
+ * Four rather than more because each open page holds a document tree, its
134
+ * decoded images and its own copy of axe-core, and Chromium's memory is the
135
+ * limit here rather than cores. Past this the gain flattens and the cost does
136
+ * not.
137
+ */
138
+ const DEFAULT_CONCURRENCY = 4;
139
+ /**
125
140
  * Audit a built site in real Chromium.
126
141
  *
127
142
  * This is the engine that can answer the rules jsdom is blind to: colour
@@ -152,11 +167,20 @@ async function runBrowserAudit(directory, pages, options = {}) {
152
167
  viewport,
153
168
  bypassCSP: true
154
169
  });
155
- const audits = [];
156
- for (const page of pages) audits.push(await auditOne(context, server?.origin, page, {
157
- tags,
158
- timeout,
159
- ...options
170
+ const audits = Array.from({ length: pages.length });
171
+ const lanes = Math.max(1, Math.min(options.concurrency ?? DEFAULT_CONCURRENCY, pages.length));
172
+ let next = 0;
173
+ await Promise.all(Array.from({ length: lanes }, async () => {
174
+ while (true) {
175
+ const index = next;
176
+ if (index >= pages.length) return;
177
+ next += 1;
178
+ audits[index] = await auditOne(context, server?.origin, pages[index], {
179
+ tags,
180
+ timeout,
181
+ ...options
182
+ });
183
+ }
160
184
  }));
161
185
  await context.close();
162
186
  return audits;