@surea11y/core 1.2.0 → 1.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 (168) hide show
  1. package/CHANGELOG.md +81 -7
  2. package/LICENSE +373 -21
  3. package/README.md +175 -35
  4. package/bin/surea11y-core.js +20 -0
  5. package/docs/API_STABILITY.md +27 -1
  6. package/docs/BINDING_AUTHORS_GUIDE.md +9 -9
  7. package/docs/CI_INTEGRATIONS.md +103 -0
  8. package/docs/ENGINE_OPTIONS.md +2 -0
  9. package/docs/I18N.md +12 -9
  10. package/docs/INTEGRATION.md +19 -1
  11. package/docs/LIMITATIONS.md +1 -1
  12. package/docs/OUTPUT_SCHEMA.md +1 -1
  13. package/docs/REPORT.md +1 -1
  14. package/docs/RULE_CATALOG.md +1 -1
  15. package/docs/SARIF.md +59 -0
  16. package/package.json +63 -18
  17. package/src/baseline.js +0 -0
  18. package/src/checks/automatic/area-alt-present.js +63 -31
  19. package/src/checks/automatic/aria-allowed-attr.js +204 -80
  20. package/src/checks/automatic/aria-allowed-role.js +23 -7
  21. package/src/checks/automatic/aria-braille-equivalent.js +34 -10
  22. package/src/checks/automatic/aria-conditional-attr.js +32 -14
  23. package/src/checks/automatic/aria-deprecated-role.js +26 -11
  24. package/src/checks/automatic/aria-hidden-body.js +48 -23
  25. package/src/checks/automatic/aria-hidden-focus.js +420 -66
  26. package/src/checks/automatic/aria-prohibited-attr.js +327 -60
  27. package/src/checks/automatic/aria-prohibited-children.js +111 -103
  28. package/src/checks/automatic/aria-required-attr.js +29 -15
  29. package/src/checks/automatic/aria-required-children.js +44 -24
  30. package/src/checks/automatic/aria-required-parent.js +64 -35
  31. package/src/checks/automatic/aria-role-name-present.js +49 -21
  32. package/src/checks/automatic/aria-roles-valid.js +24 -12
  33. package/src/checks/automatic/aria-valid-attr-value.js +46 -22
  34. package/src/checks/automatic/aria-valid-attr.js +19 -5
  35. package/src/checks/automatic/autocomplete-valid.js +76 -16
  36. package/src/checks/automatic/avoid-inline-spacing.js +23 -8
  37. package/src/checks/automatic/binary-control-name-present.js +62 -50
  38. package/src/checks/automatic/button-name-present.js +54 -24
  39. package/src/checks/automatic/bypass-blocks-present.js +51 -32
  40. package/src/checks/automatic/canvas-text-alternative-present.js +59 -26
  41. package/src/checks/automatic/combobox-name-present.js +40 -45
  42. package/src/checks/automatic/contrast-computable.js +363 -341
  43. package/src/checks/automatic/contrast-enhanced.js +489 -466
  44. package/src/checks/automatic/contrast-minimum.js +488 -465
  45. package/src/checks/automatic/css-orientation-lock.js +51 -35
  46. package/src/checks/automatic/definition-list-children-valid.js +46 -25
  47. package/src/checks/automatic/deprecated-elements-not-used.js +25 -9
  48. package/src/checks/automatic/dialog-name-present.js +47 -85
  49. package/src/checks/automatic/dlitem-parent-valid.js +25 -8
  50. package/src/checks/automatic/duplicate-id-aria.js +28 -9
  51. package/src/checks/automatic/embed-text-alternative-present.js +88 -35
  52. package/src/checks/automatic/form-control-programmatic-label-present.js +81 -196
  53. package/src/checks/automatic/form-control-single-label.js +50 -14
  54. package/src/checks/automatic/html-xml-lang-mismatch.js +36 -18
  55. package/src/checks/automatic/iframe-focusable-content.js +265 -22
  56. package/src/checks/automatic/iframe-name-present.js +33 -9
  57. package/src/checks/automatic/iframe-title-unique.js +32 -9
  58. package/src/checks/automatic/img-alt-present.js +54 -52
  59. package/src/checks/automatic/input-image-alt-present.js +141 -112
  60. package/src/checks/automatic/label-in-name.js +65 -41
  61. package/src/checks/automatic/language-page-present.js +111 -109
  62. package/src/checks/automatic/link-in-text-block.js +61 -19
  63. package/src/checks/automatic/link-name-present.js +47 -14
  64. package/src/checks/automatic/list-children-valid.js +40 -33
  65. package/src/checks/automatic/listbox-name-present.js +41 -19
  66. package/src/checks/automatic/listitem-parent-valid.js +48 -13
  67. package/src/checks/automatic/menuitem-name-present.js +41 -61
  68. package/src/checks/automatic/meta-refresh-no-exceptions.js +32 -11
  69. package/src/checks/automatic/meta-refresh-timing-absent.js +22 -6
  70. package/src/checks/automatic/meta-viewport-zoom-enabled.js +26 -7
  71. package/src/checks/automatic/meter-name-present.js +40 -36
  72. package/src/checks/automatic/nested-interactive-controls-absent.js +58 -15
  73. package/src/checks/automatic/object-text-alternative-present.js +93 -39
  74. package/src/checks/automatic/option-name-present.js +40 -21
  75. package/src/checks/automatic/page-title-present.js +19 -6
  76. package/src/checks/automatic/progressbar-name-present.js +49 -44
  77. package/src/checks/automatic/role-img-alt-present.js +211 -159
  78. package/src/checks/automatic/searchbox-name-present.js +41 -19
  79. package/src/checks/automatic/server-side-image-map-absent.js +27 -11
  80. package/src/checks/automatic/slider-name-present.js +42 -47
  81. package/src/checks/automatic/spinbutton-name-present.js +41 -19
  82. package/src/checks/automatic/summary-name-present.js +39 -17
  83. package/src/checks/automatic/svg-image-text-alternative-present.js +116 -47
  84. package/src/checks/automatic/svg-text-alternative-present.js +262 -230
  85. package/src/checks/automatic/tab-name-present.js +39 -60
  86. package/src/checks/automatic/table-headers-attr-valid.js +27 -10
  87. package/src/checks/automatic/table-th-has-data-cells.js +24 -8
  88. package/src/checks/automatic/target-size-minimum.js +123 -48
  89. package/src/checks/automatic/td-has-header.js +53 -12
  90. package/src/checks/automatic/textbox-name-present.js +41 -19
  91. package/src/checks/automatic/tooltip-name-present.js +39 -18
  92. package/src/checks/automatic/treeitem-name-present.js +40 -21
  93. package/src/checks/automatic/valid-lang.js +22 -6
  94. package/src/checks/automatic/video-poster-text-alternative-present.js +81 -36
  95. package/src/checks/manual/accesskeys-manual.js +17 -6
  96. package/src/checks/manual/area-alt-decorative-manual.js +194 -193
  97. package/src/checks/manual/area-alt-quality-manual.js +184 -141
  98. package/src/checks/manual/aria-checked-state-mismatch-manual.js +48 -34
  99. package/src/checks/manual/aria-text-manual.js +20 -11
  100. package/src/checks/manual/canvas-text-alternative-quality-manual.js +151 -114
  101. package/src/checks/manual/css-hidden-focus.js +375 -169
  102. package/src/checks/manual/embed-text-alternative-quality-manual.js +178 -162
  103. package/src/checks/manual/empty-heading-manual.js +41 -24
  104. package/src/checks/manual/empty-table-header-manual.js +69 -31
  105. package/src/checks/manual/focus-order-semantics-manual.js +60 -13
  106. package/src/checks/manual/form-control-programmatic-label-quality-manual.js +209 -246
  107. package/src/checks/manual/heading-order-manual.js +50 -8
  108. package/src/checks/manual/identical-links-same-purpose-manual.js +36 -12
  109. package/src/checks/manual/image-redundant-alt-manual.js +38 -8
  110. package/src/checks/manual/img-alt-decorative-manual.js +133 -96
  111. package/src/checks/manual/img-alt-quality-manual.js +178 -127
  112. package/src/checks/manual/input-image-alt-decorative-manual.js +127 -92
  113. package/src/checks/manual/input-image-alt-quality-manual.js +127 -92
  114. package/src/checks/manual/label-title-only-manual.js +44 -28
  115. package/src/checks/manual/landmark-banner-is-top-level-manual.js +95 -38
  116. package/src/checks/manual/landmark-contentinfo-is-top-level-manual.js +85 -32
  117. package/src/checks/manual/landmark-main-is-top-level-manual.js +69 -27
  118. package/src/checks/manual/landmark-no-duplicate-banner-manual.js +45 -33
  119. package/src/checks/manual/landmark-no-duplicate-contentinfo-manual.js +43 -31
  120. package/src/checks/manual/landmark-no-duplicate-main-manual.js +27 -21
  121. package/src/checks/manual/landmark-one-main-manual.js +38 -43
  122. package/src/checks/manual/landmark-unique-manual.js +78 -67
  123. package/src/checks/manual/link-name-quality-manual.js +45 -12
  124. package/src/checks/manual/media-transcript-present-manual.js +37 -22
  125. package/src/checks/manual/meta-viewport-large-manual.js +19 -6
  126. package/src/checks/manual/mouse-only-event-handlers-manual.js +40 -11
  127. package/src/checks/manual/no-autoplay-audio-manual.js +22 -6
  128. package/src/checks/manual/object-text-alternative-quality-manual.js +177 -154
  129. package/src/checks/manual/p-as-heading-manual.js +24 -7
  130. package/src/checks/manual/page-has-heading-one-manual.js +42 -32
  131. package/src/checks/manual/page-title-patterns-manual.js +80 -50
  132. package/src/checks/manual/presentation-role-conflict-manual.js +101 -47
  133. package/src/checks/manual/region-manual.js +244 -60
  134. package/src/checks/manual/scope-attr-valid-manual.js +13 -4
  135. package/src/checks/manual/scrollable-region-focusable-manual.js +39 -11
  136. package/src/checks/manual/skip-link-manual.js +42 -18
  137. package/src/checks/manual/svg-text-alternative-quality-manual.js +208 -165
  138. package/src/checks/manual/tabindex-manual.js +13 -4
  139. package/src/checks/manual/table-duplicate-name-manual.js +22 -11
  140. package/src/checks/manual/table-fake-caption-manual.js +48 -10
  141. package/src/checks/manual/video-caption-manual.js +17 -4
  142. package/src/checks/manual-review.js +58 -12
  143. package/src/core.js +41705 -29650
  144. package/src/index.js +2 -0
  145. package/src/report.js +109 -47
  146. package/src/sarif.js +190 -0
  147. package/surea11y.browser.js +37774 -0
  148. package/bin/core.js +0 -348
  149. package/docs/CLI.md +0 -75
  150. package/src/catalogs/composites.wcag.js +0 -490
  151. package/src/checks/rules-and-tags.full.csv +0 -19
  152. package/src/checks/rules-and-tags.full.json +0 -259
  153. package/src/core/aria-helpers.js +0 -970
  154. package/src/core/contrast-helpers.js +0 -1147
  155. package/src/core/dom-helpers.js +0 -4235
  156. package/src/core/dom-runner.js +0 -671
  157. package/src/core/frame-messaging.js +0 -210
  158. package/src/core/frame-scan.js +0 -178
  159. package/src/core/rollup-composites.js +0 -135
  160. package/src/core/rule-meta.js +0 -159
  161. package/src/coverage/wcag-facets.js +0 -1079
  162. package/src/coverage/wcag-version-map.js +0 -84
  163. package/src/i18n/en.js +0 -923
  164. package/src/i18n/fr.js +0 -844
  165. package/src/policy/contracts.js +0 -18
  166. package/src/policy/resolvePolicy.js +0 -55
  167. package/src/policy/schemas/engine-options.schema.json +0 -103
  168. package/src/policy/schemas/policy-contract.schema.json +0 -40
package/bin/core.js DELETED
@@ -1,348 +0,0 @@
1
- #!/usr/bin/env node
2
- 'use strict';
3
-
4
- /**
5
- * surea11y CLI — a thin wrapper around the library for ad hoc/CI use.
6
- *
7
- * This is the one part of the package that depends on jsdom (see
8
- * package.json's "dependencies" vs the library itself, which has none --
9
- * `require('surea11y')` never loads jsdom; only running this CLI does).
10
- * Static-HTML only: it does not execute page JavaScript, so client-rendered
11
- * content won't be scanned. For that, use a real browser via Puppeteer/
12
- * Playwright — see docs/INTEGRATION.md Pattern 2.
13
- *
14
- * Usage:
15
- * surea11y scan <file-or-url> [options]
16
- *
17
- * See docs/CLI.md for the full option reference.
18
- */
19
-
20
- const fs = require('fs');
21
- const path = require('path');
22
-
23
- const pkg = require('../package.json');
24
- const { buildBaselineEntries, matchBaseline } = require('../src/baseline.js');
25
- const { renderHtmlReport } = require('../src/report.js');
26
-
27
- // Piping output to `head`/`less`/etc. closes stdout early — without this,
28
- // the next write throws an unhandled EPIPE and crashes with a raw stack
29
- // trace instead of just stopping quietly, like well-behaved CLI tools do.
30
- process.stdout.on('error', (err) => {
31
- if (err && err.code === 'EPIPE') process.exit(0);
32
- throw err;
33
- });
34
-
35
- function printHelp() {
36
- process.stdout.write(`surea11y v${pkg.version}
37
-
38
- Usage:
39
- surea11y scan <file-or-url> [options]
40
-
41
- Options:
42
- --json Print the raw result object as JSON instead of a summary
43
- --locale <locale> Locale for output text (default: en)
44
- --rules <ids> Comma-separated rule IDs to run (only these)
45
- --exclude-rules <ids> Comma-separated rule IDs to exclude
46
- --tags <tags> Comma-separated tags to run (e.g. wcag2a,wcag2aa)
47
- --context <selector> CSS selector to scope the scan to a subtree
48
- --write-baseline <path> Write every current "fail" occurrence to <path>; never fails the build
49
- --baseline <path> Gate only on occurrences not already recorded in <path>
50
- --html <path> Write a self-contained, browsable HTML report to <path>
51
- -h, --help Show this help
52
- -v, --version Show the installed version
53
-
54
- Exit codes:
55
- 0 scan completed, no "fail" outcomes (or no *new* ones, with --baseline)
56
- 1 scan completed, at least one "fail" outcome (or *new* one, with --baseline)
57
- 2 usage error or the scan itself could not run (bad path/URL, network failure, etc.)
58
-
59
- Examples:
60
- surea11y scan ./index.html
61
- surea11y scan https://example.com/ --tags wcag2a,wcag2aa
62
- surea11y scan ./index.html --json > result.json
63
- surea11y scan ./index.html --write-baseline baseline.json
64
- surea11y scan ./index.html --baseline baseline.json
65
- surea11y scan ./index.html --html report.html
66
-
67
- See docs/BASELINE.md for the baseline/allowlist mechanism, docs/REPORT.md for the HTML report.
68
- `);
69
- }
70
-
71
- function parseArgs(argv) {
72
- const out = { _: [], json: false };
73
- for (let i = 0; i < argv.length; i++) {
74
- const a = argv[i];
75
- switch (a) {
76
- case '--json':
77
- out.json = true;
78
- break;
79
- case '--locale':
80
- out.locale = argv[++i];
81
- break;
82
- case '--rules':
83
- out.rules = argv[++i];
84
- break;
85
- case '--exclude-rules':
86
- out.excludeRules = argv[++i];
87
- break;
88
- case '--tags':
89
- out.tags = argv[++i];
90
- break;
91
- case '--context':
92
- out.context = argv[++i];
93
- break;
94
- case '--baseline':
95
- out.baseline = argv[++i];
96
- break;
97
- case '--write-baseline':
98
- out.writeBaseline = argv[++i];
99
- break;
100
- case '--html':
101
- out.html = argv[++i];
102
- break;
103
- case '-h':
104
- case '--help':
105
- out.help = true;
106
- break;
107
- case '-v':
108
- case '--version':
109
- out.version = true;
110
- break;
111
- default:
112
- out._.push(a);
113
- }
114
- }
115
- return out;
116
- }
117
-
118
- function isUrl(s) {
119
- return /^https?:\/\//i.test(s);
120
- }
121
-
122
- function formatError(err) {
123
- const base = err && err.message ? err.message : String(err);
124
- const cause = err && err.cause && err.cause.message ? err.cause.message : (err && err.cause ? String(err.cause) : '');
125
- return cause ? `${base}: ${cause}` : base;
126
- }
127
-
128
- async function loadHtml(target) {
129
- if (isUrl(target)) {
130
- const res = await fetch(target);
131
- if (!res.ok) {
132
- throw new Error(`Fetching ${target} failed: HTTP ${res.status} ${res.statusText}`);
133
- }
134
- return { html: await res.text(), url: target };
135
- }
136
-
137
- const resolved = path.resolve(process.cwd(), target);
138
- if (!fs.existsSync(resolved)) {
139
- throw new Error(`No such file: ${resolved}`);
140
- }
141
- return { html: fs.readFileSync(resolved, 'utf8'), url: `file://${resolved}` };
142
- }
143
-
144
- function buildEngineOptions(args) {
145
- const engineOptions = {};
146
- if (args.locale) engineOptions.locale = args.locale;
147
- if (args.rules || args.excludeRules) {
148
- engineOptions.rules = {};
149
- if (args.rules) engineOptions.rules.include = args.rules;
150
- if (args.excludeRules) engineOptions.rules.exclude = args.excludeRules;
151
- }
152
- if (args.tags) engineOptions.tags = { include: args.tags };
153
- return engineOptions;
154
- }
155
-
156
- function printSummary(result, baselineMatch) {
157
- const byOutcome = { pass: 0, fail: 0, cantTell: 0, notApplicable: 0 };
158
- for (const r of result.checksResults) {
159
- if (Object.prototype.hasOwnProperty.call(byOutcome, r.outcome)) byOutcome[r.outcome] += 1;
160
- }
161
-
162
- process.stdout.write(`\nsurea11y scan: ${result.url || '(no url)'}\n`);
163
- process.stdout.write(` pass: ${byOutcome.pass} fail: ${byOutcome.fail} cantTell: ${byOutcome.cantTell} notApplicable: ${byOutcome.notApplicable}\n\n`);
164
-
165
- const fails = result.checksResults.filter((r) => r.outcome === 'fail');
166
- if (fails.length) {
167
- process.stdout.write(`FAIL (${fails.length} rule(s)):\n`);
168
- for (const r of fails) {
169
- process.stdout.write(`\n ${r.ruleId} (${r.severity}, ${r.occurrences.length} occurrence(s))\n`);
170
- for (const occ of r.occurrences.slice(0, 5)) {
171
- process.stdout.write(` - ${occ.selector || '(no selector)'}\n ${occ.summary}\n`);
172
- if (occ.hint) process.stdout.write(` hint: ${occ.hint}\n`);
173
- }
174
- if (r.occurrences.length > 5) {
175
- process.stdout.write(` ... and ${r.occurrences.length - 5} more\n`);
176
- }
177
- }
178
- process.stdout.write('\n');
179
- }
180
-
181
- const cantTells = result.checksResults.filter((r) => r.outcome === 'cantTell');
182
- if (cantTells.length) {
183
- process.stdout.write(`cantTell — needs human review (${cantTells.length} rule(s)): ${cantTells.map((r) => r.ruleId).join(', ')}\n\n`);
184
- }
185
-
186
- if (baselineMatch) {
187
- process.stdout.write(`baseline: ${baselineMatch.knownCount} known, ${baselineMatch.newCount} new, ${baselineMatch.staleCount} stale (no longer detected)\n`);
188
- if (baselineMatch.newCount) {
189
- process.stdout.write(`\nNEW (not in baseline, ${baselineMatch.newCount} occurrence(s)):\n`);
190
- for (const occ of baselineMatch.newOccurrences.slice(0, 5)) {
191
- process.stdout.write(` - ${occ.ruleId}: ${occ.selector || '(no selector)'}\n ${occ.summary}\n`);
192
- }
193
- if (baselineMatch.newOccurrences.length > 5) {
194
- process.stdout.write(` ... and ${baselineMatch.newOccurrences.length - 5} more\n`);
195
- }
196
- }
197
- process.stdout.write('\n');
198
- }
199
- }
200
-
201
- function loadBaselineFile(baselinePath) {
202
- let raw;
203
- try {
204
- raw = fs.readFileSync(baselinePath, 'utf8');
205
- } catch (err) {
206
- throw new Error(`Could not read baseline file "${baselinePath}": ${formatError(err)}. Run with --write-baseline ${baselinePath} first to create one.`);
207
- }
208
-
209
- let parsed;
210
- try {
211
- parsed = JSON.parse(raw);
212
- } catch (err) {
213
- throw new Error(`Baseline file "${baselinePath}" is not valid JSON: ${formatError(err)}`);
214
- }
215
-
216
- if (!parsed || parsed.version !== 1 || !Array.isArray(parsed.entries)) {
217
- throw new Error(`Baseline file "${baselinePath}" is not a supported baseline (expected { version: 1, entries: [...] }). Regenerate it with --write-baseline.`);
218
- }
219
-
220
- return parsed;
221
- }
222
-
223
- async function runScan(args) {
224
- const target = args._[0];
225
- if (!target) {
226
- process.stderr.write('Error: scan requires a file path or URL. See --help.\n');
227
- process.exitCode = 2;
228
- return;
229
- }
230
-
231
- if (args.baseline && args.writeBaseline) {
232
- process.stderr.write('Error: --baseline and --write-baseline cannot be used together in the same run. See --help.\n');
233
- process.exitCode = 2;
234
- return;
235
- }
236
-
237
- let baselineFile = null;
238
- if (args.baseline) {
239
- try {
240
- baselineFile = loadBaselineFile(args.baseline);
241
- } catch (err) {
242
- process.stderr.write(`Error: ${formatError(err)}\n`);
243
- process.exitCode = 2;
244
- return;
245
- }
246
- }
247
-
248
- let html, url;
249
- try {
250
- ({ html, url } = await loadHtml(target));
251
- } catch (err) {
252
- process.stderr.write(`Error: ${formatError(err)}\n`);
253
- process.exitCode = 2;
254
- return;
255
- }
256
-
257
- let JSDOM;
258
- try {
259
- ({ JSDOM } = require('jsdom'));
260
- } catch {
261
- process.stderr.write('Error: the surea11y CLI requires jsdom. Run `npm install jsdom` (it should already be a dependency of this package — this likely means a broken install).\n');
262
- process.exitCode = 2;
263
- return;
264
- }
265
-
266
- const { runDomRulesInPage } = require('../src/index.js');
267
-
268
- const dom = new JSDOM(html, { url, pretendToBeVisual: true });
269
- global.window = dom.window;
270
- global.document = dom.window.document;
271
-
272
- let result;
273
- try {
274
- result = runDomRulesInPage(url, args.context || null, buildEngineOptions(args), null);
275
- } finally {
276
- dom.window.close();
277
- }
278
-
279
- if (args.html) {
280
- fs.writeFileSync(args.html, renderHtmlReport(result, { title: `surea11y scan report — ${target}` }));
281
- process.stderr.write(`Wrote HTML report to: ${args.html}\n`);
282
- }
283
-
284
- if (args.writeBaseline) {
285
- const entries = buildBaselineEntries(result);
286
- const payload = { version: 1, generatedAt: new Date().toISOString(), entries };
287
- fs.writeFileSync(args.writeBaseline, JSON.stringify(payload, null, 2) + '\n');
288
-
289
- if (args.json) {
290
- process.stdout.write(JSON.stringify({ ...result, baseline: { mode: 'write', path: args.writeBaseline, entries: entries.length } }, null, 2) + '\n');
291
- } else {
292
- printSummary(result);
293
- }
294
- process.stderr.write(`Wrote ${entries.length} occurrence(s) to baseline: ${args.writeBaseline}\n`);
295
- process.exitCode = 0;
296
- return;
297
- }
298
-
299
- if (baselineFile) {
300
- const match = matchBaseline(result, baselineFile.entries);
301
-
302
- if (args.json) {
303
- process.stdout.write(JSON.stringify({ ...result, baseline: { mode: 'check', ...match } }, null, 2) + '\n');
304
- } else {
305
- printSummary(result, match);
306
- }
307
- process.exitCode = match.newCount > 0 ? 1 : 0;
308
- return;
309
- }
310
-
311
- if (args.json) {
312
- process.stdout.write(JSON.stringify(result, null, 2) + '\n');
313
- } else {
314
- printSummary(result);
315
- }
316
-
317
- const hasFail = result.checksResults.some((r) => r.outcome === 'fail');
318
- process.exitCode = hasFail ? 1 : 0;
319
- }
320
-
321
- async function main() {
322
- const args = parseArgs(process.argv.slice(2));
323
-
324
- if (args.version) {
325
- process.stdout.write(`${pkg.version}\n`);
326
- return;
327
- }
328
- if (args.help || args._.length === 0) {
329
- printHelp();
330
- process.exitCode = args._.length === 0 && !args.help ? 2 : 0;
331
- return;
332
- }
333
-
334
- const [command, ...rest] = args._;
335
- if (command !== 'scan') {
336
- process.stderr.write(`Error: unknown command "${command}". Only "scan" is supported. See --help.\n`);
337
- process.exitCode = 2;
338
- return;
339
- }
340
-
341
- args._ = rest;
342
- await runScan(args);
343
- }
344
-
345
- main().catch((err) => {
346
- process.stderr.write(`Error: ${formatError(err)}\n`);
347
- process.exitCode = 2;
348
- });
package/docs/CLI.md DELETED
@@ -1,75 +0,0 @@
1
- # CLI
2
-
3
- `surea11y` ships a small CLI (`bin/core.js`) for ad hoc scans and CI, on top of the library API described in [`INTEGRATION.md`](./INTEGRATION.md).
4
-
5
- ```sh
6
- npx @surea11y/core scan ./index.html
7
- npx @surea11y/core scan https://example.com/
8
- ```
9
-
10
- ## What it can and can't scan
11
-
12
- The CLI reads **static HTML only** — a local file, or the raw response of an HTTP(S) GET request — and never executes page JavaScript. That means client-rendered content (anything your framework injects after page load) won't be captured, and geometry-dependent rules like `target-size-minimum` will report `notApplicable` (no real CSS layout — see [`LIMITATIONS.md`](./LIMITATIONS.md)). If you need either of those, drive a real browser yourself and use `runa11yCoreInPage` directly — see [`INTEGRATION.md`](./INTEGRATION.md) Pattern 2. The CLI is the fast path for static/server-rendered pages and CI; the library is what you reach for beyond that.
13
-
14
- ## Options
15
-
16
- | Flag | Meaning |
17
- |---|---|
18
- | `--json` | Print the raw result object (see [`OUTPUT_SCHEMA.md`](./OUTPUT_SCHEMA.md)) instead of a human-readable summary. |
19
- | `--locale <locale>` | Output text locale (default `en`) — see [`I18N.md`](./I18N.md). |
20
- | `--rules <ids>` | Comma-separated rule IDs — only run these. |
21
- | `--exclude-rules <ids>` | Comma-separated rule IDs — never run these. |
22
- | `--tags <tags>` | Comma-separated tags — e.g. `--tags wcag2a,wcag2aa` to target a conformance level (see [`WCAG_CONFORMANCE.md`](./WCAG_CONFORMANCE.md)). |
23
- | `--context <selector>` | Scope the scan to one CSS-selected subtree. |
24
- | `--write-baseline <path>` | Write every current `fail` occurrence to `<path>`; never fails the build. See [`BASELINE.md`](./BASELINE.md). |
25
- | `--baseline <path>` | Gate only on occurrences not already recorded in `<path>`. See [`BASELINE.md`](./BASELINE.md). |
26
- | `--html <path>` | Write a self-contained, browsable HTML report to `<path>`. See [`REPORT.md`](./REPORT.md). |
27
- | `-h`, `--help` | Show usage. |
28
- | `-v`, `--version` | Show the installed version. |
29
-
30
- These map directly onto `engineOptions`/`runOnly` (see [`ENGINE_OPTIONS.md`](./ENGINE_OPTIONS.md)) — `--rules`/`--exclude-rules` become `engineOptions.rules.include`/`.exclude`, `--tags` becomes `engineOptions.tags.include`.
31
-
32
- ## Exit codes
33
-
34
- | Code | Meaning |
35
- |---|---|
36
- | `0` | Scan completed, no `fail` outcomes. |
37
- | `1` | Scan completed, at least one `fail` outcome — the CI-gating case. |
38
- | `2` | Usage error, or the scan itself couldn't run (bad path/URL, network failure, missing `jsdom`). |
39
-
40
- `cantTell` outcomes never affect the exit code — they're printed as a "needs human review" summary, consistent with the manual/`cantTell` mental model in [`TROUBLESHOOTING.md`](./TROUBLESHOOTING.md#should-i-treat-canttell-as-a-failure). If you need `cantTell`-aware gating, use `--json` and inspect `checksResults` yourself, or call the library directly (see [`INTEGRATION.md`](./INTEGRATION.md#ci-gating-a-build-on-the-result)).
41
-
42
- With `--baseline`, exit code `1` means at least one *new* (not-yet-baselined) `fail` occurrence, not any `fail` occurrence — see [`BASELINE.md`](./BASELINE.md).
43
-
44
- ## Baseline / allowlist
45
-
46
- For an existing, imperfect site, gating on every `fail` on day one is often an adoption blocker. `--write-baseline`/`--baseline` let you accept the current state once and gate CI only on genuinely new violations from then on:
47
-
48
- ```sh
49
- surea11y scan ./dist/index.html --write-baseline baseline.json # once, commit the file
50
- surea11y scan ./dist/index.html --baseline baseline.json # in CI, from then on
51
- ```
52
-
53
- See [`BASELINE.md`](./BASELINE.md) for the matching semantics, file format, and known limitations.
54
-
55
- ## HTML report
56
-
57
- For a browsable view of a scan's results — hero summary, WCAG rollup grouped by conformance level, and a searchable/filterable occurrence table — rather than raw JSON or a terminal summary:
58
-
59
- ```sh
60
- surea11y scan ./dist/index.html --html report.html
61
- ```
62
-
63
- Open `report.html` directly from disk; no server, no external assets. Works alongside any other output mode. See [`REPORT.md`](./REPORT.md).
64
-
65
- ## In CI
66
-
67
- ```sh
68
- npx @surea11y/core scan ./dist/index.html || exit 1
69
- ```
70
-
71
- Or, since the exit code already reflects pass/fail, just let the command's own exit code propagate — most CI systems fail the step automatically on a non-zero exit.
72
-
73
- ## A note on dependencies
74
-
75
- The CLI is the one part of this package that depends on `jsdom` — `require('@surea11y/core')` as a library never loads it (see [`SECURITY.md`](../SECURITY.md)). If you only ever use the library API directly against your own DOM (jsdom, a real browser, whatever you already have), you're not paying for `jsdom` a second time.