ultra11y 4.4.0 → 4.4.1
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/package.json +3 -1
- package/scripts/build-guidance-wcag.mjs +335 -0
- package/scripts/ultra11y.mjs +1247 -117
- package/scripts/vendor/wcag-2.2-techniques.json +840 -0
- package/skills/review-a11y/SKILL.md +1 -1
- package/skills/review-a11y/scripts/ultra11y.mjs +1247 -117
- package/skills/ultra11y/SKILL.md +1 -1
- package/skills/ultra11y/references/guidance.md +33 -5
- package/skills/ultra11y/scripts/ultra11y.mjs +1247 -117
- package/src/data/guidance/wcag.json +973 -115
- package/src/guidance/index.ts +12 -9
- package/src/types.ts +1 -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": "4.4.
|
|
13
|
+
"version": "4.4.1",
|
|
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": "4.4.
|
|
4
|
+
"version": "4.4.1",
|
|
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": "4.4.
|
|
4
|
+
"version": "4.4.1",
|
|
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 = "4.4.
|
|
30
|
+
const ULTRA11Y_PLUGIN_VERSION = "4.4.1";
|
|
31
31
|
|
|
32
32
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
33
33
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ultra11y",
|
|
3
|
-
"version": "4.4.
|
|
3
|
+
"version": "4.4.1",
|
|
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",
|
|
@@ -58,6 +58,8 @@
|
|
|
58
58
|
"build:wcag": "node scripts/build-standards.mjs",
|
|
59
59
|
"build:pack:rgaa": "node scripts/build-pack-rgaa.mjs",
|
|
60
60
|
"check:pack:rgaa": "node scripts/build-pack-rgaa.mjs --check",
|
|
61
|
+
"build:guidance:wcag": "node scripts/build-guidance-wcag.mjs",
|
|
62
|
+
"check:guidance:wcag": "node scripts/build-guidance-wcag.mjs --check",
|
|
61
63
|
"build:criteria": "node scripts/ultra11y.mjs criteria --generate > skills/ultra11y/references/criteria.md",
|
|
62
64
|
"build:act": "UPDATE_ACT_MATRIX=1 vitest run tests/act-matrix-sync.test.ts",
|
|
63
65
|
"build:act:refresh": "node scripts/build-act-corpus.mjs --refresh",
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// DEV-ONLY (not in `bin`). Builds src/data/guidance/wcag.json — the WCAG-keyed
|
|
3
|
+
// implementation guidance — from the W3C's own Techniques and Understanding documents.
|
|
4
|
+
//
|
|
5
|
+
// Nothing here is hand-written. The RGAA guidance is derived from an upstream rule source
|
|
6
|
+
// and rebuilt by a script; this one had been authored by hand, which made it the only
|
|
7
|
+
// dataset in the repo whose content nobody could re-derive or check against a source. Now
|
|
8
|
+
// it is generated the same way everything else is:
|
|
9
|
+
//
|
|
10
|
+
// · WHICH techniques implement a criterion — understanding/understanding.11tydata.js,
|
|
11
|
+
// the W3C's own machine-readable association map (sufficient / advisory / failure).
|
|
12
|
+
// · WHAT the criterion asks — the "In brief" block of understanding/<ver>/<slug>.html
|
|
13
|
+
// (Goal / What to do / Why it's important), written by the WCAG working group.
|
|
14
|
+
// · The GOOD example — the first sufficient technique carrying an HTML or CSS code
|
|
15
|
+
// sample, in the W3C's own listing order.
|
|
16
|
+
// · The BAD example — the first documented FAILURE carrying one.
|
|
17
|
+
//
|
|
18
|
+
// The selection is positional, never editorial: "the first one that has a code sample",
|
|
19
|
+
// in the order W3C lists them. A criterion whose techniques carry no HTML/CSS sample gets
|
|
20
|
+
// an entry with the official summary and no example, rather than an invented one.
|
|
21
|
+
//
|
|
22
|
+
// WCAG 2.2 © W3C, reused under the W3C Document License; the code samples are Code
|
|
23
|
+
// Components under the W3C Software License. See NOTICE.
|
|
24
|
+
//
|
|
25
|
+
// Usage:
|
|
26
|
+
// node scripts/build-guidance-wcag.mjs --refresh # re-fetch (network) the vendored snapshot
|
|
27
|
+
// node scripts/build-guidance-wcag.mjs # emit the dataset from the snapshot
|
|
28
|
+
// node scripts/build-guidance-wcag.mjs --check # verify the committed dataset matches
|
|
29
|
+
import { writeFileSync, readFileSync, existsSync, mkdirSync, rmSync } from "node:fs";
|
|
30
|
+
import { execFileSync } from "node:child_process";
|
|
31
|
+
import { dirname, join } from "node:path";
|
|
32
|
+
import { fileURLToPath } from "node:url";
|
|
33
|
+
import { tmpdir } from "node:os";
|
|
34
|
+
|
|
35
|
+
const root = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
36
|
+
const VENDOR = join(root, "scripts", "vendor", "wcag-2.2-techniques.json");
|
|
37
|
+
const SNAPSHOT = join(root, "scripts", "vendor", "wcag-2.2-sc.json");
|
|
38
|
+
const OUT = join(root, "src", "data", "guidance", "wcag.json");
|
|
39
|
+
const BIOME = join(root, "node_modules", ".bin", "biome");
|
|
40
|
+
const RAW = "https://raw.githubusercontent.com/w3c/wcag/main";
|
|
41
|
+
const VER_OF = { "2.0": "20", "2.1": "21", "2.2": "22" };
|
|
42
|
+
|
|
43
|
+
// Technique id → the directory it lives in. The W3C names these by technology, and the
|
|
44
|
+
// prefix is the technology — no directory listing needed, and no id ever special-cased.
|
|
45
|
+
const TECH_DIR = {
|
|
46
|
+
ARIA: "aria",
|
|
47
|
+
C: "css",
|
|
48
|
+
F: "failures",
|
|
49
|
+
FLASH: "flash",
|
|
50
|
+
G: "general",
|
|
51
|
+
H: "html",
|
|
52
|
+
PDF: "pdf",
|
|
53
|
+
SCR: "client-side-script",
|
|
54
|
+
SL: "silverlight",
|
|
55
|
+
SM: "smil",
|
|
56
|
+
SVR: "server-side-script",
|
|
57
|
+
T: "text",
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
function techDir(id) {
|
|
61
|
+
const prefix = (id.match(/^[A-Z]+/) || [])[0];
|
|
62
|
+
return TECH_DIR[prefix];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function biomeFormat(text, relPath) {
|
|
66
|
+
return execFileSync(BIOME, ["format", `--stdin-file-path=${relPath}`], { input: text, encoding: "utf8" });
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function fetchText(path) {
|
|
70
|
+
const r = await fetch(`${RAW}/${path}`);
|
|
71
|
+
if (!r.ok) return null; // a technique listed but not present is data, not a crash
|
|
72
|
+
return r.text();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const ENTITIES = {
|
|
76
|
+
" ": " ",
|
|
77
|
+
"&": "&",
|
|
78
|
+
"<": "<",
|
|
79
|
+
">": ">",
|
|
80
|
+
""": '"',
|
|
81
|
+
"'": "'",
|
|
82
|
+
"’": "’",
|
|
83
|
+
"‘": "‘",
|
|
84
|
+
"“": "“",
|
|
85
|
+
"”": "”",
|
|
86
|
+
"—": "—",
|
|
87
|
+
"–": "–",
|
|
88
|
+
"…": "…",
|
|
89
|
+
};
|
|
90
|
+
function decodeEntities(s) {
|
|
91
|
+
return s
|
|
92
|
+
.replace(/&#(\d+);/g, (_, d) => String.fromCodePoint(Number(d)))
|
|
93
|
+
.replace(/&#x([0-9a-f]+);/gi, (_, h) => String.fromCodePoint(Number.parseInt(h, 16)))
|
|
94
|
+
.replace(/&[a-z]+;/gi, (m) => ENTITIES[m] ?? m);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Tags out, entities in, whitespace collapsed — for prose, never for code. */
|
|
98
|
+
function plain(html) {
|
|
99
|
+
return decodeEntities(html.replace(/<[^>]+>/g, " "))
|
|
100
|
+
.replace(/\s+/g, " ")
|
|
101
|
+
.trim();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* The W3C's own association map, read by EVALUATING the module rather than regexing it.
|
|
106
|
+
*
|
|
107
|
+
* It is an ES module exporting a function of 11ty's data cascade; calling it with a stub
|
|
108
|
+
* yields the map exactly as the W3C's own site build sees it. Parsing this shape with a
|
|
109
|
+
* regex would be guesswork, and it is dev-only code from the same repo this project already
|
|
110
|
+
* derives its entire WCAG dataset from.
|
|
111
|
+
*/
|
|
112
|
+
async function fetchAssociations() {
|
|
113
|
+
const src = await fetchText("understanding/understanding.11tydata.js");
|
|
114
|
+
if (!src) throw new Error("build-guidance-wcag: understanding.11tydata.js not found upstream");
|
|
115
|
+
const tmp = join(tmpdir(), `u11y-assoc-${process.pid}.mjs`);
|
|
116
|
+
writeFileSync(tmp, src);
|
|
117
|
+
try {
|
|
118
|
+
const mod = await import(`file://${tmp}`);
|
|
119
|
+
return mod.default({ understandingUrl: "" }).associatedTechniques ?? {};
|
|
120
|
+
} finally {
|
|
121
|
+
rmSync(tmp, { force: true });
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Every technique id an entry mentions, flattened, in the W3C's own listing order. */
|
|
126
|
+
function flattenTechniques(node, out = []) {
|
|
127
|
+
if (node === null || node === undefined) return out;
|
|
128
|
+
if (typeof node === "string") {
|
|
129
|
+
// Ids only: the map also carries free prose ("Providing a descriptive label").
|
|
130
|
+
if (/^[A-Z]+\d+$/.test(node)) out.push(node);
|
|
131
|
+
return out;
|
|
132
|
+
}
|
|
133
|
+
if (Array.isArray(node)) {
|
|
134
|
+
for (const n of node) flattenTechniques(n, out);
|
|
135
|
+
return out;
|
|
136
|
+
}
|
|
137
|
+
if (typeof node === "object") {
|
|
138
|
+
// { and: [...] } / { title, techniques, groups } — order matters, so walk explicitly.
|
|
139
|
+
for (const key of ["and", "techniques", "groups", "using"]) {
|
|
140
|
+
if (node[key]) flattenTechniques(node[key], out);
|
|
141
|
+
}
|
|
142
|
+
return out;
|
|
143
|
+
}
|
|
144
|
+
return out;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** The first HTML or CSS code sample in a technique document, with the prose above it. */
|
|
148
|
+
function firstCodeExample(html) {
|
|
149
|
+
const EX = /<section class="example">([\s\S]*?)<\/section>/g;
|
|
150
|
+
let m;
|
|
151
|
+
while ((m = EX.exec(html))) {
|
|
152
|
+
const block = m[1];
|
|
153
|
+
const code = /<pre><code class="language-(html|css)">([\s\S]*?)<\/code><\/pre>/.exec(block);
|
|
154
|
+
if (!code) continue;
|
|
155
|
+
const snippet = decodeEntities(code[2]).trim();
|
|
156
|
+
if (!snippet) continue;
|
|
157
|
+
const before = block.slice(0, code.index);
|
|
158
|
+
return { lang: code[1], code: snippet, note: plain(before) };
|
|
159
|
+
}
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function techniqueTitle(html) {
|
|
164
|
+
const h1 = /<h1>([\s\S]*?)<\/h1>/.exec(html);
|
|
165
|
+
return h1 ? plain(h1[1]) : "";
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** The "In brief" block: the working group's own Goal / What to do / Why. */
|
|
169
|
+
function briefOf(html) {
|
|
170
|
+
const sec = /<section id="brief">([\s\S]*?)<\/section>/.exec(html);
|
|
171
|
+
if (!sec) return null;
|
|
172
|
+
const out = {};
|
|
173
|
+
const DL = /<dt>([\s\S]*?)<\/dt>\s*<dd>([\s\S]*?)<\/dd>/g;
|
|
174
|
+
let m;
|
|
175
|
+
while ((m = DL.exec(sec[1]))) out[plain(m[1]).toLowerCase().replace(/[^a-z]+/g, "")] = plain(m[2]);
|
|
176
|
+
return Object.keys(out).length ? out : null;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async function refresh() {
|
|
180
|
+
if (!existsSync(SNAPSHOT)) {
|
|
181
|
+
console.error(`build-guidance-wcag --refresh: missing ${SNAPSHOT}. Run: node scripts/build-standards.mjs --refresh <w3c/wcag checkout>`);
|
|
182
|
+
process.exit(1);
|
|
183
|
+
}
|
|
184
|
+
const snap = JSON.parse(readFileSync(SNAPSHOT, "utf8"));
|
|
185
|
+
const assoc = await fetchAssociations();
|
|
186
|
+
|
|
187
|
+
const cache = new Map();
|
|
188
|
+
const techniqueDoc = async (id) => {
|
|
189
|
+
if (cache.has(id)) return cache.get(id);
|
|
190
|
+
const dir = techDir(id);
|
|
191
|
+
const doc = dir ? await fetchText(`techniques/${dir}/${id}.html`) : null;
|
|
192
|
+
cache.set(id, doc);
|
|
193
|
+
return doc;
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
const criteria = {};
|
|
197
|
+
for (const c of snap.criteria) {
|
|
198
|
+
const a = assoc[c.slug];
|
|
199
|
+
const understanding = await fetchText(`understanding/${VER_OF[c.addedIn]}/${c.slug}.html`);
|
|
200
|
+
const brief = understanding ? briefOf(understanding) : null;
|
|
201
|
+
|
|
202
|
+
// Selection: the first technique that carries a code sample, taking the technologies in
|
|
203
|
+
// a fixed order rather than purely positionally.
|
|
204
|
+
//
|
|
205
|
+
// Order matters because the W3C lists techniques by conformance strategy, not by how
|
|
206
|
+
// well they READ as an example. For 1.1.1 the first listed with a sample is ARIA6,
|
|
207
|
+
// whose own example is a navigation landmark — true to the technique, useless as an
|
|
208
|
+
// illustration of a text alternative. Preferring HTML picks H37 (`alt` on `img`)
|
|
209
|
+
// instead. This is one uniform rule applied to every criterion, not a per-criterion
|
|
210
|
+
// choice: within a technology the W3C's own order still decides.
|
|
211
|
+
const TECH_ORDER = ["html", "css", "aria", "client-side-script", "general", "text"];
|
|
212
|
+
const pick = async (ids) => {
|
|
213
|
+
const byTech = new Map();
|
|
214
|
+
for (const id of ids) {
|
|
215
|
+
const dir = techDir(id);
|
|
216
|
+
if (!dir) continue;
|
|
217
|
+
if (!byTech.has(dir)) byTech.set(dir, []);
|
|
218
|
+
byTech.get(dir).push(id);
|
|
219
|
+
}
|
|
220
|
+
const ordered = [...TECH_ORDER.flatMap((t) => byTech.get(t) ?? []), ...ids.filter((id) => !TECH_ORDER.includes(techDir(id)))];
|
|
221
|
+
for (const id of ordered) {
|
|
222
|
+
const doc = await techniqueDoc(id);
|
|
223
|
+
if (!doc) continue;
|
|
224
|
+
const ex = firstCodeExample(doc);
|
|
225
|
+
if (ex) return { id, title: techniqueTitle(doc), ...ex };
|
|
226
|
+
}
|
|
227
|
+
return null;
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
const good = a ? await pick(flattenTechniques(a.sufficient)) : null;
|
|
231
|
+
const bad = a ? await pick(flattenTechniques(a.failure)) : null;
|
|
232
|
+
|
|
233
|
+
criteria[c.sc] = {
|
|
234
|
+
slug: c.slug,
|
|
235
|
+
...(brief ? { brief } : {}),
|
|
236
|
+
...(good ? { good } : {}),
|
|
237
|
+
...(bad ? { bad } : {}),
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const out = {
|
|
242
|
+
source: "https://github.com/w3c/wcag",
|
|
243
|
+
understanding: "https://www.w3.org/WAI/WCAG22/Understanding/",
|
|
244
|
+
license: "W3C Document License (code samples: W3C Software License)",
|
|
245
|
+
fetchedAt: new Date().toISOString().slice(0, 10),
|
|
246
|
+
criteria,
|
|
247
|
+
};
|
|
248
|
+
mkdirSync(dirname(VENDOR), { recursive: true });
|
|
249
|
+
writeFileSync(VENDOR, JSON.stringify(out, null, 2) + "\n");
|
|
250
|
+
const withGood = Object.values(criteria).filter((x) => x.good).length;
|
|
251
|
+
const withBad = Object.values(criteria).filter((x) => x.bad).length;
|
|
252
|
+
const withBrief = Object.values(criteria).filter((x) => x.brief).length;
|
|
253
|
+
console.log(`build-guidance-wcag --refresh: ${Object.keys(criteria).length} SCs — ${withBrief} briefs, ${withGood} good samples, ${withBad} failure samples → ${VENDOR}`);
|
|
254
|
+
return out;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/** The dataset, from the vendored snapshot. Offline and deterministic. */
|
|
258
|
+
function build() {
|
|
259
|
+
if (!existsSync(VENDOR)) {
|
|
260
|
+
console.error(`build-guidance-wcag: missing ${VENDOR}. Run: node scripts/build-guidance-wcag.mjs --refresh`);
|
|
261
|
+
process.exit(1);
|
|
262
|
+
}
|
|
263
|
+
const snap = JSON.parse(readFileSync(VENDOR, "utf8"));
|
|
264
|
+
const core = JSON.parse(readFileSync(join(root, "src", "data", "wcag.json"), "utf8"));
|
|
265
|
+
const titleOf = new Map(core.criteria.map((c) => [c.sc, c]));
|
|
266
|
+
|
|
267
|
+
const entries = [];
|
|
268
|
+
for (const [sc, d] of Object.entries(snap.criteria)) {
|
|
269
|
+
const meta = titleOf.get(sc);
|
|
270
|
+
if (!meta) continue; // not in the shipped AA core
|
|
271
|
+
const summary = d.brief
|
|
272
|
+
? [d.brief.whattodo, d.brief.goal, d.brief.whyitsimportant].filter(Boolean).join(" ")
|
|
273
|
+
: undefined;
|
|
274
|
+
// A `bad`/`good` pair shares ONE `lang`, so the two halves may only be paired when they
|
|
275
|
+
// are the same technology. A CSS failure beside an HTML fix, labelled `html`, would be
|
|
276
|
+
// a snippet the reader cannot trust — so they are emitted separately instead.
|
|
277
|
+
const label = (t) => ({ en: `${t.kind}: ${t.title} (${t.id}).` });
|
|
278
|
+
const examples = [];
|
|
279
|
+
if (d.good && d.bad && d.good.lang === d.bad.lang) {
|
|
280
|
+
examples.push({
|
|
281
|
+
lang: d.good.lang,
|
|
282
|
+
bad: d.bad.code,
|
|
283
|
+
good: d.good.code,
|
|
284
|
+
note: { en: `Good: ${d.good.title} (${d.good.id}). Bad: ${d.bad.title} (${d.bad.id}).` },
|
|
285
|
+
});
|
|
286
|
+
} else {
|
|
287
|
+
if (d.good) examples.push({ lang: d.good.lang, good: d.good.code, note: label({ kind: "Good", ...d.good }) });
|
|
288
|
+
if (d.bad) examples.push({ lang: d.bad.lang, bad: d.bad.code, note: label({ kind: "Bad", ...d.bad }) });
|
|
289
|
+
}
|
|
290
|
+
entries.push({
|
|
291
|
+
id: `wcag-${d.slug}`,
|
|
292
|
+
criterionId: sc,
|
|
293
|
+
wcag: [sc],
|
|
294
|
+
title: { en: meta.title, ...(meta.titleFr ? { fr: meta.titleFr } : {}) },
|
|
295
|
+
...(summary ? { summary: { en: summary } } : { summary: {} }),
|
|
296
|
+
examples,
|
|
297
|
+
reference: `https://www.w3.org/WAI/WCAG22/Understanding/${d.slug}.html`,
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
entries.sort((a, b) => a.criterionId.localeCompare(b.criterionId, "en", { numeric: true }));
|
|
301
|
+
|
|
302
|
+
const out = {
|
|
303
|
+
pack: "wcag",
|
|
304
|
+
source: snap.understanding,
|
|
305
|
+
license: snap.license,
|
|
306
|
+
attribution:
|
|
307
|
+
"Derived from the W3C Techniques for WCAG 2.2 and the Understanding documents (https://github.com/w3c/wcag). WCAG 2.2 © W3C. " +
|
|
308
|
+
"Summaries are the working group's own 'In brief' text; code samples are the techniques' own examples. Generated by scripts/build-guidance-wcag.mjs — never hand-edited.",
|
|
309
|
+
entries,
|
|
310
|
+
};
|
|
311
|
+
return biomeFormat(JSON.stringify(out, null, 2) + "\n", "src/data/guidance/wcag.json");
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
async function main() {
|
|
315
|
+
if (process.argv.includes("--refresh")) await refresh();
|
|
316
|
+
const text = build();
|
|
317
|
+
if (process.argv.includes("--check")) {
|
|
318
|
+
const current = existsSync(OUT) ? readFileSync(OUT, "utf8") : "";
|
|
319
|
+
if (current !== text) {
|
|
320
|
+
console.error("build-guidance-wcag --check: src/data/guidance/wcag.json does not match the vendored W3C source. Re-run: node scripts/build-guidance-wcag.mjs");
|
|
321
|
+
process.exit(1);
|
|
322
|
+
}
|
|
323
|
+
console.log("build-guidance-wcag --check: src/data/guidance/wcag.json matches the vendored source.");
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
mkdirSync(dirname(OUT), { recursive: true });
|
|
327
|
+
writeFileSync(OUT, text);
|
|
328
|
+
const n = JSON.parse(text).entries.length;
|
|
329
|
+
console.log(`build-guidance-wcag: ${n} entries → src/data/guidance/wcag.json`);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
main().catch((e) => {
|
|
333
|
+
console.error(e);
|
|
334
|
+
process.exit(1);
|
|
335
|
+
});
|