ultra11y 5.11.3 → 5.12.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.
@@ -10,7 +10,7 @@
10
10
  "name": "ultra11y",
11
11
  "source": "./",
12
12
  "description": "Automatic accessibility review of the code under change before every commit, push and pull request, plus full WCAG 2.2 AA / RGAA audits and conformance deliverables on demand.",
13
- "version": "5.11.3",
13
+ "version": "5.12.0",
14
14
  "author": { "name": "maxgfr" },
15
15
  "homepage": "https://github.com/maxgfr/ultra11y",
16
16
  "repository": "https://github.com/maxgfr/ultra11y",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ultra11y",
3
3
  "description": "Accessibility for WCAG 2.2 AA and country standards (RGAA…): an automatic review of the code under change before every commit, push and pull request, plus full audits and conformance deliverables on demand.",
4
- "version": "5.11.3",
4
+ "version": "5.12.0",
5
5
  "author": { "name": "maxgfr" },
6
6
  "homepage": "https://github.com/maxgfr/ultra11y",
7
7
  "repository": "https://github.com/maxgfr/ultra11y",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ultra11y",
3
3
  "description": "Accessibility for WCAG 2.2 AA and country standards (RGAA…): an automatic review of the code under change before every commit, push and pull request, plus full audits and conformance deliverables on demand.",
4
- "version": "5.11.3",
4
+ "version": "5.12.0",
5
5
  "author": { "name": "maxgfr", "url": "https://github.com/maxgfr" },
6
6
  "homepage": "https://github.com/maxgfr/ultra11y",
7
7
  "repository": "https://github.com/maxgfr/ultra11y",
@@ -27,7 +27,7 @@ import { fileURLToPath } from "node:url";
27
27
  // Ownership marker: `ultra11y install --opencode` refuses to overwrite a plugin file that
28
28
  // does not carry it, so a user's own ultra11y.js is never clobbered.
29
29
  const ULTRA11Y_OPENCODE_PLUGIN = "ULTRA11Y_OPENCODE_PLUGIN";
30
- const ULTRA11Y_PLUGIN_VERSION = "5.11.3";
30
+ const ULTRA11Y_PLUGIN_VERSION = "5.12.0";
31
31
 
32
32
  const here = dirname(fileURLToPath(import.meta.url));
33
33
 
@@ -1,4 +1,4 @@
1
- import { S as SnapshotPayload, F as FindingLike } from './payload-DzQb84Kw.js';
1
+ import { S as SnapshotPayload, F as FindingLike } from './payload-DvcIA6VQ.js';
2
2
 
3
3
  type On = (event: string, handler: any) => void;
4
4
  interface TaskPayload extends SnapshotPayload {
package/dist/cypress.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { C as CheckOptions } from './payload-DzQb84Kw.js';
2
- export { s as slugify } from './payload-DzQb84Kw.js';
1
+ import { C as CheckOptions } from './payload-DvcIA6VQ.js';
2
+ export { s as slugify } from './payload-DvcIA6VQ.js';
3
3
 
4
4
  interface CypressCheckOptions extends CheckOptions {
5
5
  /** Capture a viewport screenshot so the pixel tier can run. On by default — the plugin
package/dist/cypress.mjs CHANGED
@@ -155,7 +155,7 @@ function stayedOnPage(expected, actual) {
155
155
  if (a === void 0 || b === void 0) return true;
156
156
  return a === b;
157
157
  }
158
- function buildPayload(collected, url, runner, opts, screenshot, probes) {
158
+ function buildPayload(collected, url, runner, opts, screenshot, probes, axe) {
159
159
  const id = opts.as || slugify(url);
160
160
  return {
161
161
  meta: {
@@ -188,7 +188,10 @@ function buildPayload(collected, url, runner, opts, screenshot, probes) {
188
188
  // What the live probes measured, when the caller asked for them. It rides in the payload
189
189
  // because `snapshot write` persists it beside the DOM — the audit that folds it runs later
190
190
  // and in another process, so a measurement kept in memory decides nothing.
191
- ...probes ? { probes } : {}
191
+ ...probes ? { probes } : {},
192
+ // Same contract as the probes: it rides in the payload and `snapshot write` persists it
193
+ // beside the DOM, so the offline re-audit sees what the browser saw.
194
+ ...axe ? { axe } : {}
192
195
  };
193
196
  }
194
197
 
@@ -0,0 +1,101 @@
1
+ interface FindingLike {
2
+ ruleId: string;
3
+ criteriaId: string;
4
+ file: string;
5
+ line: number;
6
+ severity: string;
7
+ message: string;
8
+ advisory?: boolean;
9
+ origin?: {
10
+ sourceFile?: string;
11
+ sourceLine?: number;
12
+ };
13
+ }
14
+ interface AuditLike {
15
+ findings?: FindingLike[];
16
+ }
17
+ interface SnapshotPayload {
18
+ meta: Record<string, unknown>;
19
+ dom: string;
20
+ styles?: unknown;
21
+ boxes?: unknown;
22
+ css?: unknown;
23
+ screenshot?: string;
24
+ /** What the live probes measured, when the caller ran them. */
25
+ probes?: unknown;
26
+ /** What axe-core found on this page, when the caller ran it. */
27
+ axe?: unknown;
28
+ }
29
+ /** Same slug rule as the engine (src/snapshot.ts slugifyPageId): accent-folded URL path. */
30
+ declare function slugify(url: string): string;
31
+ interface CheckOptions {
32
+ /** The page id (directory name). Defaults to the slugified URL path. */
33
+ as?: string;
34
+ /** Human page name for the report. Defaults to the document title. */
35
+ name?: string;
36
+ /** Severity at which the check fails, or `false` to record without ever failing. */
37
+ failOn?: "blocking" | "major" | "minor" | false;
38
+ /** Mark the page as sitting behind authentication. */
39
+ auth?: boolean;
40
+ /** Source files that rendered it — findings are then reported against YOUR code. */
41
+ sources?: string[];
42
+ /** Reproduction notes carried into the auditor ticket. */
43
+ notes?: string;
44
+ /** Capture a viewport screenshot for the pixel tier (Playwright: on by default). */
45
+ screenshot?: boolean;
46
+ /** The path you NAVIGATED to. Given it, the fixture refuses to record a page the browser
47
+ * did not stay on — a guarded route that redirected, a session that expired — instead of
48
+ * filing that other screen under this page's `as`/`name`. Nothing about such a snapshot
49
+ * looks wrong, which is what makes it the worst thing an accessibility report can carry.
50
+ * Compared by path (a query or fragment the app appends to its own route is the same
51
+ * page); a full URL works too. `scan --sample` applies the same rule. */
52
+ expectPath?: string;
53
+ /** Run the LIVE PROBES on this page after collecting it: 200% zoom, 320px reflow, the
54
+ * text-spacing override, focus visibility and content-on-hover.
55
+ *
56
+ * These are the measurements a recorded snapshot can never settle, because they are
57
+ * properties of a page being acted on rather than of a page as it stands. `scan` runs them
58
+ * in its own browser, which is exactly why they were undecidable on any screen behind a
59
+ * login or a state machine — the scanner arrives cold and gets a redirect. Your suite has
60
+ * the page in the right state; this is how it says so.
61
+ *
62
+ * Off by default: the probes press Tab, hover, resize the viewport and inject a stylesheet,
63
+ * and a suite should opt into that. Everything is restored before it returns, and the
64
+ * snapshot has already been collected, so what they measure cannot alter what was recorded.
65
+ * Pass an array to run only some of them (by criterion: "1.4.4", "1.4.10", "1.4.12",
66
+ * "1.4.13", "2.4.7"), or an object to also set the bounds — how wide the reflow probe
67
+ * narrows to, how many focusables are worth tabbing through, how many hits are worth
68
+ * recording. 320px is normative and stays the default; the other two are judgements about
69
+ * YOUR pages, and a screen with 400 focusables should be able to say so rather than being
70
+ * quietly cut off at 120. */
71
+ probes?: boolean | string[] | ProbeTuning;
72
+ /** Run AXE-CORE on the page before recording it.
73
+ *
74
+ * `scan` has always driven axe; your suite never did — and your suite is the only tier that
75
+ * reaches a page behind a login and a state machine, which is exactly where the criteria
76
+ * axe decides (computed contrast above all) were left « to assess » run after run.
77
+ *
78
+ * Default `auto`: axe runs when `@axe-core/playwright` resolves from your project, and is
79
+ * silently skipped when it does not — so adding this costs nothing to a repo that has not
80
+ * installed it. `false` turns it off; `true` demands it and reports loudly if it cannot be
81
+ * resolved, which is what you want in CI where a silent downgrade is indistinguishable from
82
+ * a clean page. */
83
+ axe?: boolean | "auto";
84
+ }
85
+ /** The bounds a repository may put on the live probes. Stated once, here, because the same
86
+ * list is read from `.ultra11yrc.json`, accepted at the call site and handed to the engine —
87
+ * three copies of a field list is three chances for one of them to fall behind.
88
+ *
89
+ * `budgetMs` and `actionTimeoutMs` are not tuning in the same sense as the others: they are
90
+ * what stops a probe from spending SOMEBODY ELSE'S test timeout. See ProbeLimits. */
91
+ interface ProbeTuning {
92
+ only?: string[];
93
+ reflowWidth?: number;
94
+ maxFocusables?: number;
95
+ maxHits?: number;
96
+ maxTriggers?: number;
97
+ actionTimeoutMs?: number;
98
+ budgetMs?: number;
99
+ }
100
+
101
+ export { type AuditLike as A, type CheckOptions as C, type FindingLike as F, type SnapshotPayload as S, slugify as s };
@@ -1,4 +1,4 @@
1
- import { C as CheckOptions, A as AuditLike } from './payload-DzQb84Kw.js';
1
+ import { C as CheckOptions, A as AuditLike } from './payload-DvcIA6VQ.js';
2
2
 
3
3
  interface PlaywrightPage {
4
4
  evaluate(script: string): Promise<unknown>;
@@ -489,7 +489,7 @@ function stayedOnPage(expected, actual) {
489
489
  if (a === void 0 || b === void 0) return true;
490
490
  return a === b;
491
491
  }
492
- function buildPayload(collected, url, runner, opts, screenshot, probes) {
492
+ function buildPayload(collected, url, runner, opts, screenshot, probes, axe) {
493
493
  const id = opts.as || slugify(url);
494
494
  return {
495
495
  meta: {
@@ -522,7 +522,10 @@ function buildPayload(collected, url, runner, opts, screenshot, probes) {
522
522
  // What the live probes measured, when the caller asked for them. It rides in the payload
523
523
  // because `snapshot write` persists it beside the DOM — the audit that folds it runs later
524
524
  // and in another process, so a measurement kept in memory decides nothing.
525
- ...probes ? { probes } : {}
525
+ ...probes ? { probes } : {},
526
+ // Same contract as the probes: it rides in the payload and `snapshot write` persists it
527
+ // beside the DOM, so the offline re-audit sees what the browser saw.
528
+ ...axe ? { axe } : {}
526
529
  };
527
530
  }
528
531
  function gate(result, pageName, failOn) {
@@ -588,6 +591,32 @@ function fromConfig() {
588
591
  return {};
589
592
  }
590
593
  }
594
+ async function runAxe(page, mode) {
595
+ if (mode === false) return void 0;
596
+ const demanded = mode === true;
597
+ let AxeBuilder;
598
+ try {
599
+ const req = createRequire2(join(process.cwd(), "package.json"));
600
+ const mod = req("@axe-core/playwright");
601
+ AxeBuilder = mod.default ?? mod;
602
+ } catch (e) {
603
+ if (demanded) {
604
+ console.warn(
605
+ `ultra11y: axe was requested but @axe-core/playwright could not be resolved from ${process.cwd()} \u2014 ${e instanceof Error ? e.message : String(e)}. Install it, or set \`axe: false\`. The criteria axe decides stay to assess.`
606
+ );
607
+ }
608
+ return void 0;
609
+ }
610
+ try {
611
+ const res = await new AxeBuilder({ page }).analyze();
612
+ return { ran: true, violations: res.violations ?? [] };
613
+ } catch (e) {
614
+ console.warn(
615
+ `ultra11y: the axe pass failed on this page \u2014 ${e instanceof Error ? e.message : String(e)}. The snapshot was still recorded; the criteria axe decides stay to assess.`
616
+ );
617
+ return void 0;
618
+ }
619
+ }
591
620
  async function checkA11y(page, opts = {}) {
592
621
  const collected = await page.evaluate(COLLECT_SNAPSHOT);
593
622
  let shot;
@@ -613,7 +642,8 @@ async function checkA11y(page, opts = {}) {
613
642
  );
614
643
  }
615
644
  }
616
- const payload = buildPayload(collected, url, "playwright", opts, shot, probes);
645
+ const axe = await runAxe(page, opts.axe ?? "auto");
646
+ const payload = buildPayload(collected, url, "playwright", opts, shot, probes, axe);
617
647
  const result = auditSnapshot(payload);
618
648
  if (opts.report) writePagesReport(typeof opts.report === "object" ? opts.report : {});
619
649
  gate(result, String(payload.meta.name), opts.failOn);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ultra11y",
3
- "version": "5.11.3",
3
+ "version": "5.12.0",
4
4
  "description": "Audit HTML/CSS/JSX against WCAG 2.2 AA and produce a dated compliance report, or author/review accessible markup (native-HTML-first). A skills.sh agent skill: an install-free static engine (bundled, no npm install) plus the agent's judgment, with check/verify gates against hallucinated non-conformities, and pluggable in-repo standards packs (RGAA France first; Section 508 & EN 301 549 next).",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@10.33.0",