eaa-kit 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Kristof Kolity
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,102 @@
1
+ # eaa-kit
2
+
3
+ [![CI](https://github.com/likeBloodMoon/eaa-kit/actions/workflows/ci.yml/badge.svg)](https://github.com/likeBloodMoon/eaa-kit/actions/workflows/ci.yml)
4
+
5
+ Build-time WCAG 2.2 AA auditor and EU accessibility statement generator for static sites,
6
+ aimed at freelancers and small agencies in the DACH region who have to comply with the
7
+ European Accessibility Act (in force since 28 June 2025), the BFSG in Germany and the BaFG
8
+ in Austria.
9
+
10
+ ```bash
11
+ npx eaa-kit audit ./dist # WCAG 2.2 AA report over built HTML
12
+ npx eaa-kit statement # Barrierefreiheitserklärung from eaa.config
13
+ ```
14
+
15
+ > **Not legal advice.** eaa-kit reports what an automated engine can and cannot determine
16
+ > about your markup. Automated testing catches a minority of accessibility barriers; it is
17
+ > a floor, not a certificate.
18
+
19
+ ## Install
20
+
21
+ ```bash
22
+ pnpm add -D eaa-kit # npm i -D eaa-kit
23
+ ```
24
+
25
+ Node 22.22 or newer. jsdom, which the browserless engine is built on, does not support
26
+ Node 20.
27
+
28
+ ## What it does
29
+
30
+ **Audits your build.** Globs the HTML out of `./dist`, parses it with jsdom and runs
31
+ axe-core against it. No Chromium download, fast enough for CI, and it never fetches
32
+ anything or executes your site's JavaScript. `--browser` swaps in real Chromium for the
33
+ rules that need layout and CSS.
34
+
35
+ ```bash
36
+ eaa-kit audit ./dist --fail-on serious
37
+ ```
38
+
39
+ **Writes the statement.** A Barrierefreiheitserklärung from one config file, in German or
40
+ English, as Markdown or HTML, naming the statute and supervisory body of Austria,
41
+ Switzerland or Germany — and optionally listing the barriers a real audit found.
42
+
43
+ ```bash
44
+ eaa-kit statement --output src/content/a11y.md
45
+ ```
46
+
47
+ **Fails only on what is new.** The first run on a site that already exists finds
48
+ everything at once. A baseline records what is already wrong so the build fails on
49
+ regressions instead, without ever letting an accepted barrier look like a passing one.
50
+
51
+ ```bash
52
+ eaa-kit baseline ./dist
53
+ eaa-kit audit ./dist --baseline eaa-baseline.json
54
+ ```
55
+
56
+ **Reports in four shapes**: a console report for whoever ran it, JSON for other tools,
57
+ SARIF for GitHub code scanning, and a self-contained HTML page for the client whose site
58
+ it is.
59
+
60
+ **Runs in your build**, as an [Astro integration](docs/integrations.md#astro-integration)
61
+ or the bundled [GitHub Action](docs/integrations.md#github-actions).
62
+
63
+ ## Documentation
64
+
65
+ | | |
66
+ | --- | --- |
67
+ | [Auditing a build](docs/audit.md) | The `audit` command, both engines, exit codes, and what an automated run can and cannot tell you |
68
+ | [The statement command](docs/statement.md) | The config file, the three countries, and filling a statement from audit results |
69
+ | [Baselines](docs/baseline.md) | Adopting the tool on a site that already has violations |
70
+ | [Report formats](docs/reports.md) | The JSON contract, SARIF, and the HTML report |
71
+ | [Integrations](docs/integrations.md) | Astro and GitHub Actions |
72
+
73
+ Complete generated output for every format is checked in under [examples/](examples).
74
+
75
+ ## What it will not tell you
76
+
77
+ The two things worth knowing before you rely on any of it:
78
+
79
+ **Automated testing finds a minority of barriers.** It cannot judge whether alternative
80
+ text is accurate, whether a page makes sense in reading order, or whether a form can
81
+ actually be completed with a screen reader. A clean report means nothing was found by this
82
+ engine, which is not the same as a site being accessible — and none of the four output
83
+ formats will say otherwise on your behalf.
84
+
85
+ **The browserless engine cannot decide everything.** jsdom has no layout, so rules that
86
+ depend on rendering — colour contrast, target size, computed overflow — cannot be
87
+ evaluated. axe-core does not know that and will report some of them as *passing*. eaa-kit
88
+ never passes those on: they are reported as **not evaluated**, with the reason, whatever
89
+ axe-core said about them. See [what the browserless engine can and cannot tell
90
+ you](docs/audit.md#what-the-browserless-engine-can-and-cannot-tell-you).
91
+
92
+ The same applies to the statement: it says what you told it. eaa-kit cannot check whether
93
+ those claims are true, and a statement claiming full conformance for a site that is not
94
+ conformant is worse than no statement at all.
95
+
96
+ ## Changelog
97
+
98
+ See [CHANGELOG.md](CHANGELOG.md).
99
+
100
+ ## License
101
+
102
+ MIT
@@ -0,0 +1,90 @@
1
+ import { t as ImpactLevel } from "../impact-EEB9ZXmC.js";
2
+ //#region src/cli/audit.d.ts
3
+ /**
4
+ * The engines and the machine-readable reporters are imported where they are
5
+ * used, not at the top of the file.
6
+ *
7
+ * jsdom costs 630 ms to load and axe-core another 94 ms, and a static import
8
+ * here charges that to every invocation — `eaa-kit statement`, `--help` and a
9
+ * mistyped flag included, none of which parse a single page. The audit path
10
+ * pays the same cost either way, a few milliseconds later.
11
+ */
12
+ declare const OUTPUT_FORMATS: readonly ['console', 'json', 'sarif', 'html'];
13
+ type OutputFormat = (typeof OUTPUT_FORMATS)[number];
14
+ //#endregion
15
+ //#region src/astro/index.d.ts
16
+ /**
17
+ * An Astro integration that audits the build Astro just produced.
18
+ *
19
+ * `astro build` already knows where the output went and when it finished, which
20
+ * is the one moment a build-time auditor wants. Wiring the CLI into a project's
21
+ * scripts works, but it means remembering to, and it means the audit runs in a
22
+ * separate step that is easy to drop from a pipeline when it goes red.
23
+ *
24
+ * The Astro types are described structurally here rather than imported. Astro is
25
+ * an optional peer dependency, exactly like Playwright, and a published .d.ts
26
+ * that referenced `astro` would fail to typecheck for everyone who installed
27
+ * this package without it. The shapes below are checked against the real
28
+ * `AstroIntegration` type at compile time in the tests, and the integration is
29
+ * driven by an actual `astro build` there, so the structural copy cannot drift
30
+ * from the API it stands in for.
31
+ */
32
+ /** The part of Astro's integration logger this uses. */
33
+ interface AstroLoggerLike {
34
+ info(message: string): void;
35
+ warn(message: string): void;
36
+ error(message: string): void;
37
+ }
38
+ interface BuildDoneOptions {
39
+ /** Where Astro wrote the build. */
40
+ dir: URL;
41
+ logger: AstroLoggerLike;
42
+ }
43
+ interface AstroIntegrationLike {
44
+ name: string;
45
+ hooks: {
46
+ 'astro:build:done'?: (options: BuildDoneOptions) => void | Promise<void>;
47
+ };
48
+ }
49
+ interface EaaKitIntegrationOptions {
50
+ /** Lowest impact that fails the build. Defaults to 'serious'. */
51
+ failOn?: ImpactLevel;
52
+ /**
53
+ * Whether a failing audit fails the build. Defaults to true.
54
+ *
55
+ * The CLI has no equivalent because a shell can ignore an exit code; a build
56
+ * hook cannot. Turning this off is for the first week of adopting the tool on
57
+ * an existing site — after that, a baseline is the honest way to go green,
58
+ * because it records what is wrong instead of hiding it.
59
+ */
60
+ failBuild?: boolean;
61
+ /** Skip the audit entirely. For turning it off by environment. */
62
+ enabled?: boolean;
63
+ include?: string[];
64
+ exclude?: string[];
65
+ /** Audit pages under their real site URL instead of file://. */
66
+ baseUrl?: string;
67
+ /** Audit in real Chromium. Needs the playwright peer. */
68
+ browser?: boolean;
69
+ /** Worker threads for the browserless engine. */
70
+ concurrency?: number;
71
+ /** Accept the violations recorded in this file; fail only on new ones. */
72
+ baseline?: string;
73
+ /** Write a report as well as printing one. */
74
+ format?: OutputFormat;
75
+ /** Where to write it. Required for `format` to do anything useful. */
76
+ output?: string;
77
+ }
78
+ declare class AstroAuditError extends Error {
79
+ readonly name = "AstroAuditError";
80
+ }
81
+ /**
82
+ * Audit the build in `astro:build:done`, and fail the build if it does not pass.
83
+ *
84
+ * Failing by default is the point: an auditor that only ever prints is one
85
+ * nobody reads. `failBuild: false` is there for the week it takes to adopt it,
86
+ * and `baseline` is the answer after that.
87
+ */
88
+ declare function eaaKit(options?: EaaKitIntegrationOptions): AstroIntegrationLike;
89
+ //#endregion
90
+ export { AstroAuditError, AstroIntegrationLike, AstroLoggerLike, BuildDoneOptions, EaaKitIntegrationOptions, eaaKit as default };
@@ -0,0 +1,48 @@
1
+ import { fileURLToPath } from "node:url";
2
+ //#region src/astro/index.ts
3
+ var AstroAuditError = class extends Error {
4
+ name = "AstroAuditError";
5
+ };
6
+ /**
7
+ * Audit the build in `astro:build:done`, and fail the build if it does not pass.
8
+ *
9
+ * Failing by default is the point: an auditor that only ever prints is one
10
+ * nobody reads. `failBuild: false` is there for the week it takes to adopt it,
11
+ * and `baseline` is the answer after that.
12
+ */
13
+ function eaaKit(options = {}) {
14
+ return {
15
+ name: "eaa-kit",
16
+ hooks: { "astro:build:done": async ({ dir, logger }) => {
17
+ if (options.enabled === false) {
18
+ logger.info("skipped (enabled: false)");
19
+ return;
20
+ }
21
+ const { runAuditCommand } = await import("../audit-VtuUOuyX.js");
22
+ const { exitCode } = await runAuditCommand(fileURLToPath(dir), {
23
+ ...options.failOn ? { failOn: options.failOn } : {},
24
+ ...options.include ? { include: options.include } : {},
25
+ ...options.exclude ? { exclude: options.exclude } : {},
26
+ ...options.baseUrl ? { baseUrl: options.baseUrl } : {},
27
+ ...options.browser ? { browser: true } : {},
28
+ ...options.concurrency === void 0 ? {} : { concurrency: options.concurrency },
29
+ ...options.baseline ? { baseline: options.baseline } : {},
30
+ ...options.format ? { format: options.format } : {},
31
+ ...options.output ? { output: options.output } : {}
32
+ });
33
+ if (exitCode === 0) {
34
+ logger.info("no violations at or above the threshold");
35
+ return;
36
+ }
37
+ const message = exitCode === 2 ? "the audit could not be completed, so this build was not checked" : "accessibility violations at or above the threshold";
38
+ if (options.failBuild === false) {
39
+ logger.warn(`${message} (failBuild: false, so the build continues)`);
40
+ return;
41
+ }
42
+ logger.error(message);
43
+ throw new AstroAuditError(`eaa-kit: ${message}`);
44
+ } }
45
+ };
46
+ }
47
+ //#endregion
48
+ export { AstroAuditError, eaaKit as default };
@@ -0,0 +1 @@
1
+ export {}
@@ -0,0 +1,27 @@
1
+ import { n as auditPage } from "../../jsdom-BEu6Ra_2.js";
2
+ import { parentPort, workerData } from "node:worker_threads";
3
+ //#region src/audit/runners/worker.ts
4
+ /**
5
+ * One audit worker: a thread that owns its own jsdom and axe-core, and audits
6
+ * whatever page the pool hands it.
7
+ *
8
+ * It exists as a separate entry point rather than as inline source passed to
9
+ * `new Worker(…, { eval: true })` because the thing it has to import — the
10
+ * jsdom runner — sits at a different path when running from `src` than it does
11
+ * in the bundle, and a string of code cannot be resolved against either.
12
+ *
13
+ * The protocol is deliberately one message each way: a CollectedPage in, the
14
+ * PageAudit it produced out, both plain data that structured-clone handles.
15
+ * `auditPage` turns a page it cannot audit into a PageAudit carrying an
16
+ * `error`, so a failure is an ordinary reply rather than a special case. Only
17
+ * something catastrophic — the thread running out of memory — reaches the
18
+ * pool's error handler instead.
19
+ */
20
+ const port = parentPort;
21
+ if (!port) throw new Error("eaa-kit audit worker was started outside a worker thread");
22
+ const options = workerData ?? {};
23
+ port.on("message", async (page) => {
24
+ port.postMessage(await auditPage(page, options));
25
+ });
26
+ //#endregion
27
+ export {};