ultra11y 5.11.3 → 5.13.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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.opencode/plugins/ultra11y.js +1 -1
- package/dist/cypress-plugin.d.ts +1 -1
- package/dist/cypress.d.ts +2 -2
- package/dist/cypress.mjs +5 -2
- package/dist/payload-DvcIA6VQ.d.ts +101 -0
- package/dist/playwright.d.ts +1 -1
- package/dist/playwright.mjs +33 -3
- package/package.json +1 -1
- package/scripts/ultra11y.mjs +508 -290
- package/skills/review-a11y/SKILL.md +1 -1
- package/skills/review-a11y/scripts/ultra11y.mjs +508 -290
- package/skills/ultra11y/SKILL.md +1 -1
- package/skills/ultra11y/scripts/ultra11y.mjs +508 -290
- package/src/adjudicate.ts +8 -1
- package/src/annotate.ts +92 -7
- package/src/audit.ts +94 -9
- package/src/axe-map.ts +21 -0
- package/src/cli.ts +14 -2
- package/src/integrations/payload.ts +18 -0
- package/src/integrations/playwright.ts +47 -1
- package/src/pages-report.ts +37 -1
- package/src/report.ts +129 -12
- package/src/scan.ts +7 -2
- package/src/snapshot.ts +5 -0
- package/src/standards/derive.ts +52 -14
- package/src/types.ts +43 -1
|
@@ -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.
|
|
13
|
+
"version": "5.13.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.
|
|
4
|
+
"version": "5.13.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.
|
|
4
|
+
"version": "5.13.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.
|
|
30
|
+
const ULTRA11Y_PLUGIN_VERSION = "5.13.0";
|
|
31
31
|
|
|
32
32
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
33
33
|
|
package/dist/cypress-plugin.d.ts
CHANGED
package/dist/cypress.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { C as CheckOptions } from './payload-
|
|
2
|
-
export { s as slugify } from './payload-
|
|
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 };
|
package/dist/playwright.d.ts
CHANGED
package/dist/playwright.mjs
CHANGED
|
@@ -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
|
|
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.
|
|
3
|
+
"version": "5.13.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",
|