bfsg-scanner 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.
Files changed (104) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/LICENSE +21 -0
  3. package/README.md +147 -0
  4. package/dist/cli/index.d.ts +2 -0
  5. package/dist/cli/index.js +4 -0
  6. package/dist/cli/index.js.map +1 -0
  7. package/dist/cli/run.d.ts +29 -0
  8. package/dist/cli/run.js +202 -0
  9. package/dist/cli/run.js.map +1 -0
  10. package/dist/config/index.d.ts +2 -0
  11. package/dist/config/index.js +3 -0
  12. package/dist/config/index.js.map +1 -0
  13. package/dist/config/load.d.ts +12 -0
  14. package/dist/config/load.js +47 -0
  15. package/dist/config/load.js.map +1 -0
  16. package/dist/config/schema.d.ts +51 -0
  17. package/dist/config/schema.js +16 -0
  18. package/dist/config/schema.js.map +1 -0
  19. package/dist/discovery/crawl.d.ts +21 -0
  20. package/dist/discovery/crawl.js +79 -0
  21. package/dist/discovery/crawl.js.map +1 -0
  22. package/dist/discovery/discover-site.d.ts +26 -0
  23. package/dist/discovery/discover-site.js +50 -0
  24. package/dist/discovery/discover-site.js.map +1 -0
  25. package/dist/discovery/discover.d.ts +25 -0
  26. package/dist/discovery/discover.js +81 -0
  27. package/dist/discovery/discover.js.map +1 -0
  28. package/dist/discovery/index.d.ts +11 -0
  29. package/dist/discovery/index.js +7 -0
  30. package/dist/discovery/index.js.map +1 -0
  31. package/dist/discovery/links.d.ts +11 -0
  32. package/dist/discovery/links.js +69 -0
  33. package/dist/discovery/links.js.map +1 -0
  34. package/dist/discovery/robots.d.ts +18 -0
  35. package/dist/discovery/robots.js +91 -0
  36. package/dist/discovery/robots.js.map +1 -0
  37. package/dist/discovery/sitemap.d.ts +18 -0
  38. package/dist/discovery/sitemap.js +36 -0
  39. package/dist/discovery/sitemap.js.map +1 -0
  40. package/dist/index.d.ts +1 -0
  41. package/dist/index.js +2 -0
  42. package/dist/index.js.map +1 -0
  43. package/dist/report/build.d.ts +14 -0
  44. package/dist/report/build.js +100 -0
  45. package/dist/report/build.js.map +1 -0
  46. package/dist/report/clauses.d.ts +32 -0
  47. package/dist/report/clauses.js +57 -0
  48. package/dist/report/clauses.js.map +1 -0
  49. package/dist/report/i18n.d.ts +54 -0
  50. package/dist/report/i18n.js +95 -0
  51. package/dist/report/i18n.js.map +1 -0
  52. package/dist/report/index.d.ts +12 -0
  53. package/dist/report/index.js +8 -0
  54. package/dist/report/index.js.map +1 -0
  55. package/dist/report/pdf.d.ts +12 -0
  56. package/dist/report/pdf.js +25 -0
  57. package/dist/report/pdf.js.map +1 -0
  58. package/dist/report/render.d.ts +8 -0
  59. package/dist/report/render.js +204 -0
  60. package/dist/report/render.js.map +1 -0
  61. package/dist/report/types.d.ts +78 -0
  62. package/dist/report/types.js +2 -0
  63. package/dist/report/types.js.map +1 -0
  64. package/dist/report/verdict.d.ts +20 -0
  65. package/dist/report/verdict.js +28 -0
  66. package/dist/report/verdict.js.map +1 -0
  67. package/dist/report/write.d.ts +21 -0
  68. package/dist/report/write.js +35 -0
  69. package/dist/report/write.js.map +1 -0
  70. package/dist/scan/index.d.ts +2 -0
  71. package/dist/scan/index.js +2 -0
  72. package/dist/scan/index.js.map +1 -0
  73. package/dist/scan/scan.d.ts +2 -0
  74. package/dist/scan/scan.js +82 -0
  75. package/dist/scan/scan.js.map +1 -0
  76. package/dist/scan/types.d.ts +56 -0
  77. package/dist/scan/types.js +2 -0
  78. package/dist/scan/types.js.map +1 -0
  79. package/dist/shared/index.d.ts +4 -0
  80. package/dist/shared/index.js +5 -0
  81. package/dist/shared/index.js.map +1 -0
  82. package/dist/shared/package-info.d.ts +10 -0
  83. package/dist/shared/package-info.js +11 -0
  84. package/dist/shared/package-info.js.map +1 -0
  85. package/dist/shared/pool.d.ts +7 -0
  86. package/dist/shared/pool.js +25 -0
  87. package/dist/shared/pool.js.map +1 -0
  88. package/dist/shared/rate-limiter.d.ts +11 -0
  89. package/dist/shared/rate-limiter.js +26 -0
  90. package/dist/shared/rate-limiter.js.map +1 -0
  91. package/dist/shared/user-agent.d.ts +11 -0
  92. package/dist/shared/user-agent.js +12 -0
  93. package/dist/shared/user-agent.js.map +1 -0
  94. package/docs/SCANNING-ETHICS.md +52 -0
  95. package/docs/adr/0001-page-discovery-strategy.md +74 -0
  96. package/docs/adr/0002-json-report-output.md +54 -0
  97. package/docs/adr/0003-failon-threshold-and-exit-codes.md +72 -0
  98. package/docs/adr/0004-clause-mapping-from-axe-tags.md +81 -0
  99. package/docs/adr/0005-html-report-rendering.md +71 -0
  100. package/docs/adr/0006-pdf-report-rendering.md +58 -0
  101. package/docs/adr/0007-cli-argument-surface.md +73 -0
  102. package/docs/adr/0008-report-json-schema.md +59 -0
  103. package/package.json +73 -0
  104. package/schema/report.v1.json +156 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,39 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project
5
+ follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [Unreleased]
8
+
9
+ Nothing yet.
10
+
11
+ ## [0.1.0] - 2026-09-01
12
+
13
+ First release.
14
+
15
+ ### Added
16
+
17
+ - Site discovery: `sitemap.xml` first, then a `robots.txt`-respecting
18
+ breadth-first crawl, bounded by `maxPages` and `excludePaths`.
19
+ - Scan engine: axe-core in Chromium via Playwright, with a concurrency pool,
20
+ a per-host rate limit, retries, and an identifiable User-Agent.
21
+ - `report.json` — versioned, self-describing (`schemaVersion: 1`), with a
22
+ shipped JSON Schema at `schema/report.v1.json`.
23
+ - Clause mapping: every finding is tagged with its WCAG success criteria and
24
+ EN 301 549 clauses, read from axe-core rule metadata; the report rolls up
25
+ the distinct breached clauses.
26
+ - `failOn` severity threshold driving the process exit code (`0`/`1`), plus
27
+ `2` for bad configuration and `3` for an incomplete scan.
28
+ - Localised `report.html` (German or English) — a self-contained page — and
29
+ `report.pdf` (the HTML printed to A4).
30
+ - CLI: a positional `url` argument for a one-page scan without a config
31
+ file, `--config`, `--fail-on`, `--report-language`, `--output-dir`,
32
+ `--format`, `--help`, `--version`.
33
+ - `reportFormats` config key / `--format` flag to choose which report files
34
+ are written.
35
+ - Distributable package: a `bfsg-scanner` bin and a `dist` build that
36
+ excludes tests.
37
+
38
+ [Unreleased]: https://github.com/MihaelaAghirculesei/bfsg-scanner/compare/v0.1.0...HEAD
39
+ [0.1.0]: https://github.com/MihaelaAghirculesei/bfsg-scanner/releases/tag/v0.1.0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Mihaela Aghirculesei
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,147 @@
1
+ # bfsg-scanner
2
+
3
+ Automated **WCAG 2.1 AA** accessibility scanning for a whole site, built on
4
+ [Playwright](https://playwright.dev) and [axe-core](https://github.com/dequelabs/axe-core).
5
+ Every finding is mapped to the clause it breaches in
6
+ [EN 301 549](https://www.etsi.org/deliver/etsi_en/301500_301599/301549/) and,
7
+ through it, Germany's [BFSG](https://www.gesetze-im-internet.de/bfsg/) — so a
8
+ run produces a citeable compliance report, not just a list of rule IDs.
9
+
10
+ Requires **Node.js ≥ 24**.
11
+
12
+ ## What it does
13
+
14
+ 1. **Discovers** the pages of a site — `sitemap.xml` first, then a
15
+ `robots.txt`-respecting breadth-first crawl, bounded by `maxPages`.
16
+ 2. **Scans** each page in a real Chromium via axe-core, with a concurrency
17
+ pool and a per-host rate limit, sending an identifiable User-Agent.
18
+ 3. **Reports** the result as `report.json`, `report.html` (German or English),
19
+ and `report.pdf`, with each violation tagged with its WCAG success
20
+ criteria and EN 301 549 clauses.
21
+ 4. **Exits non-zero** when violations reach a configurable severity
22
+ threshold, so a CI job can gate a merge on accessibility.
23
+
24
+ ## Quick start
25
+
26
+ ```sh
27
+ npx bfsg-scanner https://example.de
28
+
29
+ # scan one page, JSON only, fail the process on any "critical" finding:
30
+ npx bfsg-scanner https://example.de --format json --fail-on critical
31
+ ```
32
+
33
+ The first run needs a Chromium build for Playwright:
34
+
35
+ ```sh
36
+ npx playwright install chromium
37
+ ```
38
+
39
+ Install it for repeated use with `npm i -g bfsg-scanner`.
40
+
41
+ ## From source
42
+
43
+ ```sh
44
+ git clone https://github.com/MihaelaAghirculesei/bfsg-scanner.git
45
+ cd bfsg-scanner
46
+ npm install
47
+ npx playwright install chromium
48
+ npm run build
49
+ node dist/cli/index.js --help # or `npm link` for the bfsg-scanner command
50
+ ```
51
+
52
+ ## Usage
53
+
54
+ ```
55
+ bfsg-scanner [url] [options]
56
+ ```
57
+
58
+ | Argument / option | Meaning |
59
+ |--------------------------|---------|
60
+ | `url` | Base URL to scan. Omit it to load a config file instead. |
61
+ | `-c, --config <path>` | YAML config file (default: `bfsg.config.yaml`). Not allowed together with a `url`. |
62
+ | `--fail-on <level>` | `critical` \| `serious` \| `moderate` \| `minor` (default: `serious`). |
63
+ | `--report-language <l>` | `de` \| `en` (default: `de`). |
64
+ | `--output-dir <dir>` | Directory for the report files (default: `reports`). |
65
+ | `--format <list>` | Comma-separated subset of `json,html,pdf` (default: all three). |
66
+ | `-h, --help` | Show help and exit. |
67
+ | `-V, --version` | Print the version and exit. |
68
+
69
+ A `url` argument scans that one page with defaults; anything more (a page
70
+ budget, excluded paths, a custom rule set) needs a config file.
71
+
72
+ ## Configuration
73
+
74
+ A YAML file — `bfsg.config.yaml` by default, or `--config <path>`. Only
75
+ `baseUrl` is required; every other key falls back to the default shown.
76
+
77
+ ```yaml
78
+ baseUrl: "https://example.de" # required
79
+
80
+ maxPages: 50 # sitemap + crawl combined
81
+ wcagTags: # axe rule sets to run
82
+ - wcag2a
83
+ - wcag2aa
84
+ - wcag21aa
85
+ excludePaths: [] # URL-path globs to skip while crawling
86
+ outputDir: reports # where the report files are written
87
+ failOn: serious # critical | serious | moderate | minor
88
+ reportLanguage: de # de | en — language of the HTML/PDF report
89
+ reportFormats: # any non-empty subset of json | html | pdf
90
+ - json
91
+ - html
92
+ - pdf
93
+ ```
94
+
95
+ CLI flags override the matching config key. See the annotated
96
+ [`bfsg.config.yaml`](./bfsg.config.yaml) in this repo for a working example.
97
+
98
+ ## Output
99
+
100
+ Written to `outputDir`, overwritten each run:
101
+
102
+ - **`report.json`** — the machine-readable result. Its shape is described by
103
+ [`schema/report.v1.json`](./schema/report.v1.json) (JSON Schema, shipped
104
+ with the package); `schemaVersion` is `1`.
105
+ - **`report.html`** — a self-contained page (no external assets) leading with
106
+ the pass/fail verdict, the breached WCAG / EN 301 549 clauses, and the
107
+ findings per page.
108
+ - **`report.pdf`** — the HTML report printed to A4, for filing as the
109
+ compliance record.
110
+
111
+ The terminal prints a summary and the distinct breached clauses.
112
+
113
+ ## Exit codes
114
+
115
+ | Code | Meaning |
116
+ |------|---------|
117
+ | `0` | Scan completed; nothing at or above `--fail-on`. |
118
+ | `1` | Scan completed; violations at or above `--fail-on`. |
119
+ | `2` | Invalid arguments or configuration. |
120
+ | `3` | No pages discovered, or a page could not be scanned. |
121
+
122
+ `3` outranks `1`: a run with unreachable pages scanned an incomplete site, so
123
+ "no violations found" would be a claim the data cannot support.
124
+
125
+ ## How the clause mapping works
126
+
127
+ axe-core's rule metadata already carries the mapping: a rule tagged
128
+ `wcag143` checks WCAG SC 1.4.3, `EN-9.1.4.3` maps it to that EN 301 549
129
+ clause. The scanner reads those tags rather than maintaining a hand-authored
130
+ table. § 4 BFSG presumes conformity for content that meets the relevant
131
+ harmonised standard — for the web, EN 301 549, whose Chapter 9 references
132
+ WCAG 2.1 level A/AA — so the EN 301 549 clauses in the report are the
133
+ corresponding citations. This is not legal advice.
134
+
135
+ ## Responsible use
136
+
137
+ This tool sends real traffic to whatever site you point it at. Scan only
138
+ sites you own or are authorised to test. See
139
+ [`docs/SCANNING-ETHICS.md`](./docs/SCANNING-ETHICS.md).
140
+
141
+ ## Design decisions
142
+
143
+ Each significant choice is recorded as an ADR in [`docs/adr/`](./docs/adr).
144
+
145
+ ## License
146
+
147
+ [MIT](./LICENSE)
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ import { run } from './run.js';
3
+ process.exitCode = await run(process.argv.slice(2));
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAE/B,OAAO,CAAC,QAAQ,GAAG,MAAM,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC"}
@@ -0,0 +1,29 @@
1
+ import { type ScanDeps } from '../scan/index.js';
2
+ interface CliArgs {
3
+ readonly help: boolean;
4
+ readonly version: boolean;
5
+ /** Explicit --config path, if given. */
6
+ readonly configPath?: string;
7
+ /** Positional base URL, if given. */
8
+ readonly url?: string;
9
+ /**
10
+ * Config fields overridden on the command line, still as raw values —
11
+ * `configSchema` is the single place they get validated.
12
+ */
13
+ readonly overrides: Record<string, unknown>;
14
+ }
15
+ /**
16
+ * Exit code contract:
17
+ * 0 - scan completed, nothing at or above the `failOn` threshold
18
+ * 1 - scan completed, violations at or above `failOn` were found
19
+ * 2 - invalid arguments or configuration
20
+ * 3 - no pages were discovered, or one or more pages could not be scanned
21
+ *
22
+ * 3 outranks 1: a run with unreachable pages scanned an incomplete site, so
23
+ * "no violations found" would be a claim the data cannot support. The
24
+ * report is still written first, so partial results and failure entries
25
+ * survive either way.
26
+ */
27
+ export declare function parseCliArgs(argv: readonly string[]): CliArgs;
28
+ export declare function run(argv: readonly string[], deps?: ScanDeps): Promise<number>;
29
+ export {};
@@ -0,0 +1,202 @@
1
+ import { parseArgs } from 'node:util';
2
+ import { chromium } from 'playwright';
3
+ import { ConfigError, loadConfig, parseConfig } from '../config/index.js';
4
+ import { discoverSite } from '../discovery/index.js';
5
+ import { buildReport, renderHtmlReport, renderPdfReport, writeHtmlReport, writePdfReport, writeReport, } from '../report/index.js';
6
+ import { scan } from '../scan/index.js';
7
+ import { toolInfo } from '../shared/index.js';
8
+ const DEFAULT_CONFIG_PATH = 'bfsg.config.yaml';
9
+ const HELP_TEXT = `bfsg-scanner — WCAG 2.1 AA / EN 301 549 / BFSG accessibility scanner
10
+
11
+ Usage:
12
+ bfsg-scanner [url] [options]
13
+
14
+ Arguments:
15
+ url Base URL to scan. Omit to load a config file instead.
16
+
17
+ Options:
18
+ -c, --config <path> YAML config file (default: ${DEFAULT_CONFIG_PATH}).
19
+ Not allowed together with a url argument.
20
+ --fail-on <level> critical | serious | moderate | minor (default: serious).
21
+ --report-language <l> de | en (default: de).
22
+ --output-dir <dir> Directory for the report files (default: reports).
23
+ --format <list> Comma-separated subset of json,html,pdf (default: all three).
24
+ -h, --help Show this help and exit.
25
+ -V, --version Print the version and exit.
26
+
27
+ Exit codes:
28
+ 0 scan completed, nothing at or above the fail-on threshold
29
+ 1 scan completed, violations at or above fail-on
30
+ 2 invalid arguments or configuration
31
+ 3 no pages discovered, or a page could not be scanned
32
+ `;
33
+ const OPTIONS = {
34
+ help: { type: 'boolean', short: 'h' },
35
+ version: { type: 'boolean', short: 'V' },
36
+ config: { type: 'string', short: 'c' },
37
+ 'fail-on': { type: 'string' },
38
+ 'report-language': { type: 'string' },
39
+ 'output-dir': { type: 'string' },
40
+ format: { type: 'string' },
41
+ };
42
+ function safeParseArgs(argv) {
43
+ try {
44
+ return parseArgs({ args: [...argv], options: OPTIONS, allowPositionals: true });
45
+ }
46
+ catch (cause) {
47
+ throw new ConfigError(cause.message);
48
+ }
49
+ }
50
+ /**
51
+ * Exit code contract:
52
+ * 0 - scan completed, nothing at or above the `failOn` threshold
53
+ * 1 - scan completed, violations at or above `failOn` were found
54
+ * 2 - invalid arguments or configuration
55
+ * 3 - no pages were discovered, or one or more pages could not be scanned
56
+ *
57
+ * 3 outranks 1: a run with unreachable pages scanned an incomplete site, so
58
+ * "no violations found" would be a claim the data cannot support. The
59
+ * report is still written first, so partial results and failure entries
60
+ * survive either way.
61
+ */
62
+ export function parseCliArgs(argv) {
63
+ const { values, positionals } = safeParseArgs(argv);
64
+ if (positionals.length > 1) {
65
+ throw new ConfigError(`Expected at most one URL argument, got ${positionals.length}.`);
66
+ }
67
+ const url = positionals[0];
68
+ if (url !== undefined && values.config !== undefined) {
69
+ throw new ConfigError('Pass a URL argument or --config, not both.');
70
+ }
71
+ const overrides = {};
72
+ if (values['fail-on'] !== undefined) {
73
+ overrides.failOn = values['fail-on'];
74
+ }
75
+ if (values['report-language'] !== undefined) {
76
+ overrides.reportLanguage = values['report-language'];
77
+ }
78
+ if (values['output-dir'] !== undefined) {
79
+ overrides.outputDir = values['output-dir'];
80
+ }
81
+ if (values.format !== undefined) {
82
+ overrides.reportFormats = values.format
83
+ .split(',')
84
+ .map((part) => part.trim())
85
+ .filter((part) => part.length > 0);
86
+ }
87
+ return {
88
+ help: values.help ?? false,
89
+ version: values.version ?? false,
90
+ overrides,
91
+ ...(values.config !== undefined ? { configPath: values.config } : {}),
92
+ ...(url !== undefined ? { url } : {}),
93
+ };
94
+ }
95
+ function resolveConfig(cli) {
96
+ if (cli.url !== undefined) {
97
+ return parseConfig({ baseUrl: cli.url, ...cli.overrides }, 'command-line arguments');
98
+ }
99
+ const fromFile = loadConfig(cli.configPath ?? DEFAULT_CONFIG_PATH);
100
+ if (Object.keys(cli.overrides).length === 0) {
101
+ return fromFile;
102
+ }
103
+ return parseConfig({ ...fromFile, ...cli.overrides }, 'command-line arguments');
104
+ }
105
+ export async function run(argv, deps = {}) {
106
+ let cli;
107
+ try {
108
+ cli = parseCliArgs(argv);
109
+ }
110
+ catch (error) {
111
+ console.error(error instanceof Error ? error.message : String(error));
112
+ return 2;
113
+ }
114
+ if (cli.help) {
115
+ console.log(HELP_TEXT);
116
+ return 0;
117
+ }
118
+ if (cli.version) {
119
+ const { name, version } = toolInfo();
120
+ console.log(`${name} ${version}`);
121
+ return 0;
122
+ }
123
+ let config;
124
+ try {
125
+ config = resolveConfig(cli);
126
+ }
127
+ catch (error) {
128
+ if (error instanceof ConfigError) {
129
+ console.error(error.message);
130
+ return 2;
131
+ }
132
+ throw error;
133
+ }
134
+ const urls = await discoverSite({
135
+ baseUrl: config.baseUrl,
136
+ maxPages: config.maxPages,
137
+ excludePaths: config.excludePaths,
138
+ });
139
+ if (urls.length === 0) {
140
+ console.error(`No scannable pages discovered for ${config.baseUrl}`);
141
+ return 3;
142
+ }
143
+ console.log(`Discovered ${urls.length} page(s) to scan.`);
144
+ // One Chromium for the whole run: the scan uses it, and the PDF report is
145
+ // printed with it. `run` closes only a browser it launched itself, so an
146
+ // injected one (tests) is left to its owner — the same contract `scan` keeps.
147
+ const browser = deps.browser ?? (await chromium.launch());
148
+ const launchedHere = deps.browser === undefined;
149
+ try {
150
+ return await scanAndReport(config, urls, browser);
151
+ }
152
+ finally {
153
+ if (launchedHere) {
154
+ await browser.close();
155
+ }
156
+ }
157
+ }
158
+ async function scanAndReport(config, urls, browser) {
159
+ const result = await scan(urls, { wcagTags: config.wcagTags }, { browser });
160
+ const report = buildReport(result, {
161
+ baseUrl: config.baseUrl,
162
+ wcagTags: config.wcagTags,
163
+ failOn: config.failOn,
164
+ });
165
+ const formats = new Set(config.reportFormats);
166
+ if (formats.has('json')) {
167
+ console.log(`Report written to ${await writeReport(report, config.outputDir)}`);
168
+ }
169
+ if (formats.has('html') || formats.has('pdf')) {
170
+ const html = renderHtmlReport(report, config.reportLanguage);
171
+ if (formats.has('html')) {
172
+ console.log(`HTML report written to ${await writeHtmlReport(html, config.outputDir)}`);
173
+ }
174
+ if (formats.has('pdf')) {
175
+ const pdf = await renderPdfReport(html, browser);
176
+ console.log(`PDF report written to ${await writePdfReport(pdf, config.outputDir)}`);
177
+ }
178
+ }
179
+ const failedPages = result.pages.filter((page) => page.status === 'error');
180
+ if (failedPages.length > 0) {
181
+ for (const page of failedPages) {
182
+ console.error(`Failed to scan ${page.url}: ${page.error}`);
183
+ }
184
+ return 3;
185
+ }
186
+ console.log(`Scanned ${report.summary.pagesScanned} page(s), ${report.summary.totalViolations} rule(s) violated.`);
187
+ const { breachedSuccessCriteria, breachedEn301549Clauses } = report.summary;
188
+ if (breachedSuccessCriteria.length > 0) {
189
+ console.log(`WCAG 2.1 SC breached: ${breachedSuccessCriteria.join(', ')}`);
190
+ console.log(`EN 301 549 clauses breached: ${breachedEn301549Clauses.join(', ')}`);
191
+ }
192
+ const { verdict } = report;
193
+ if (verdict.unrankedViolations > 0) {
194
+ console.warn(`${verdict.unrankedViolations} violation(s) carry no impact rating and were excluded from the ${config.failOn} threshold. Review them in the report.`);
195
+ }
196
+ if (!verdict.passed) {
197
+ console.error(`${verdict.violationsAtOrAboveThreshold} violation(s) at or above "${config.failOn}".`);
198
+ return 1;
199
+ }
200
+ return 0;
201
+ }
202
+ //# sourceMappingURL=run.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run.js","sourceRoot":"","sources":["../../src/cli/run.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export { ConfigError, loadConfig, parseConfig } from './load.js';
2
+ export { type Config, configSchema, failOnSchema, type ReportFormat, reportFormatSchema, reportLanguageSchema, wcagTagSchema, } from './schema.js';
@@ -0,0 +1,3 @@
1
+ export { ConfigError, loadConfig, parseConfig } from './load.js';
2
+ export { configSchema, failOnSchema, reportFormatSchema, reportLanguageSchema, wcagTagSchema, } from './schema.js';
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/config/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACjE,OAAO,EAEL,YAAY,EACZ,YAAY,EAEZ,kBAAkB,EAClB,oBAAoB,EACpB,aAAa,GACd,MAAM,aAAa,CAAC"}
@@ -0,0 +1,12 @@
1
+ import { type Config } from './schema.js';
2
+ export declare class ConfigError extends Error {
3
+ constructor(message: string);
4
+ }
5
+ /**
6
+ * Validates an already-parsed config object against the schema, applying
7
+ * defaults. Throws {@link ConfigError} with all issues listed on failure.
8
+ * `source` names where the object came from, for the error message
9
+ * (e.g. a quoted file path, or "command-line arguments").
10
+ */
11
+ export declare function parseConfig(raw: unknown, source: string): Config;
12
+ export declare function loadConfig(path: string): Config;
@@ -0,0 +1,47 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { parse as parseYaml } from 'yaml';
3
+ import { configSchema } from './schema.js';
4
+ export class ConfigError extends Error {
5
+ constructor(message) {
6
+ super(message);
7
+ this.name = 'ConfigError';
8
+ }
9
+ }
10
+ /**
11
+ * Validates an already-parsed config object against the schema, applying
12
+ * defaults. Throws {@link ConfigError} with all issues listed on failure.
13
+ * `source` names where the object came from, for the error message
14
+ * (e.g. a quoted file path, or "command-line arguments").
15
+ */
16
+ export function parseConfig(raw, source) {
17
+ const result = configSchema.safeParse(raw);
18
+ if (!result.success) {
19
+ throw new ConfigError(formatIssues(source, result.error));
20
+ }
21
+ return result.data;
22
+ }
23
+ export function loadConfig(path) {
24
+ let raw;
25
+ try {
26
+ raw = readFileSync(path, 'utf8');
27
+ }
28
+ catch (cause) {
29
+ throw new ConfigError(`Cannot read config file at "${path}": ${cause.message}`);
30
+ }
31
+ let parsed;
32
+ try {
33
+ parsed = parseYaml(raw);
34
+ }
35
+ catch (cause) {
36
+ throw new ConfigError(`Cannot parse "${path}" as YAML: ${cause.message}`);
37
+ }
38
+ return parseConfig(parsed, `"${path}"`);
39
+ }
40
+ function formatIssues(source, error) {
41
+ const lines = error.issues.map((issue) => {
42
+ const field = issue.path.length > 0 ? issue.path.join('.') : '(root)';
43
+ return ` - ${field}: ${issue.message}`;
44
+ });
45
+ return `Invalid config (${source}):\n${lines.join('\n')}`;
46
+ }
47
+ //# sourceMappingURL=load.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"load.js","sourceRoot":"","sources":["../../src/config/load.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,KAAK,IAAI,SAAS,EAAE,MAAM,MAAM,CAAC;AAE1C,OAAO,EAAe,YAAY,EAAE,MAAM,aAAa,CAAC;AAExD,MAAM,OAAO,WAAY,SAAQ,KAAK;IACpC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,aAAa,CAAC;IAC5B,CAAC;CACF;AAED;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,GAAY,EAAE,MAAc;IACtD,MAAM,MAAM,GAAG,YAAY,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;IAC3C,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,IAAI,WAAW,CAAC,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IAC5D,CAAC;IACD,OAAO,MAAM,CAAC,IAAI,CAAC;AACrB,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,IAAI,GAAW,CAAC;IAChB,IAAI,CAAC;QACH,GAAG,GAAG,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACnC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,WAAW,CAAC,+BAA+B,IAAI,MAAO,KAAe,CAAC,OAAO,EAAE,CAAC,CAAC;IAC7F,CAAC;IAED,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;IAC1B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,WAAW,CAAC,iBAAiB,IAAI,cAAe,KAAe,CAAC,OAAO,EAAE,CAAC,CAAC;IACvF,CAAC;IAED,OAAO,WAAW,CAAC,MAAM,EAAE,IAAI,IAAI,GAAG,CAAC,CAAC;AAC1C,CAAC;AAED,SAAS,YAAY,CAAC,MAAc,EAAE,KAAiB;IACrD,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;QACvC,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;QACtE,OAAO,OAAO,KAAK,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC;IAC1C,CAAC,CAAC,CAAC;IACH,OAAO,mBAAmB,MAAM,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;AAC5D,CAAC"}
@@ -0,0 +1,51 @@
1
+ import { z } from 'zod';
2
+ export declare const wcagTagSchema: z.ZodEnum<{
3
+ wcag2a: "wcag2a";
4
+ wcag2aa: "wcag2aa";
5
+ wcag21a: "wcag21a";
6
+ wcag21aa: "wcag21aa";
7
+ }>;
8
+ export declare const failOnSchema: z.ZodEnum<{
9
+ critical: "critical";
10
+ serious: "serious";
11
+ moderate: "moderate";
12
+ minor: "minor";
13
+ }>;
14
+ export declare const reportLanguageSchema: z.ZodEnum<{
15
+ de: "de";
16
+ en: "en";
17
+ }>;
18
+ export declare const reportFormatSchema: z.ZodEnum<{
19
+ json: "json";
20
+ html: "html";
21
+ pdf: "pdf";
22
+ }>;
23
+ export declare const configSchema: z.ZodObject<{
24
+ baseUrl: z.ZodURL;
25
+ maxPages: z.ZodDefault<z.ZodNumber>;
26
+ wcagTags: z.ZodDefault<z.ZodArray<z.ZodEnum<{
27
+ wcag2a: "wcag2a";
28
+ wcag2aa: "wcag2aa";
29
+ wcag21a: "wcag21a";
30
+ wcag21aa: "wcag21aa";
31
+ }>>>;
32
+ excludePaths: z.ZodDefault<z.ZodArray<z.ZodString>>;
33
+ outputDir: z.ZodDefault<z.ZodString>;
34
+ failOn: z.ZodDefault<z.ZodEnum<{
35
+ critical: "critical";
36
+ serious: "serious";
37
+ moderate: "moderate";
38
+ minor: "minor";
39
+ }>>;
40
+ reportLanguage: z.ZodDefault<z.ZodEnum<{
41
+ de: "de";
42
+ en: "en";
43
+ }>>;
44
+ reportFormats: z.ZodDefault<z.ZodArray<z.ZodEnum<{
45
+ json: "json";
46
+ html: "html";
47
+ pdf: "pdf";
48
+ }>>>;
49
+ }, z.core.$strict>;
50
+ export type Config = z.infer<typeof configSchema>;
51
+ export type ReportFormat = z.infer<typeof reportFormatSchema>;
@@ -0,0 +1,16 @@
1
+ import { z } from 'zod';
2
+ export const wcagTagSchema = z.enum(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa']);
3
+ export const failOnSchema = z.enum(['critical', 'serious', 'moderate', 'minor']);
4
+ export const reportLanguageSchema = z.enum(['de', 'en']);
5
+ export const reportFormatSchema = z.enum(['json', 'html', 'pdf']);
6
+ export const configSchema = z.strictObject({
7
+ baseUrl: z.url(),
8
+ maxPages: z.number().int().positive().max(1000).default(50),
9
+ wcagTags: z.array(wcagTagSchema).min(1).default(['wcag2a', 'wcag2aa', 'wcag21aa']),
10
+ excludePaths: z.array(z.string()).default([]),
11
+ outputDir: z.string().min(1).default('reports'),
12
+ failOn: failOnSchema.default('serious'),
13
+ reportLanguage: reportLanguageSchema.default('de'),
14
+ reportFormats: z.array(reportFormatSchema).min(1).default(['json', 'html', 'pdf']),
15
+ });
16
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/config/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,SAAS,EAAE,SAAS,EAAE,UAAU,CAAC,CAAC,CAAC;AAElF,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,OAAO,CAAC,CAAC,CAAC;AAEjF,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;AAEzD,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC;AAElE,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,CAAC,YAAY,CAAC;IACzC,OAAO,EAAE,CAAC,CAAC,GAAG,EAAE;IAChB,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;IAC3D,QAAQ,EAAE,CAAC,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,SAAS,EAAE,UAAU,CAAC,CAAC;IAClF,YAAY,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;IAC7C,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC;IAC/C,MAAM,EAAE,YAAY,CAAC,OAAO,CAAC,SAAS,CAAC;IACvC,cAAc,EAAE,oBAAoB,CAAC,OAAO,CAAC,IAAI,CAAC;IAClD,aAAa,EAAE,CAAC,CAAC,KAAK,CAAC,kBAAkB,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC;CACnF,CAAC,CAAC"}
@@ -0,0 +1,21 @@
1
+ export interface CrawlOptions {
2
+ readonly baseUrl: string;
3
+ readonly maxPages: number;
4
+ readonly excludePaths?: readonly string[];
5
+ /** Injectable for tests; defaults to the global fetch. */
6
+ readonly fetchFn?: typeof fetch;
7
+ }
8
+ /**
9
+ * Breadth-first fallback crawl, used when a site has no sitemap. Starts at
10
+ * baseUrl, follows same-origin links in document order, and stops once
11
+ * maxPages pages have been collected. robots.txt, the configured
12
+ * excludePaths, and non-page assets (mailto:, PDFs, images, ...) are all
13
+ * applied identically to the seed URL and to every discovered link, so a
14
+ * disallowed or excluded home page yields zero results rather than being
15
+ * scanned anyway.
16
+ *
17
+ * Deterministic: links are visited in a plain FIFO queue in the order they
18
+ * appear in each page's HTML, so the same input always produces the same
19
+ * page list in the same order.
20
+ */
21
+ export declare function crawlSite(options: CrawlOptions): Promise<string[]>;