ultra11y 4.0.0 → 4.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.
- 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/README.md +22 -0
- package/package.json +1 -1
- package/scripts/ultra11y.mjs +1639 -337
- package/skills/review-a11y/SKILL.md +1 -1
- package/skills/review-a11y/scripts/ultra11y.mjs +1639 -337
- package/skills/ultra11y/SKILL.md +1 -1
- package/skills/ultra11y/references/ci.md +87 -0
- package/skills/ultra11y/references/pages.md +57 -0
- package/skills/ultra11y/scripts/ultra11y.mjs +1639 -337
- package/src/annotate.ts +180 -15
- package/src/auditor.ts +0 -0
- package/src/cli.ts +158 -8
- package/src/dev.ts +32 -76
- package/src/html-emit.ts +310 -0
- package/src/html-report.ts +459 -0
- package/src/html.ts +261 -0
- package/src/mcp/handlers.ts +11 -1
- package/src/mcp/tools.ts +10 -1
- package/src/pages-report.ts +34 -21
- package/src/pages.ts +28 -4
- package/src/report.ts +71 -27
- package/src/types.ts +1 -1
package/scripts/ultra11y.mjs
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
-
import { realpathSync as realpathSync6, writeFileSync as
|
|
5
|
-
import { join as
|
|
4
|
+
import { realpathSync as realpathSync6, writeFileSync as writeFileSync19, mkdirSync as mkdirSync17, existsSync as existsSync32, readFileSync as readFileSync29, appendFileSync, copyFileSync as copyFileSync4 } from "fs";
|
|
5
|
+
import { join as join47, relative as relative5, sep as sep7, dirname as dirname14 } from "path";
|
|
6
6
|
import { fileURLToPath as fileURLToPath5, pathToFileURL as pathToFileURL3 } from "url";
|
|
7
7
|
|
|
8
8
|
// src/types.ts
|
|
9
|
-
var VERSION = "4.
|
|
9
|
+
var VERSION = "4.1.0";
|
|
10
10
|
var SCHEMA_VERSION = 2;
|
|
11
11
|
|
|
12
12
|
// src/audit.ts
|
|
@@ -2435,9 +2435,9 @@ var Tokenizer = class {
|
|
|
2435
2435
|
this.running = true;
|
|
2436
2436
|
this.offset = 0;
|
|
2437
2437
|
}
|
|
2438
|
-
write(
|
|
2438
|
+
write(chunk2) {
|
|
2439
2439
|
this.offset += this.buffer.length;
|
|
2440
|
-
this.buffer =
|
|
2440
|
+
this.buffer = chunk2;
|
|
2441
2441
|
this.parse();
|
|
2442
2442
|
}
|
|
2443
2443
|
end() {
|
|
@@ -3725,14 +3725,14 @@ var Parser = class {
|
|
|
3725
3725
|
* Parses a chunk of data and calls the corresponding callbacks.
|
|
3726
3726
|
* @param chunk Chunk to parse.
|
|
3727
3727
|
*/
|
|
3728
|
-
write(
|
|
3728
|
+
write(chunk2) {
|
|
3729
3729
|
if (this.ended) {
|
|
3730
3730
|
this.cbs.onerror?.(new Error(".write() after done!"));
|
|
3731
3731
|
return;
|
|
3732
3732
|
}
|
|
3733
|
-
this.buffers.push(
|
|
3733
|
+
this.buffers.push(chunk2);
|
|
3734
3734
|
if (this.tokenizer.running) {
|
|
3735
|
-
this.tokenizer.write(
|
|
3735
|
+
this.tokenizer.write(chunk2);
|
|
3736
3736
|
this.writeIndex++;
|
|
3737
3737
|
}
|
|
3738
3738
|
}
|
|
@@ -3740,13 +3740,13 @@ var Parser = class {
|
|
|
3740
3740
|
* Parses the end of the buffer and clears the stack, calls onend.
|
|
3741
3741
|
* @param chunk Optional final chunk to parse.
|
|
3742
3742
|
*/
|
|
3743
|
-
end(
|
|
3743
|
+
end(chunk2) {
|
|
3744
3744
|
if (this.ended) {
|
|
3745
3745
|
this.cbs.onerror?.(new Error(".end() after done!"));
|
|
3746
3746
|
return;
|
|
3747
3747
|
}
|
|
3748
|
-
if (
|
|
3749
|
-
this.write(
|
|
3748
|
+
if (chunk2)
|
|
3749
|
+
this.write(chunk2);
|
|
3750
3750
|
this.ended = true;
|
|
3751
3751
|
this.tokenizer.end();
|
|
3752
3752
|
}
|
|
@@ -18820,7 +18820,7 @@ function repoRelative(file, baseDir) {
|
|
|
18820
18820
|
}
|
|
18821
18821
|
async function readStdin() {
|
|
18822
18822
|
const chunks = [];
|
|
18823
|
-
for await (const
|
|
18823
|
+
for await (const chunk2 of process.stdin) chunks.push(chunk2);
|
|
18824
18824
|
return Buffer.concat(chunks).toString("utf8");
|
|
18825
18825
|
}
|
|
18826
18826
|
|
|
@@ -19448,14 +19448,14 @@ function isGitWorktree(dir) {
|
|
|
19448
19448
|
function resolveBaseRef(dir, base) {
|
|
19449
19449
|
const verify = (ref) => sh("git", [...gitArgs(dir), "rev-parse", "--verify", "--quiet", `${ref}^{commit}`]).ok;
|
|
19450
19450
|
const mergeBase = (ref) => {
|
|
19451
|
-
const
|
|
19452
|
-
return
|
|
19451
|
+
const mb2 = sh("git", [...gitArgs(dir), "merge-base", ref, "HEAD"]);
|
|
19452
|
+
return mb2.ok ? mb2.stdout.trim() : void 0;
|
|
19453
19453
|
};
|
|
19454
19454
|
if (base) {
|
|
19455
19455
|
if (!verify(base)) return { error: `base ref "${base}" not found (tried git rev-parse --verify)` };
|
|
19456
|
-
const
|
|
19457
|
-
if (!
|
|
19458
|
-
return { ref: base, mergeBase:
|
|
19456
|
+
const mb2 = mergeBase(base);
|
|
19457
|
+
if (!mb2) return { error: `no merge-base between "${base}" and HEAD` };
|
|
19458
|
+
return { ref: base, mergeBase: mb2 };
|
|
19459
19459
|
}
|
|
19460
19460
|
const originHead = sh("git", [...gitArgs(dir), "symbolic-ref", "--quiet", "refs/remotes/origin/HEAD"]);
|
|
19461
19461
|
const candidates2 = [
|
|
@@ -19467,8 +19467,8 @@ function resolveBaseRef(dir, base) {
|
|
|
19467
19467
|
];
|
|
19468
19468
|
for (const c2 of candidates2) {
|
|
19469
19469
|
if (!verify(c2)) continue;
|
|
19470
|
-
const
|
|
19471
|
-
if (
|
|
19470
|
+
const mb2 = mergeBase(c2);
|
|
19471
|
+
if (mb2) return { ref: c2, mergeBase: mb2 };
|
|
19472
19472
|
}
|
|
19473
19473
|
const head = sh("git", [...gitArgs(dir), "rev-parse", "HEAD"]);
|
|
19474
19474
|
if (!head.ok) return { error: "cannot resolve HEAD \u2014 empty repository?" };
|
|
@@ -34007,15 +34007,15 @@ function deltaFor(repo, graph, symbols, opts = {}) {
|
|
|
34007
34007
|
return computeDelta(graph, symbols, { files, hunks: diffHunks(repo, spec), base, notes }, opts.depth ?? DEFAULT_DELTA_DEPTH);
|
|
34008
34008
|
}
|
|
34009
34009
|
function formatDeltaPanel(res) {
|
|
34010
|
-
const
|
|
34010
|
+
const mb2 = res.base.mergeBase.slice(0, 7);
|
|
34011
34011
|
const vs = `${res.base.staged ? "staged vs " : ""}${res.base.ref}`;
|
|
34012
34012
|
if (!res.changes.length && !res.unindexed.length) {
|
|
34013
|
-
return `codeindex: no changes vs ${vs} (merge-base ${
|
|
34013
|
+
return `codeindex: no changes vs ${vs} (merge-base ${mb2})
|
|
34014
34014
|
`;
|
|
34015
34015
|
}
|
|
34016
34016
|
const changedCount = res.changes.length + res.unindexed.length;
|
|
34017
34017
|
const lines = [
|
|
34018
|
-
`codeindex: delta vs ${vs} (merge-base ${
|
|
34018
|
+
`codeindex: delta vs ${vs} (merge-base ${mb2}) \u2014 ${changedCount} changed file(s), ${res.modules.length} module(s)${res.indexCommit ? `, index @ ${res.indexCommit}` : ""}`
|
|
34019
34019
|
];
|
|
34020
34020
|
for (const n of res.notes) lines.push(` note: ${n}`);
|
|
34021
34021
|
for (const m of res.modules) {
|
|
@@ -35329,6 +35329,16 @@ function readSnapshots(root) {
|
|
|
35329
35329
|
}
|
|
35330
35330
|
return out2;
|
|
35331
35331
|
}
|
|
35332
|
+
function alignedBoxes(dom, boxes, doc) {
|
|
35333
|
+
const parsed = doc ?? parseHtml(dom, "snapshot");
|
|
35334
|
+
const out2 = /* @__PURE__ */ new Map();
|
|
35335
|
+
for (const e of boxes.entries) {
|
|
35336
|
+
const el = parsed.elements[e.i];
|
|
35337
|
+
if (!el || el.tag !== e.tag) return null;
|
|
35338
|
+
out2.set(e.i, e);
|
|
35339
|
+
}
|
|
35340
|
+
return out2;
|
|
35341
|
+
}
|
|
35332
35342
|
function isSnapshotDom(file) {
|
|
35333
35343
|
const posix3 = file.split("\\").join("/");
|
|
35334
35344
|
return posix3.endsWith("/dom.html") && posix3.includes(`${PAGES_DIR}/`);
|
|
@@ -39288,6 +39298,112 @@ function dominantBackground(img, rect) {
|
|
|
39288
39298
|
if (!best || best.n / total < DOMINANCE) return null;
|
|
39289
39299
|
return { r: Math.round(best.r / best.n), g: Math.round(best.g / best.n), b: Math.round(best.b / best.n), a: 1 };
|
|
39290
39300
|
}
|
|
39301
|
+
var CRC_TABLE = /* @__PURE__ */ (() => {
|
|
39302
|
+
const t2 = new Uint32Array(256);
|
|
39303
|
+
for (let n = 0; n < 256; n++) {
|
|
39304
|
+
let c2 = n;
|
|
39305
|
+
for (let k = 0; k < 8; k++) c2 = c2 & 1 ? 3988292384 ^ c2 >>> 1 : c2 >>> 1;
|
|
39306
|
+
t2[n] = c2 >>> 0;
|
|
39307
|
+
}
|
|
39308
|
+
return t2;
|
|
39309
|
+
})();
|
|
39310
|
+
function crc32(buf) {
|
|
39311
|
+
let c2 = 4294967295;
|
|
39312
|
+
for (let i2 = 0; i2 < buf.length; i2++) c2 = CRC_TABLE[(c2 ^ buf[i2]) & 255] ^ c2 >>> 8;
|
|
39313
|
+
return (c2 ^ 4294967295) >>> 0;
|
|
39314
|
+
}
|
|
39315
|
+
function chunk(type, data2) {
|
|
39316
|
+
const len = Buffer.alloc(4);
|
|
39317
|
+
len.writeUInt32BE(data2.length);
|
|
39318
|
+
const body2 = Buffer.concat([Buffer.from(type, "latin1"), data2]);
|
|
39319
|
+
const crc = Buffer.alloc(4);
|
|
39320
|
+
crc.writeUInt32BE(crc32(body2));
|
|
39321
|
+
return Buffer.concat([len, body2, crc]);
|
|
39322
|
+
}
|
|
39323
|
+
function encodePng(r) {
|
|
39324
|
+
const expected = r.width * r.height * 4;
|
|
39325
|
+
if (r.width <= 0 || r.height <= 0) throw new Error(`encodePng: refusing a ${r.width}\xD7${r.height} raster`);
|
|
39326
|
+
if (r.data.length !== expected) {
|
|
39327
|
+
throw new Error(`encodePng: ${r.width}\xD7${r.height} needs ${expected} bytes of RGBA, got ${r.data.length}`);
|
|
39328
|
+
}
|
|
39329
|
+
const stride = r.width * 4;
|
|
39330
|
+
const raw = Buffer.alloc(r.height * (stride + 1));
|
|
39331
|
+
for (let y = 0; y < r.height; y++) {
|
|
39332
|
+
raw[y * (stride + 1)] = 0;
|
|
39333
|
+
r.data.copy(raw, y * (stride + 1) + 1, y * stride, (y + 1) * stride);
|
|
39334
|
+
}
|
|
39335
|
+
const ihdr = Buffer.alloc(13);
|
|
39336
|
+
ihdr.writeUInt32BE(r.width, 0);
|
|
39337
|
+
ihdr.writeUInt32BE(r.height, 4);
|
|
39338
|
+
ihdr[8] = 8;
|
|
39339
|
+
ihdr[9] = 6;
|
|
39340
|
+
return Buffer.concat([Buffer.from(SIGNATURE), chunk("IHDR", ihdr), chunk("IDAT", deflateSync(raw, { level: 9 })), chunk("IEND", Buffer.alloc(0))]);
|
|
39341
|
+
}
|
|
39342
|
+
function cropImage(img, rect) {
|
|
39343
|
+
const r = clamp(img, rect);
|
|
39344
|
+
if (!r) return null;
|
|
39345
|
+
const data2 = Buffer.alloc(r.w * r.h * 4);
|
|
39346
|
+
let i2 = 0;
|
|
39347
|
+
for (let y = r.y; y < r.y + r.h; y++) {
|
|
39348
|
+
for (let x = r.x; x < r.x + r.w; x++) {
|
|
39349
|
+
const p = img.at(x, y);
|
|
39350
|
+
data2[i2] = p?.r ?? 0;
|
|
39351
|
+
data2[i2 + 1] = p?.g ?? 0;
|
|
39352
|
+
data2[i2 + 2] = p?.b ?? 0;
|
|
39353
|
+
data2[i2 + 3] = Math.round((p?.a ?? 1) * 255);
|
|
39354
|
+
i2 += 4;
|
|
39355
|
+
}
|
|
39356
|
+
}
|
|
39357
|
+
return { raster: { width: r.w, height: r.h, data: data2 }, rect: r };
|
|
39358
|
+
}
|
|
39359
|
+
function blend(raster, x, y, c2) {
|
|
39360
|
+
if (x < 0 || y < 0 || x >= raster.width || y >= raster.height) return;
|
|
39361
|
+
const p = (y * raster.width + x) * 4;
|
|
39362
|
+
const a = Math.min(1, Math.max(0, c2.a));
|
|
39363
|
+
if (a >= 1) {
|
|
39364
|
+
raster.data[p] = c2.r;
|
|
39365
|
+
raster.data[p + 1] = c2.g;
|
|
39366
|
+
raster.data[p + 2] = c2.b;
|
|
39367
|
+
raster.data[p + 3] = 255;
|
|
39368
|
+
return;
|
|
39369
|
+
}
|
|
39370
|
+
const inv = 1 - a;
|
|
39371
|
+
raster.data[p] = Math.round(c2.r * a + raster.data[p] * inv);
|
|
39372
|
+
raster.data[p + 1] = Math.round(c2.g * a + raster.data[p + 1] * inv);
|
|
39373
|
+
raster.data[p + 2] = Math.round(c2.b * a + raster.data[p + 2] * inv);
|
|
39374
|
+
raster.data[p + 3] = Math.max(raster.data[p + 3], Math.round(a * 255));
|
|
39375
|
+
}
|
|
39376
|
+
function fillRect(raster, rect, color) {
|
|
39377
|
+
const x0 = Math.max(0, Math.round(rect.x));
|
|
39378
|
+
const y0 = Math.max(0, Math.round(rect.y));
|
|
39379
|
+
const x1 = Math.min(raster.width, Math.round(rect.x + rect.w));
|
|
39380
|
+
const y1 = Math.min(raster.height, Math.round(rect.y + rect.h));
|
|
39381
|
+
for (let y = y0; y < y1; y++) for (let x = x0; x < x1; x++) blend(raster, x, y, color);
|
|
39382
|
+
}
|
|
39383
|
+
function strokeRect(raster, rect, color, width, dash) {
|
|
39384
|
+
const t2 = Math.max(1, Math.round(width));
|
|
39385
|
+
const x0 = Math.round(rect.x);
|
|
39386
|
+
const y0 = Math.round(rect.y);
|
|
39387
|
+
const x1 = Math.round(rect.x + rect.w);
|
|
39388
|
+
const y1 = Math.round(rect.y + rect.h);
|
|
39389
|
+
if (x1 <= x0 || y1 <= y0) return;
|
|
39390
|
+
const on = (coord) => {
|
|
39391
|
+
if (!dash || dash <= 1) return true;
|
|
39392
|
+
const half = dash / 2;
|
|
39393
|
+
return Math.floor(coord / half) % 2 === 0;
|
|
39394
|
+
};
|
|
39395
|
+
for (let y = y0; y < y1; y++) {
|
|
39396
|
+
for (let x = x0; x < x1; x++) {
|
|
39397
|
+
const top = y < y0 + t2;
|
|
39398
|
+
const bottom = y >= y1 - t2;
|
|
39399
|
+
const left = x < x0 + t2;
|
|
39400
|
+
const right = x >= x1 - t2;
|
|
39401
|
+
if (!top && !bottom && !left && !right) continue;
|
|
39402
|
+
if (!on(top || bottom ? x : y)) continue;
|
|
39403
|
+
blend(raster, x, y, color);
|
|
39404
|
+
}
|
|
39405
|
+
}
|
|
39406
|
+
}
|
|
39291
39407
|
|
|
39292
39408
|
// src/rules/rendered.ts
|
|
39293
39409
|
var SKIP_TAGS2 = /* @__PURE__ */ new Set(["script", "style", "head", "title", "meta", "noscript", "link", "html", "br", "wbr"]);
|
|
@@ -48677,8 +48793,10 @@ function groupOccurrences(findings, collapse2) {
|
|
|
48677
48793
|
}
|
|
48678
48794
|
return [...groups.values()];
|
|
48679
48795
|
}
|
|
48680
|
-
function renderOccurrenceDetails(out2, f, lang, s, indent) {
|
|
48796
|
+
function renderOccurrenceDetails(out2, f, lang, s, indent, cropFor) {
|
|
48681
48797
|
if (f.secondary?.note) out2.push(`${indent} - \u21B3 ${f.secondary.note}`);
|
|
48798
|
+
const crop = cropFor?.(f);
|
|
48799
|
+
if (crop) out2.push(`${indent} - `);
|
|
48682
48800
|
if (f.related) out2.push(indent + relatedLine(f.related, lang, { selector: true }));
|
|
48683
48801
|
if (f.origin) {
|
|
48684
48802
|
const comp = f.origin.component ?? f.origin.sourceFile ?? f.file;
|
|
@@ -48707,60 +48825,75 @@ function relatedLine(related, lang, opts) {
|
|
|
48707
48825
|
function renderAuditorUnit(unit, standard, lang, opts = {}) {
|
|
48708
48826
|
const s = L[lang];
|
|
48709
48827
|
if (unit.advisory) return renderAdvisoryUnit(unit, standard, lang, opts);
|
|
48710
|
-
const
|
|
48711
|
-
const technical = opts.technical ?? true;
|
|
48828
|
+
const m = auditorUnitModel(unit, standard, lang, opts);
|
|
48712
48829
|
const out2 = [];
|
|
48713
|
-
if (opts.heading) out2.push(`${opts.heading} ${
|
|
48714
|
-
out2.push(`> ${
|
|
48830
|
+
if (opts.heading) out2.push(`${opts.heading} ${m.icon} ${m.label}`, "");
|
|
48831
|
+
out2.push(`> ${m.normativeNote}`, "");
|
|
48832
|
+
for (const f of m.fields) out2.push(`**${f.label}** : ${f.value}`);
|
|
48833
|
+
out2.push("");
|
|
48834
|
+
out2.push(`**${s.finding} (${m.conformanceTerms.nonConformant})** : ${m.occurrences} ${s.occ} \u2014 ${m.messages.join(" ; ")}`);
|
|
48835
|
+
if (m.fixes.length) out2.push(`**${s.expected} (${m.conformanceTerms.conformant})** : ${m.fixes.join(" ; ")}`);
|
|
48836
|
+
out2.push(`**${s.verification}** : ${s.verify}`, "");
|
|
48837
|
+
for (const group of m.groups) {
|
|
48838
|
+
if (group.count > 1) out2.push(`- **\`${group.lead.selectorHint}\`** \u2014 ${resolveMessage(group.lead, lang)} \xB7 \xD7${group.count}`);
|
|
48839
|
+
for (const f of group.findings) {
|
|
48840
|
+
const indent = group.count > 1 ? " " : "";
|
|
48841
|
+
out2.push(indent + occurrenceLine(f, lang, { marker: "checkbox" }));
|
|
48842
|
+
renderOccurrenceDetails(out2, f, lang, s, indent, opts.cropFor);
|
|
48843
|
+
}
|
|
48844
|
+
}
|
|
48845
|
+
out2.push("");
|
|
48846
|
+
if (m.advisories.length) {
|
|
48847
|
+
out2.push(`_${s.associatedRec}_`, "");
|
|
48848
|
+
for (const f of m.advisories) out2.push(occurrenceLine(f, lang, { marker: "advisory" }));
|
|
48849
|
+
out2.push("");
|
|
48850
|
+
}
|
|
48851
|
+
if (opts.technical ?? true) {
|
|
48852
|
+
out2.push(...renderTechnicalSection({ ...unit, findings: m.normative }, unit, standard, lang, opts));
|
|
48853
|
+
out2.push(...renderReproductionContext(m.normative, lang));
|
|
48854
|
+
}
|
|
48855
|
+
return out2;
|
|
48856
|
+
}
|
|
48857
|
+
function auditorUnitModel(unit, standard, lang, opts = {}) {
|
|
48858
|
+
const s = L[lang];
|
|
48859
|
+
const v = vocabularyFor(standard, lang);
|
|
48860
|
+
const fields = [];
|
|
48715
48861
|
if (isCore(standard)) {
|
|
48716
48862
|
const sc = getSC(unit.criteriaId);
|
|
48717
48863
|
if (sc) {
|
|
48718
48864
|
const pr = `${sc.principle} ${principleTitle(sc.principle, lang) ?? ""}`.trim();
|
|
48719
48865
|
const gl = `${sc.guideline} ${guidelineTitle(sc.guideline, lang) ?? ""}`.trim();
|
|
48720
|
-
|
|
48866
|
+
fields.push({ label: v.theme, value: [pr, gl].filter(Boolean).join(" \xB7 ") });
|
|
48721
48867
|
}
|
|
48722
|
-
|
|
48868
|
+
fields.push({ label: v.criterion, value: `${unit.criteriaId}${sc ? ` \u2014 ${unit.title}` : ""}` });
|
|
48723
48869
|
const techs = techniques(unit.criteriaId);
|
|
48724
|
-
if (techs.length)
|
|
48725
|
-
|
|
48870
|
+
if (techs.length) fields.push({ label: v.test, value: techs.join(", ") });
|
|
48871
|
+
fields.push({ label: "WCAG", value: `${unit.criteriaId}${sc ? ` (${sc.level})` : ""}` });
|
|
48726
48872
|
} else {
|
|
48727
48873
|
const pack = loadPack(standard);
|
|
48728
48874
|
const pc = pack.criteria.find((c2) => c2.id === unit.criteriaId);
|
|
48729
|
-
if (pc)
|
|
48730
|
-
|
|
48875
|
+
if (pc) fields.push({ label: v.theme, value: `${pc.theme}. ${themeName(pack, pc.theme, lang) ?? ""}`.trimEnd() });
|
|
48876
|
+
fields.push({ label: v.criterion, value: `${unit.criteriaId} \u2014 ${unit.title}` });
|
|
48731
48877
|
const testNums = packTestIds(pack, unit.criteriaId);
|
|
48732
|
-
if (testNums.length)
|
|
48733
|
-
if (unit.refs.length)
|
|
48878
|
+
if (testNums.length) fields.push({ label: `${v.test}(s)`, value: testNums.join(" \xB7 ") });
|
|
48879
|
+
if (unit.refs.length) fields.push({ label: "WCAG", value: unit.refs.map((sc) => `${sc}${scLevel(sc)}`).join(" \xB7 ") });
|
|
48734
48880
|
}
|
|
48735
|
-
|
|
48881
|
+
fields.push({ label: s.priority, value: `${ICON[unit.severity]} ${SEV_LABEL[lang][unit.severity]}` });
|
|
48736
48882
|
const normative = unit.findings.filter((f) => !f.advisory);
|
|
48737
|
-
|
|
48738
|
-
|
|
48739
|
-
|
|
48740
|
-
|
|
48741
|
-
|
|
48742
|
-
|
|
48743
|
-
|
|
48744
|
-
|
|
48745
|
-
|
|
48746
|
-
|
|
48747
|
-
|
|
48748
|
-
|
|
48749
|
-
|
|
48750
|
-
|
|
48751
|
-
}
|
|
48752
|
-
}
|
|
48753
|
-
out2.push("");
|
|
48754
|
-
if (advisories.length) {
|
|
48755
|
-
out2.push(`_${s.associatedRec}_`, "");
|
|
48756
|
-
for (const f of advisories) out2.push(occurrenceLine(f, lang, { marker: "advisory" }));
|
|
48757
|
-
out2.push("");
|
|
48758
|
-
}
|
|
48759
|
-
if (technical) {
|
|
48760
|
-
out2.push(...renderTechnicalSection(ncView, unit, standard, lang, opts));
|
|
48761
|
-
out2.push(...renderReproductionContext(normative, lang));
|
|
48762
|
-
}
|
|
48763
|
-
return out2;
|
|
48883
|
+
return {
|
|
48884
|
+
severity: unit.severity,
|
|
48885
|
+
icon: ICON[unit.severity],
|
|
48886
|
+
label: unit.label,
|
|
48887
|
+
normativeNote: v.normativeNote ?? `${s.lead} \u2014 ${standardLabel(standard)}. ${s.tail}`,
|
|
48888
|
+
fields,
|
|
48889
|
+
conformanceTerms: { conformant: v.conformant, nonConformant: v.nonConformant },
|
|
48890
|
+
normative,
|
|
48891
|
+
advisories: unit.findings.filter((f) => f.advisory),
|
|
48892
|
+
occurrences: normative.length,
|
|
48893
|
+
messages: uniq(normative.map((f) => resolveMessage(f, lang))),
|
|
48894
|
+
fixes: uniq(normative.map((f) => resolveRemediation(f, lang))),
|
|
48895
|
+
groups: groupOccurrences(normative, opts.collapse === true).map((g) => ({ lead: g[0], count: g.length, findings: g }))
|
|
48896
|
+
};
|
|
48764
48897
|
}
|
|
48765
48898
|
function renderTechnicalSection(ncView, unit, standard, lang, opts) {
|
|
48766
48899
|
const s = L[lang];
|
|
@@ -49053,14 +49186,14 @@ function unitBlock(unit, lang, heading, standard) {
|
|
|
49053
49186
|
out2.push("");
|
|
49054
49187
|
return out2;
|
|
49055
49188
|
}
|
|
49056
|
-
function header(r, lang, title2, note = L2[lang].note,
|
|
49189
|
+
function header(r, lang, title2, note = L2[lang].note, ratePct) {
|
|
49057
49190
|
const s = L2[lang];
|
|
49058
49191
|
return [
|
|
49059
49192
|
`# ${title2}`,
|
|
49060
49193
|
"",
|
|
49061
49194
|
`- **${s.date}** : ${r.date}`,
|
|
49062
49195
|
`- **${s.scope}** : ${r.scope.files} ${s.files} \u2014 ${r.scope.inputs.join(", ")}`,
|
|
49063
|
-
`- **${s.rate}** : ${
|
|
49196
|
+
`- **${s.rate}** : ${ratePct ?? r.conformancePct}%`,
|
|
49064
49197
|
"",
|
|
49065
49198
|
`> ${note}`,
|
|
49066
49199
|
""
|
|
@@ -49069,8 +49202,8 @@ function header(r, lang, title2, note = L2[lang].note, ratePct2) {
|
|
|
49069
49202
|
function renderBacklog(r, lang = "en", standard = "wcag") {
|
|
49070
49203
|
const s = L2[lang];
|
|
49071
49204
|
const units = prdUnits(r, standard, lang);
|
|
49072
|
-
const
|
|
49073
|
-
const out2 = header(r, lang, s.title(standardLabel(standard)), void 0,
|
|
49205
|
+
const ratePct = isCore(standard) ? void 0 : packConformancePct(derivePackResults(r, standard));
|
|
49206
|
+
const out2 = header(r, lang, s.title(standardLabel(standard)), void 0, ratePct);
|
|
49074
49207
|
if (!units.length) {
|
|
49075
49208
|
out2.push(s.none, "");
|
|
49076
49209
|
out2.push(...toRuleOnSection(r, standard, lang));
|
|
@@ -49086,9 +49219,9 @@ function renderBacklog(r, lang = "en", standard = "wcag") {
|
|
|
49086
49219
|
}
|
|
49087
49220
|
function renderPerCriterion(r, lang = "en", standard = "wcag") {
|
|
49088
49221
|
const s = L2[lang];
|
|
49089
|
-
const
|
|
49222
|
+
const ratePct = isCore(standard) ? void 0 : packConformancePct(derivePackResults(r, standard));
|
|
49090
49223
|
return prdUnits(r, standard, lang).map((u) => {
|
|
49091
|
-
const out2 = header(r, lang, s.prdTitle(u.label), void 0,
|
|
49224
|
+
const out2 = header(r, lang, s.prdTitle(u.label), void 0, ratePct);
|
|
49092
49225
|
out2.push(...unitBlock(u, lang, "##", standard));
|
|
49093
49226
|
return { name: `prd-${u.criteriaId}-${r.date}.md`, content: out2.join("\n") };
|
|
49094
49227
|
});
|
|
@@ -49146,8 +49279,8 @@ function toRuleOnSection(r, standard, lang) {
|
|
|
49146
49279
|
function renderPrdDoc(r, lang = "en", standard = "wcag") {
|
|
49147
49280
|
const s = L2[lang];
|
|
49148
49281
|
const units = prdUnits(r, standard, lang);
|
|
49149
|
-
const
|
|
49150
|
-
const out2 = header(r, lang, s.title(standardLabel(standard)), s.docNote,
|
|
49282
|
+
const ratePct = isCore(standard) ? void 0 : packConformancePct(derivePackResults(r, standard));
|
|
49283
|
+
const out2 = header(r, lang, s.title(standardLabel(standard)), s.docNote, ratePct);
|
|
49151
49284
|
if (!units.length) {
|
|
49152
49285
|
out2.push(s.none, "");
|
|
49153
49286
|
return out2.join("\n");
|
|
@@ -49327,7 +49460,8 @@ var L3 = {
|
|
|
49327
49460
|
snapshot: "instantan\xE9",
|
|
49328
49461
|
source: "source",
|
|
49329
49462
|
notAudited: "non audit\xE9",
|
|
49330
|
-
notAuditedNote: "Une page marqu\xE9e \xAB non audit\xE9 \xBB a bien un instantan\xE9, mais CET audit ne l'a pas lu (il ne portait que sur les sources). L'absence de constat n'y vaut donc PAS conformit\xE9 \u2014 relancez l'audit en incluant `.ultra11y/pages`."
|
|
49463
|
+
notAuditedNote: "Une page marqu\xE9e \xAB non audit\xE9 \xBB a bien un instantan\xE9, mais CET audit ne l'a pas lu (il ne portait que sur les sources). L'absence de constat n'y vaut donc PAS conformit\xE9 \u2014 relancez l'audit en incluant `.ultra11y/pages`.",
|
|
49464
|
+
agentMark: "`C*` : conformit\xE9 tranch\xE9e par l'agent IA \xE0 partir des \xE9vidences cit\xE9es (gat\xE9), et non prouv\xE9e par le moteur d\xE9terministe."
|
|
49331
49465
|
},
|
|
49332
49466
|
en: {
|
|
49333
49467
|
title: "Per-page grid",
|
|
@@ -49341,12 +49475,16 @@ var L3 = {
|
|
|
49341
49475
|
snapshot: "snapshot",
|
|
49342
49476
|
source: "source",
|
|
49343
49477
|
notAudited: "not audited",
|
|
49344
|
-
notAuditedNote: 'A page marked "not audited" does have a snapshot, but THIS audit never read it (it covered sources only). Absence of a finding there does NOT mean conforming \u2014 re-run the audit with `.ultra11y/pages` in scope.'
|
|
49478
|
+
notAuditedNote: 'A page marked "not audited" does have a snapshot, but THIS audit never read it (it covered sources only). Absence of a finding there does NOT mean conforming \u2014 re-run the audit with `.ultra11y/pages` in scope.',
|
|
49479
|
+
agentMark: "`C*`: conformity ruled by the AI agent from the evidence it cited (gated), not proven by the deterministic engine."
|
|
49345
49480
|
}
|
|
49346
49481
|
};
|
|
49347
49482
|
function formatRate(rate, decided, total) {
|
|
49348
49483
|
return `${rate === null ? "\u2014" : `${rate} %`} (${decided}/${total})`;
|
|
49349
49484
|
}
|
|
49485
|
+
function agentMarkNote(lang) {
|
|
49486
|
+
return L3[lang].agentMark;
|
|
49487
|
+
}
|
|
49350
49488
|
function pageBasisWarning(basis, lang) {
|
|
49351
49489
|
if (basis === "snapshot") return void 0;
|
|
49352
49490
|
return basis === "not-audited" ? L3[lang].notAuditedNote : L3[lang].basisNote;
|
|
@@ -49366,7 +49504,7 @@ function pageView(result, page) {
|
|
|
49366
49504
|
...result.packFindings ? { packFindings: result.packFindings.filter((f) => f.page === page.id) } : {}
|
|
49367
49505
|
};
|
|
49368
49506
|
}
|
|
49369
|
-
function
|
|
49507
|
+
function pageGridModel(result, derived, standard, lang) {
|
|
49370
49508
|
const status = /* @__PURE__ */ new Map();
|
|
49371
49509
|
const put = (rowId, pageId, s) => {
|
|
49372
49510
|
const m = status.get(rowId) ?? /* @__PURE__ */ new Map();
|
|
@@ -49398,7 +49536,7 @@ function renderPageGrid(result, pages, standard = CORE2, lang = "en") {
|
|
|
49398
49536
|
out2.push(`| ${head.join(" | ")} |`, `| ${head.map(() => "---").join(" | ")} |`);
|
|
49399
49537
|
out2.push(`| **${s.rate}** | ${derived.map((p) => `**${formatRate(p.conformancePct, p.decided, p.total)}**`).join(" | ")} |`);
|
|
49400
49538
|
out2.push(`| _${s.snapshot}?_ | ${derived.map((p) => `_${basisLabel(p.basis, lang)}_`).join(" | ")} |`);
|
|
49401
|
-
const { rows, status } =
|
|
49539
|
+
const { rows, status } = pageGridModel(result, derived, standard, lang);
|
|
49402
49540
|
let group = "";
|
|
49403
49541
|
for (const row of rows) {
|
|
49404
49542
|
if (row.group !== group) {
|
|
@@ -49550,6 +49688,29 @@ function partialAuditBanner(lang, untested = NEEDS_RENDERING.map((c2) => c2.sc))
|
|
|
49550
49688
|
const labels = NEEDS_RENDERING.filter((c2) => set.has(c2.sc)).map((c2) => c2.label[lang]);
|
|
49551
49689
|
return L4[lang].partialAudit(labels.join(", "));
|
|
49552
49690
|
}
|
|
49691
|
+
function tallyRows(rows) {
|
|
49692
|
+
return {
|
|
49693
|
+
c: rows.filter((x) => x.status === "C").length,
|
|
49694
|
+
nc: rows.filter((x) => x.status === "NC").length,
|
|
49695
|
+
na: rows.filter((x) => x.status === "NA").length,
|
|
49696
|
+
manual: rows.filter((x) => x.status === "manual").length
|
|
49697
|
+
};
|
|
49698
|
+
}
|
|
49699
|
+
function reportTotals(groups) {
|
|
49700
|
+
const tot = { c: 0, nc: 0, na: 0, manual: 0 };
|
|
49701
|
+
for (const g of groups) {
|
|
49702
|
+
const t2 = tallyRows(g.rows);
|
|
49703
|
+
tot.c += t2.c;
|
|
49704
|
+
tot.nc += t2.nc;
|
|
49705
|
+
tot.na += t2.na;
|
|
49706
|
+
tot.manual += t2.manual;
|
|
49707
|
+
}
|
|
49708
|
+
return tot;
|
|
49709
|
+
}
|
|
49710
|
+
function reportCoverage(groups) {
|
|
49711
|
+
const t2 = reportTotals(groups);
|
|
49712
|
+
return { decided: t2.c + t2.nc, total: t2.c + t2.nc + t2.na + t2.manual };
|
|
49713
|
+
}
|
|
49553
49714
|
function render(r, lang, opts) {
|
|
49554
49715
|
const s = L4[lang];
|
|
49555
49716
|
const out2 = [];
|
|
@@ -49572,18 +49733,11 @@ function render(r, lang, opts) {
|
|
|
49572
49733
|
out2.push(`## ${s.synthTitle(opts.groupHead)}`, "");
|
|
49573
49734
|
out2.push(`| ${th.join(" | ")} |`);
|
|
49574
49735
|
out2.push(`|${"---|".repeat(th.length)}`);
|
|
49575
|
-
const tot = { c: 0, nc: 0, na: 0, manual: 0 };
|
|
49576
49736
|
for (const g of opts.groups) {
|
|
49577
|
-
const
|
|
49578
|
-
|
|
49579
|
-
const na2 = g.rows.filter((x) => x.status === "NA").length;
|
|
49580
|
-
const manual2 = g.rows.filter((x) => x.status === "manual").length;
|
|
49581
|
-
out2.push(`| ${g.key} ${g.title} | ${c2} | ${nc} | ${na2} | ${manual2} |`);
|
|
49582
|
-
tot.c += c2;
|
|
49583
|
-
tot.nc += nc;
|
|
49584
|
-
tot.na += na2;
|
|
49585
|
-
tot.manual += manual2;
|
|
49737
|
+
const t2 = tallyRows(g.rows);
|
|
49738
|
+
out2.push(`| ${g.key} ${g.title} | ${t2.c} | ${t2.nc} | ${t2.na} | ${t2.manual} |`);
|
|
49586
49739
|
}
|
|
49740
|
+
const tot = reportTotals(opts.groups);
|
|
49587
49741
|
out2.push(`| **${s.total}** | **${tot.c}** | **${tot.nc}** | **${tot.na}** | **${tot.manual}** |`, "");
|
|
49588
49742
|
out2.push(`## ${s.ncTitle}`, "");
|
|
49589
49743
|
const { nc: ncUnits, advisory: advisoryUnits } = partitionUnits(prdUnits(r, opts.standard, lang));
|
|
@@ -49667,8 +49821,7 @@ function render(r, lang, opts) {
|
|
|
49667
49821
|
}
|
|
49668
49822
|
return out2.join("\n");
|
|
49669
49823
|
}
|
|
49670
|
-
function
|
|
49671
|
-
const s = L4[lang];
|
|
49824
|
+
function reportGroups(r, lang = "en") {
|
|
49672
49825
|
const byGuideline = /* @__PURE__ */ new Map();
|
|
49673
49826
|
for (const c2 of r.criteria) {
|
|
49674
49827
|
const title2 = scTitle(c2.id, lang);
|
|
@@ -49682,12 +49835,14 @@ function renderReport(r, lang = "en", outDir) {
|
|
|
49682
49835
|
};
|
|
49683
49836
|
(byGuideline.get(c2.guideline) ?? byGuideline.set(c2.guideline, []).get(c2.guideline)).push(row);
|
|
49684
49837
|
}
|
|
49685
|
-
|
|
49686
|
-
return render(r, lang, { std: s.wcagStd, groupHead: s.byGuideline, groups, standard: CORE2, outDir });
|
|
49838
|
+
return r.guidelines.map((g) => ({ key: g.key, title: guidelineTitle(g.key, lang) ?? g.title, rows: byGuideline.get(g.key) ?? [] }));
|
|
49687
49839
|
}
|
|
49688
|
-
function
|
|
49840
|
+
function renderReport(r, lang = "en", outDir) {
|
|
49841
|
+
const s = L4[lang];
|
|
49842
|
+
return render(r, lang, { std: s.wcagStd, groupHead: s.byGuideline, groups: reportGroups(r, lang), standard: CORE2, outDir });
|
|
49843
|
+
}
|
|
49844
|
+
function packReportGroups(r, pack, lang = "en") {
|
|
49689
49845
|
const derived = derivePackResults(r, pack.key);
|
|
49690
|
-
const std = `${pack.name} ${pack.baseVersion}`;
|
|
49691
49846
|
const s = L4[lang];
|
|
49692
49847
|
const naReason = lang === "fr" ? "Aucun crit\xE8re de succ\xE8s WCAG mapp\xE9 n'est applicable dans le p\xE9rim\xE8tre." : "No mapped WCAG success criterion is applicable in scope.";
|
|
49693
49848
|
const byTheme = /* @__PURE__ */ new Map();
|
|
@@ -49705,11 +49860,15 @@ function renderPackReport(r, pack, lang = "en", outDir) {
|
|
|
49705
49860
|
};
|
|
49706
49861
|
(byTheme.get(pr.theme) ?? byTheme.set(pr.theme, []).get(pr.theme)).push(row);
|
|
49707
49862
|
}
|
|
49708
|
-
|
|
49863
|
+
return pack.themes.map((t2) => ({ key: `${t2.number}.`, title: themeName(pack, t2.number, lang) ?? "", rows: byTheme.get(t2.number) ?? [] }));
|
|
49864
|
+
}
|
|
49865
|
+
function renderPackReport(r, pack, lang = "en", outDir) {
|
|
49866
|
+
const derived = derivePackResults(r, pack.key);
|
|
49867
|
+
const std = `${pack.name} ${pack.baseVersion}`;
|
|
49709
49868
|
return render(r, lang, {
|
|
49710
49869
|
std,
|
|
49711
49870
|
groupHead: L4[lang].byTheme,
|
|
49712
|
-
groups,
|
|
49871
|
+
groups: packReportGroups(r, pack, lang),
|
|
49713
49872
|
derivedOf: std,
|
|
49714
49873
|
standard: pack.key,
|
|
49715
49874
|
partialAudit: untestedNeedsRendering(r),
|
|
@@ -51236,7 +51395,6 @@ var L7 = {
|
|
|
51236
51395
|
tally: (c2, nc, na, m) => `${c2} conforme(s) \xB7 ${nc} non conforme(s) \xB7 ${na} non applicable(s) \xB7 ${m} \xE0 \xE9valuer`,
|
|
51237
51396
|
coverage: (decided, total) => `Couverture : ${decided}/${total} crit\xE8re(s) \xE9valu\xE9(s) \u2014 le taux ci-dessus ne porte que sur eux, et ne dit rien des ${total - decided} autres.`,
|
|
51238
51397
|
tests: "Tests",
|
|
51239
|
-
agentMark: "`C*` : conformit\xE9 tranch\xE9e par l'agent IA \xE0 partir des \xE9vidences cit\xE9es (gat\xE9), et non prouv\xE9e par le moteur d\xE9terministe.",
|
|
51240
51398
|
screenshotAlt: (n) => `Capture d'\xE9cran de la page ${n}`,
|
|
51241
51399
|
noScreenshot: "Aucune capture d'\xE9cran pour cette page (le producteur n'en a pas fourni) \u2014 le tier pixel est donc inactif ici.",
|
|
51242
51400
|
gridTitle: "Grille des crit\xE8res",
|
|
@@ -51277,7 +51435,6 @@ var L7 = {
|
|
|
51277
51435
|
tally: (c2, nc, na, m) => `${c2} conforming \xB7 ${nc} non-conforming \xB7 ${na} not applicable \xB7 ${m} to assess`,
|
|
51278
51436
|
coverage: (decided, total) => `Coverage: ${decided}/${total} criteria assessed \u2014 the rate above covers only those, and says nothing about the other ${total - decided}.`,
|
|
51279
51437
|
tests: "Tests",
|
|
51280
|
-
agentMark: "`C*`: conformity ruled by the AI agent from the evidence it cited (gated), not proven by the deterministic engine.",
|
|
51281
51438
|
screenshotAlt: (n) => `Screenshot of the ${n} page`,
|
|
51282
51439
|
noScreenshot: "No screenshot for this page (the producer supplied none) \u2014 the pixel tier is therefore inactive here.",
|
|
51283
51440
|
gridTitle: "Criteria grid",
|
|
@@ -51298,7 +51455,7 @@ var L7 = {
|
|
|
51298
51455
|
indexNote: "One sheet per page. `X % (d/t)`: the rate covers only the `d` criteria decided out of `t` \u2014 it says nothing about the others. `\u2014` means no criterion was decided on this page, so it is NEITHER conformity NOR non-conformity."
|
|
51299
51456
|
}
|
|
51300
51457
|
};
|
|
51301
|
-
function
|
|
51458
|
+
function pageCriterionRows(result, page, standard, lang) {
|
|
51302
51459
|
if (isCore(standard)) {
|
|
51303
51460
|
return [...page.criteria].sort((a, b) => compareSC(a.id, b.id)).map((c2) => ({
|
|
51304
51461
|
id: c2.id,
|
|
@@ -51320,7 +51477,7 @@ function rowsFor(result, page, standard, lang) {
|
|
|
51320
51477
|
decidedBy: byId2.get(pc.id)?.decidedBy
|
|
51321
51478
|
}));
|
|
51322
51479
|
}
|
|
51323
|
-
function
|
|
51480
|
+
function pageTally(rows) {
|
|
51324
51481
|
return {
|
|
51325
51482
|
c: rows.filter((r) => r.status === "C").length,
|
|
51326
51483
|
nc: rows.filter((r) => r.status === "NC").length,
|
|
@@ -51328,12 +51485,12 @@ function tally(rows) {
|
|
|
51328
51485
|
manual: rows.filter((r) => r.status === "manual").length
|
|
51329
51486
|
};
|
|
51330
51487
|
}
|
|
51331
|
-
function
|
|
51332
|
-
const t2 =
|
|
51488
|
+
function pageCoverage(rows) {
|
|
51489
|
+
const t2 = pageTally(rows);
|
|
51333
51490
|
return { decided: t2.c + t2.nc, total: rows.length };
|
|
51334
51491
|
}
|
|
51335
|
-
function
|
|
51336
|
-
const { c: c2, nc } =
|
|
51492
|
+
function pageRatePct(rows) {
|
|
51493
|
+
const { c: c2, nc } = pageTally(rows);
|
|
51337
51494
|
return c2 + nc === 0 ? null : Math.round(c2 / (c2 + nc) * 100);
|
|
51338
51495
|
}
|
|
51339
51496
|
function renderPageReport(result, page, opts = {}) {
|
|
@@ -51348,10 +51505,10 @@ function renderPageReport(result, page, opts = {}) {
|
|
|
51348
51505
|
meta2.push(`- **${s.basis}** : ${basisLabel(page.basis, lang)}`);
|
|
51349
51506
|
if (page.auth) meta2.push(`- **${s.auth}** : \u2705`);
|
|
51350
51507
|
out2.push(...meta2);
|
|
51351
|
-
const rows =
|
|
51352
|
-
const t2 =
|
|
51353
|
-
const cov =
|
|
51354
|
-
const rate =
|
|
51508
|
+
const rows = pageCriterionRows(result, page, standard, lang);
|
|
51509
|
+
const t2 = pageTally(rows);
|
|
51510
|
+
const cov = pageCoverage(rows);
|
|
51511
|
+
const rate = pageRatePct(rows);
|
|
51355
51512
|
out2.push(`- **${s.rate}** : **${rate === null ? "\u2014" : `${rate} %`}** _(${s.rateNote})_`);
|
|
51356
51513
|
out2.push(`- ${s.tally(t2.c, t2.nc, t2.na, t2.manual)}`);
|
|
51357
51514
|
out2.push(`- ${s.coverage(cov.decided, cov.total)}`, "");
|
|
@@ -51359,6 +51516,7 @@ function renderPageReport(result, page, opts = {}) {
|
|
|
51359
51516
|
const shot = opts.screenshots?.get(page.id);
|
|
51360
51517
|
if (shot) out2.push(``, "");
|
|
51361
51518
|
else out2.push(`_${s.noScreenshot}_`, "");
|
|
51519
|
+
if (opts.evidenceNotice?.length) out2.push(...opts.evidenceNotice, "");
|
|
51362
51520
|
const withTests = rows.some((r) => r.tests.length);
|
|
51363
51521
|
out2.push(`${h}# ${s.gridTitle}`, "", `> ${s.gridNote}`, "");
|
|
51364
51522
|
out2.push(withTests ? `| ${s.criterion} | ${s.tests} | ${s.status} |` : `| ${s.criterion} | ${s.status} |`);
|
|
@@ -51374,16 +51532,17 @@ function renderPageReport(result, page, opts = {}) {
|
|
|
51374
51532
|
else out2.push(`| ${row.label} | ${mark} |`);
|
|
51375
51533
|
}
|
|
51376
51534
|
out2.push("", `> ${s.manualWarn}`, "");
|
|
51377
|
-
if (rows.some((r) => r.decidedBy === "agent" && r.status === "C")) out2.push(`> ${
|
|
51535
|
+
if (rows.some((r) => r.decidedBy === "agent" && r.status === "C")) out2.push(`> ${agentMarkNote(lang)}`, "");
|
|
51378
51536
|
const units = prdUnits(pageView(result, page), standard, lang);
|
|
51379
51537
|
const ncUnits = units.filter((u) => !u.advisory);
|
|
51380
51538
|
const advUnits = units.filter((u) => u.advisory);
|
|
51539
|
+
const unit = { heading: `${h}##`, collapse: true, ...opts.cropFor ? { cropFor: opts.cropFor } : {} };
|
|
51381
51540
|
out2.push(`${h}# ${s.ncTitle}`, "");
|
|
51382
51541
|
if (!ncUnits.length) out2.push(s.noNc, "");
|
|
51383
|
-
else for (const u of ncUnits) out2.push(...renderAuditorUnit(u, standard, lang,
|
|
51542
|
+
else for (const u of ncUnits) out2.push(...renderAuditorUnit(u, standard, lang, unit));
|
|
51384
51543
|
if (advUnits.length) {
|
|
51385
51544
|
out2.push(`${h}# \u{1F4A1} ${s.recTitle}`, "", `> ${s.recNote}`, "");
|
|
51386
|
-
for (const u of advUnits) out2.push(...renderAuditorUnit(u, standard, lang,
|
|
51545
|
+
for (const u of advUnits) out2.push(...renderAuditorUnit(u, standard, lang, unit));
|
|
51387
51546
|
}
|
|
51388
51547
|
return out2.join("\n");
|
|
51389
51548
|
}
|
|
@@ -51409,12 +51568,12 @@ function renderPagesIndex(result, pages, opts = {}) {
|
|
|
51409
51568
|
out2.push(`| ${s.page} | ${s.url} | ${s.basis} | ${s.rateShort} | ${s.blocking} | ${s.major} | ${s.minor} | ${s.sheet} |`);
|
|
51410
51569
|
out2.push("| --- | --- | --- | --- | --- | --- | --- | --- |");
|
|
51411
51570
|
for (const p of pages) {
|
|
51412
|
-
const rows =
|
|
51413
|
-
const cov =
|
|
51571
|
+
const rows = pageCriterionRows(result, p, standard, lang);
|
|
51572
|
+
const cov = pageCoverage(rows);
|
|
51414
51573
|
const nc = [...p.findings, ...(result.packFindings ?? []).filter((f) => f.page === p.id)].filter((f) => !f.advisory);
|
|
51415
51574
|
const href = opts.hrefs?.get(p.id);
|
|
51416
51575
|
out2.push(
|
|
51417
|
-
`| ${p.name}${p.auth ? " \u{1F512}" : ""} | \`${p.url}\` | ${basisLabel(p.basis, lang)} | ${formatRate(
|
|
51576
|
+
`| ${p.name}${p.auth ? " \u{1F512}" : ""} | \`${p.url}\` | ${basisLabel(p.basis, lang)} | ${formatRate(pageRatePct(rows), cov.decided, cov.total)} | ${nc.filter((f) => f.severity === "bloquant").length} | ${nc.filter((f) => f.severity === "majeur").length} | ${nc.filter((f) => f.severity === "mineur").length} | ${href ? `[${p.id}](${href})` : p.id} |`
|
|
51418
51577
|
);
|
|
51419
51578
|
}
|
|
51420
51579
|
out2.push("");
|
|
@@ -56097,6 +56256,7 @@ var S = {
|
|
|
56097
56256
|
where: "Emplacement",
|
|
56098
56257
|
what: "Constat",
|
|
56099
56258
|
more: (n) => `\u2026 et ${n} autre(s).`,
|
|
56259
|
+
moreGroups: (n) => `\u2026 et ${n} autre(s) groupe(s) \u2014 voir le r\xE9sum\xE9 de job.`,
|
|
56100
56260
|
perPage: "Bilan page par page",
|
|
56101
56261
|
page: "Page",
|
|
56102
56262
|
count: "Constats",
|
|
@@ -56104,6 +56264,16 @@ var S = {
|
|
|
56104
56264
|
pageRate: "Taux",
|
|
56105
56265
|
snapshot: "instantan\xE9",
|
|
56106
56266
|
source: "source",
|
|
56267
|
+
occurrences: "Occ.",
|
|
56268
|
+
pagesCol: "Pages",
|
|
56269
|
+
grouped: (groups, occ) => `${groups} d\xE9faut(s) distinct(s) \xB7 ${occ} occurrence(s)`,
|
|
56270
|
+
groupNote: "Une ligne par (crit\xE8re, r\xE8gle, s\xE9lecteur) : un m\xEAme d\xE9faut de design system r\xE9p\xE9t\xE9 sur toutes les routes compte pour un. Les colonnes Occ. et Pages disent l'ampleur r\xE9elle.",
|
|
56271
|
+
verdictFail: (n) => `\u{1F534} ${n} non-conformit\xE9(s) bloquante(s) \u2014 la porte est rouge.`,
|
|
56272
|
+
verdictWarn: "\u{1F7E0} Aucune non-conformit\xE9 bloquante ; des constats majeurs ou mineurs restent \xE0 traiter.",
|
|
56273
|
+
verdictPass: "\u2705 Aucune non-conformit\xE9 relev\xE9e par le moteur statique.",
|
|
56274
|
+
artifact: (name2) => `Rapport complet (HTML, captures annot\xE9es) : artefact **${name2}** du run.`,
|
|
56275
|
+
runLink: (url) => `[Voir le run et son r\xE9sum\xE9 de job](${url})`,
|
|
56276
|
+
clamped: (n) => `_${n} groupe(s) retir\xE9(s) de ce commentaire pour tenir dans la limite de GitHub \u2014 le r\xE9sum\xE9 de job les porte tous._`,
|
|
56107
56277
|
unanchored: (n) => `${n} constat(s) rattach\xE9(s) \xE0 une URL, sans ligne de code \xE0 annoter \u2014 voir le rapport.`,
|
|
56108
56278
|
unattributed: (n) => `${n} constat(s) ne sont rattach\xE9s \xE0 aucune page (code partag\xE9, fichier hors routes) \u2014 compt\xE9s dans l'audit global, jamais r\xE9partis d'office.`,
|
|
56109
56279
|
sourceBasis: "Une page marqu\xE9e \xAB source \xBB n'a pas d'instantan\xE9 : l'absence de constat n'y vaut PAS conformit\xE9, et son taux ne porte que sur ce que le moteur a pu d\xE9cider ailleurs."
|
|
@@ -56119,6 +56289,7 @@ var S = {
|
|
|
56119
56289
|
where: "Location",
|
|
56120
56290
|
what: "Finding",
|
|
56121
56291
|
more: (n) => `\u2026 and ${n} more.`,
|
|
56292
|
+
moreGroups: (n) => `\u2026 and ${n} more group(s) \u2014 see the job summary.`,
|
|
56122
56293
|
perPage: "Page-by-page scoreboard",
|
|
56123
56294
|
page: "Page",
|
|
56124
56295
|
count: "Findings",
|
|
@@ -56126,20 +56297,75 @@ var S = {
|
|
|
56126
56297
|
pageRate: "Rate",
|
|
56127
56298
|
snapshot: "snapshot",
|
|
56128
56299
|
source: "source",
|
|
56300
|
+
occurrences: "Occ.",
|
|
56301
|
+
pagesCol: "Pages",
|
|
56302
|
+
grouped: (groups, occ) => `${groups} distinct defect(s) \xB7 ${occ} occurrence(s)`,
|
|
56303
|
+
groupNote: "One row per (criterion, rule, selector): one design-system defect repeated across every route counts once. The Occ. and Pages columns carry the real scale.",
|
|
56304
|
+
verdictFail: (n) => `\u{1F534} ${n} blocking non-conformity(ies) \u2014 the gate is red.`,
|
|
56305
|
+
verdictWarn: "\u{1F7E0} No blocking non-conformity; major or minor findings remain.",
|
|
56306
|
+
verdictPass: "\u2705 No non-conformity found by the static engine.",
|
|
56307
|
+
artifact: (name2) => `Full report (HTML, annotated crops): artifact **${name2}** of this run.`,
|
|
56308
|
+
runLink: (url) => `[See the run and its job summary](${url})`,
|
|
56309
|
+
clamped: (n) => `_${n} group(s) dropped from this comment to fit GitHub's limit \u2014 the job summary carries them all._`,
|
|
56129
56310
|
unanchored: (n) => `${n} finding(s) keyed to a URL, with no code line to annotate \u2014 see the report.`,
|
|
56130
56311
|
unattributed: (n) => `${n} finding(s) are attributed to no page (shared code, file outside any route) \u2014 counted in the overall audit, never spread across pages.`,
|
|
56131
56312
|
sourceBasis: 'A page marked "source" has no snapshot: the absence of a finding there does NOT mean conforming, and its rate covers only what the engine could decide elsewhere.'
|
|
56132
56313
|
}
|
|
56133
56314
|
};
|
|
56134
56315
|
var MAX_ROWS = 50;
|
|
56316
|
+
var COMMENT_ROWS = 10;
|
|
56317
|
+
var COMMENT_LIMIT = 65536;
|
|
56318
|
+
function groupFindings(findings, standard, lang, baseDir) {
|
|
56319
|
+
const groups = /* @__PURE__ */ new Map();
|
|
56320
|
+
for (const f of findings) {
|
|
56321
|
+
const criterion = criterionLabel2(f, standard);
|
|
56322
|
+
const key = `${criterion}\0${f.ruleId}\0${f.selectorHint}`;
|
|
56323
|
+
const g = groups.get(key);
|
|
56324
|
+
if (g) {
|
|
56325
|
+
g.occurrences++;
|
|
56326
|
+
if (f.page) g.pageSet.add(f.page);
|
|
56327
|
+
continue;
|
|
56328
|
+
}
|
|
56329
|
+
groups.set(key, {
|
|
56330
|
+
criterion,
|
|
56331
|
+
ruleId: f.ruleId,
|
|
56332
|
+
selectorHint: f.selectorHint,
|
|
56333
|
+
severity: f.severity,
|
|
56334
|
+
message: resolveMessage(f, lang),
|
|
56335
|
+
where: isUrl2(f.file) ? f.file : `${repoRelative(f.file, baseDir)}:${Math.max(1, f.line)}`,
|
|
56336
|
+
occurrences: 1,
|
|
56337
|
+
pages: 0,
|
|
56338
|
+
pageSet: new Set(f.page ? [f.page] : [])
|
|
56339
|
+
});
|
|
56340
|
+
}
|
|
56341
|
+
return [...groups.values()].map(({ pageSet, ...g }) => ({ ...g, pages: pageSet.size })).sort((a, b) => SEV_ORDER4.indexOf(a.severity) - SEV_ORDER4.indexOf(b.severity) || b.occurrences - a.occurrences || a.criterion.localeCompare(b.criterion));
|
|
56342
|
+
}
|
|
56343
|
+
function runRate(result, standard, lang) {
|
|
56344
|
+
const groups = isCore(standard) ? reportGroups(result, lang) : packReportGroups(result, loadPack(standard), lang);
|
|
56345
|
+
const { decided, total } = reportCoverage(groups);
|
|
56346
|
+
const agentRuled = groups.some((g) => g.rows.some((r) => r.decidedBy === "agent" && r.status === "C"));
|
|
56347
|
+
return { text: `${formatRate(decided === 0 ? null : result.conformancePct, decided, total)}${agentRuled ? "*" : ""}`, agentRuled };
|
|
56348
|
+
}
|
|
56349
|
+
function groupTableHead(s) {
|
|
56350
|
+
return [`| ${s.severity} | ${s.criterion} | ${s.where} | ${s.what} | ${s.occurrences} | ${s.pagesCol} |`, "| --- | --- | --- | --- | ---: | ---: |"];
|
|
56351
|
+
}
|
|
56352
|
+
function groupRow(g) {
|
|
56353
|
+
const cell = (v) => v.replace(/\|/g, "\\|");
|
|
56354
|
+
return `| ${ICON5[g.severity]} ${g.severity} | ${cell(g.criterion)} | \`${cell(g.where)}\` (\`${cell(g.selectorHint)}\`) | ${cell(g.message)} | ${g.occurrences} | ${g.pages || "\u2014"} |`;
|
|
56355
|
+
}
|
|
56356
|
+
function groupTable(rows, s) {
|
|
56357
|
+
return [...groupTableHead(s), ...rows.map(groupRow)];
|
|
56358
|
+
}
|
|
56135
56359
|
function stepSummary(result, opts = {}) {
|
|
56136
56360
|
const standard = opts.standard ?? CORE2;
|
|
56137
56361
|
const lang = opts.lang ?? "en";
|
|
56138
56362
|
const s = S[lang];
|
|
56139
56363
|
const stdLabel = isCore(standard) ? "WCAG 2.2 AA" : loadPack(standard).name;
|
|
56364
|
+
const rate = runRate(result, standard, lang);
|
|
56140
56365
|
const out2 = [];
|
|
56141
56366
|
out2.push(`## ${s.title} \u2014 ${stdLabel}`, "");
|
|
56142
|
-
out2.push(`\`${result.date}\` \xB7 ${result.scope.files} ${s.files} \xB7 **${
|
|
56367
|
+
out2.push(`\`${result.date}\` \xB7 ${result.scope.files} ${s.files} \xB7 **${rate.text}** ${s.rate}`, "");
|
|
56368
|
+
if (rate.agentRuled) out2.push(`> ${agentMarkNote(lang)}`, "");
|
|
56143
56369
|
const baseDir = opts.baseDir ?? process.cwd();
|
|
56144
56370
|
const all = findingsForStandard(result, standard);
|
|
56145
56371
|
if (!all.length) {
|
|
@@ -56147,21 +56373,52 @@ function stepSummary(result, opts = {}) {
|
|
|
56147
56373
|
out2.push(perPageTable(result, standard, lang));
|
|
56148
56374
|
return out2.join("\n");
|
|
56149
56375
|
}
|
|
56150
|
-
const
|
|
56151
|
-
out2.push(`### ${s.findings}
|
|
56152
|
-
out2.push(
|
|
56153
|
-
|
|
56154
|
-
|
|
56155
|
-
const msg = resolveMessage(f, lang).replace(/\|/g, "\\|");
|
|
56156
|
-
out2.push(`| ${ICON5[f.severity]} ${f.severity} | ${criterionLabel2(f, standard)} | \`${where}\` | ${msg} |`);
|
|
56157
|
-
}
|
|
56158
|
-
if (sorted.length > MAX_ROWS) out2.push("", s.more(sorted.length - MAX_ROWS));
|
|
56376
|
+
const grouped = groupFindings(all, standard, lang, baseDir);
|
|
56377
|
+
out2.push(`### ${s.findings} \u2014 ${s.grouped(grouped.length, all.length)}`, "");
|
|
56378
|
+
out2.push(`> ${s.groupNote}`, "");
|
|
56379
|
+
out2.push(...groupTable(grouped.slice(0, MAX_ROWS), s));
|
|
56380
|
+
if (grouped.length > MAX_ROWS) out2.push("", s.more(grouped.length - MAX_ROWS));
|
|
56159
56381
|
out2.push("");
|
|
56160
56382
|
const unanchored = all.filter((f) => isUrl2(f.file)).length;
|
|
56161
56383
|
if (unanchored) out2.push(`> ${s.unanchored(unanchored)}`, "");
|
|
56162
56384
|
out2.push(perPageTable(result, standard, lang));
|
|
56163
56385
|
return out2.join("\n");
|
|
56164
56386
|
}
|
|
56387
|
+
function prComment(result, opts = {}) {
|
|
56388
|
+
const standard = opts.standard ?? CORE2;
|
|
56389
|
+
const lang = opts.lang ?? "en";
|
|
56390
|
+
const s = S[lang];
|
|
56391
|
+
const baseDir = opts.baseDir ?? process.cwd();
|
|
56392
|
+
const stdLabel = isCore(standard) ? "WCAG 2.2 AA" : loadPack(standard).name;
|
|
56393
|
+
const all = findingsForStandard(result, standard);
|
|
56394
|
+
const normative = all.filter((f) => !f.advisory);
|
|
56395
|
+
const blocking = normative.filter((f) => f.severity === "bloquant").length;
|
|
56396
|
+
const rate = runRate(result, standard, lang);
|
|
56397
|
+
const grouped = groupFindings(all, standard, lang, baseDir);
|
|
56398
|
+
const orphans = unattributedFindings(result).filter((f) => !f.advisory).length;
|
|
56399
|
+
const head = [];
|
|
56400
|
+
head.push(`### ${s.title} \u2014 ${stdLabel}`, "");
|
|
56401
|
+
head.push(blocking ? s.verdictFail(blocking) : normative.length ? s.verdictWarn : s.verdictPass, "");
|
|
56402
|
+
head.push(`\`${result.date}\` \xB7 ${result.scope.files} ${s.files} \xB7 **${rate.text}** ${s.rate}`, "");
|
|
56403
|
+
if (rate.agentRuled) head.push(`> ${agentMarkNote(lang)}`, "");
|
|
56404
|
+
if (orphans) head.push(`> ${s.unattributed(orphans)}`, "");
|
|
56405
|
+
const tail = [];
|
|
56406
|
+
if (opts.artifactName) tail.push(s.artifact(opts.artifactName), "");
|
|
56407
|
+
if (opts.runUrl) tail.push(s.runLink(opts.runUrl), "");
|
|
56408
|
+
const assemble = (rows2) => {
|
|
56409
|
+
const body2 = [];
|
|
56410
|
+
if (grouped.length) {
|
|
56411
|
+
body2.push(s.grouped(grouped.length, all.length), "");
|
|
56412
|
+
body2.push(...groupTable(grouped.slice(0, rows2), s), "");
|
|
56413
|
+
const omitted = grouped.length - rows2;
|
|
56414
|
+
if (omitted > 0) body2.push(rows2 < COMMENT_ROWS ? s.clamped(omitted) : s.moreGroups(omitted), "");
|
|
56415
|
+
}
|
|
56416
|
+
return [...head, ...body2, ...tail].join("\n").trimEnd();
|
|
56417
|
+
};
|
|
56418
|
+
let rows = Math.min(COMMENT_ROWS, grouped.length);
|
|
56419
|
+
while (rows > 0 && assemble(rows).length > COMMENT_LIMIT) rows--;
|
|
56420
|
+
return assemble(rows);
|
|
56421
|
+
}
|
|
56165
56422
|
function perPageTable(result, standard = CORE2, lang = "en") {
|
|
56166
56423
|
const s = S[lang];
|
|
56167
56424
|
const scope = pagesOf(result);
|
|
@@ -56186,10 +56443,992 @@ function perPageTable(result, standard = CORE2, lang = "en") {
|
|
|
56186
56443
|
return out2.join("\n");
|
|
56187
56444
|
}
|
|
56188
56445
|
|
|
56446
|
+
// src/evidence.ts
|
|
56447
|
+
import { createHash as createHash6 } from "crypto";
|
|
56448
|
+
import { existsSync as existsSync28, mkdirSync as mkdirSync13, readFileSync as readFileSync23, writeFileSync as writeFileSync15 } from "fs";
|
|
56449
|
+
import { join as join40 } from "path";
|
|
56450
|
+
var DEFAULT_GEOMETRY = { pad: 24, minWidth: 320, minHeight: 120, maxWidth: 960, maxHeight: 540 };
|
|
56451
|
+
var DEFAULT_CAPS = { perRule: 6, perPage: 12, total: 200 };
|
|
56452
|
+
var HALO = { r: 255, g: 255, b: 255, a: 0.92 };
|
|
56453
|
+
var MARK3 = { r: 179, g: 38, b: 30, a: 1 };
|
|
56454
|
+
var RING = 3;
|
|
56455
|
+
function pageIdOfSnapshot(file) {
|
|
56456
|
+
if (!isSnapshotDom(file)) return null;
|
|
56457
|
+
const posix3 = file.split("\\").join("/");
|
|
56458
|
+
const at = posix3.lastIndexOf(`${PAGES_DIR}/`);
|
|
56459
|
+
if (at < 0) return null;
|
|
56460
|
+
const rest = posix3.slice(at + PAGES_DIR.length + 1);
|
|
56461
|
+
const id = rest.slice(0, rest.indexOf("/"));
|
|
56462
|
+
return id || null;
|
|
56463
|
+
}
|
|
56464
|
+
function readJson3(file) {
|
|
56465
|
+
try {
|
|
56466
|
+
return JSON.parse(readFileSync23(file, "utf8"));
|
|
56467
|
+
} catch {
|
|
56468
|
+
return void 0;
|
|
56469
|
+
}
|
|
56470
|
+
}
|
|
56471
|
+
function resolveScale(img, viewport, boxes) {
|
|
56472
|
+
const candidates2 = [viewport?.width, boxes.get(0)?.w];
|
|
56473
|
+
for (const w of candidates2) {
|
|
56474
|
+
if (!w || w <= 0) continue;
|
|
56475
|
+
const scale = img.width / w;
|
|
56476
|
+
if (scale >= 0.5 && scale <= 4) return scale;
|
|
56477
|
+
}
|
|
56478
|
+
return null;
|
|
56479
|
+
}
|
|
56480
|
+
function loadPageEvidence(root, pageId) {
|
|
56481
|
+
const dir = snapshotDir(root, pageId);
|
|
56482
|
+
const domFile = join40(dir, "dom.html");
|
|
56483
|
+
const shotFile = join40(dir, "screen.png");
|
|
56484
|
+
if (!existsSync28(domFile)) return { skip: "no-snapshot" };
|
|
56485
|
+
if (!existsSync28(shotFile)) return { skip: "no-screenshot" };
|
|
56486
|
+
let dom;
|
|
56487
|
+
let shot;
|
|
56488
|
+
try {
|
|
56489
|
+
dom = readFileSync23(domFile, "utf8");
|
|
56490
|
+
shot = readFileSync23(shotFile);
|
|
56491
|
+
} catch {
|
|
56492
|
+
return { skip: "no-snapshot" };
|
|
56493
|
+
}
|
|
56494
|
+
const img = decodePng(shot);
|
|
56495
|
+
if (!img) return { skip: "unreadable-image" };
|
|
56496
|
+
const digest = readJson3(join40(dir, "boxes.json"));
|
|
56497
|
+
if (!digest) return { skip: "no-boxes" };
|
|
56498
|
+
const doc = parseHtml(dom, domFile);
|
|
56499
|
+
const boxes = alignedBoxes(dom, digest, doc);
|
|
56500
|
+
if (!boxes) return { skip: "no-boxes" };
|
|
56501
|
+
const scale = resolveScale(img, metaOf(dir)?.viewport, boxes);
|
|
56502
|
+
if (scale === null) return { skip: "unknown-scale" };
|
|
56503
|
+
const ordinalOf = /* @__PURE__ */ new Map();
|
|
56504
|
+
for (let i2 = 0; i2 < doc.elements.length; i2++) {
|
|
56505
|
+
const start2 = doc.elements[i2]?.start;
|
|
56506
|
+
if (start2 !== void 0 && !ordinalOf.has(start2)) ordinalOf.set(start2, i2);
|
|
56507
|
+
}
|
|
56508
|
+
const viewport = metaOf(dir)?.viewport ?? { width: Math.round(img.width / scale), height: Math.round(img.height / scale) };
|
|
56509
|
+
return { pageId, img, scale, viewport, ordinalOf, boxes, truncated: Boolean(digest.truncated) };
|
|
56510
|
+
}
|
|
56511
|
+
function metaOf(dir) {
|
|
56512
|
+
const raw = readJson3(join40(dir, "meta.json"));
|
|
56513
|
+
if (raw === void 0) return void 0;
|
|
56514
|
+
const v = validateSnapshotMeta(raw);
|
|
56515
|
+
return v.ok && v.meta ? v.meta : void 0;
|
|
56516
|
+
}
|
|
56517
|
+
function cropRect(box, g, scale, img) {
|
|
56518
|
+
const bx = box.x * scale;
|
|
56519
|
+
const by = box.y * scale;
|
|
56520
|
+
const bw = box.w * scale;
|
|
56521
|
+
const bh = box.h * scale;
|
|
56522
|
+
const w = Math.min(Math.max(bw + g.pad * 2 * scale, g.minWidth * scale), g.maxWidth * scale, img.width);
|
|
56523
|
+
const h = Math.min(Math.max(bh + g.pad * 2 * scale, g.minHeight * scale), g.maxHeight * scale, img.height);
|
|
56524
|
+
let x = bx + bw / 2 - w / 2;
|
|
56525
|
+
let y = by + bh / 2 - h / 2;
|
|
56526
|
+
x = Math.max(0, Math.min(x, img.width - w));
|
|
56527
|
+
y = Math.max(0, Math.min(y, img.height - h));
|
|
56528
|
+
return { x, y, w, h };
|
|
56529
|
+
}
|
|
56530
|
+
function annotate(raster, target) {
|
|
56531
|
+
strokeRect(raster, { x: target.x - RING, y: target.y - RING, w: target.w + RING * 2, h: target.h + RING * 2 }, HALO, RING);
|
|
56532
|
+
strokeRect(raster, target, MARK3, RING);
|
|
56533
|
+
strokeRect(raster, { x: target.x + RING, y: target.y + RING, w: target.w - RING * 2, h: target.h - RING * 2 }, HALO, 1, 6);
|
|
56534
|
+
const arm = Math.max(6, Math.min(18, Math.round(Math.min(target.w, target.h) / 3)));
|
|
56535
|
+
const t2 = RING;
|
|
56536
|
+
const x0 = target.x - RING;
|
|
56537
|
+
const y0 = target.y - RING;
|
|
56538
|
+
const x1 = target.x + target.w + RING;
|
|
56539
|
+
const y1 = target.y + target.h + RING;
|
|
56540
|
+
for (const [cx, cy, dx, dy] of [
|
|
56541
|
+
[x0, y0, 1, 1],
|
|
56542
|
+
[x1, y0, -1, 1],
|
|
56543
|
+
[x0, y1, 1, -1],
|
|
56544
|
+
[x1, y1, -1, -1]
|
|
56545
|
+
]) {
|
|
56546
|
+
fillRect(raster, { x: dx > 0 ? cx : cx - arm, y: dy > 0 ? cy - t2 : cy, w: arm, h: t2 }, MARK3);
|
|
56547
|
+
fillRect(raster, { x: dx > 0 ? cx - t2 : cx, y: dy > 0 ? cy : cy - arm, w: t2, h: arm }, MARK3);
|
|
56548
|
+
}
|
|
56549
|
+
}
|
|
56550
|
+
function cropFinding(ctx, f, geometry) {
|
|
56551
|
+
const g = { ...DEFAULT_GEOMETRY, ...geometry };
|
|
56552
|
+
if (f.sourceStart === void 0) return { skip: "no-offsets" };
|
|
56553
|
+
const ordinal = ctx.ordinalOf.get(f.sourceStart);
|
|
56554
|
+
if (ordinal === void 0) return { skip: "unjoinable" };
|
|
56555
|
+
if (ordinal === 0) return { skip: "page-scope" };
|
|
56556
|
+
const box = ctx.boxes.get(ordinal);
|
|
56557
|
+
if (!box) return { skip: ctx.truncated ? "truncated" : "no-boxes" };
|
|
56558
|
+
if (box.w <= 0 || box.h <= 0) return { skip: "zero-area" };
|
|
56559
|
+
const vpArea = ctx.viewport.width * ctx.viewport.height;
|
|
56560
|
+
if (vpArea > 0 && box.w * box.h / vpArea >= 0.7) return { skip: "page-scope" };
|
|
56561
|
+
const visibleTop = Math.max(0, box.y * ctx.scale);
|
|
56562
|
+
const visibleBottom = Math.min(ctx.img.height, (box.y + box.h) * ctx.scale);
|
|
56563
|
+
const visibleFraction = box.h > 0 ? (visibleBottom - visibleTop) / (box.h * ctx.scale) : 0;
|
|
56564
|
+
if (visibleFraction < 0.5) return { skip: "below-the-fold" };
|
|
56565
|
+
const rect = cropRect(box, g, ctx.scale, ctx.img);
|
|
56566
|
+
const crop = cropImage(ctx.img, rect);
|
|
56567
|
+
if (!crop) return { skip: "below-the-fold" };
|
|
56568
|
+
annotate(crop.raster, {
|
|
56569
|
+
x: box.x * ctx.scale - crop.rect.x,
|
|
56570
|
+
y: box.y * ctx.scale - crop.rect.y,
|
|
56571
|
+
w: box.w * ctx.scale,
|
|
56572
|
+
h: box.h * ctx.scale
|
|
56573
|
+
});
|
|
56574
|
+
return {
|
|
56575
|
+
png: encodePng(crop.raster),
|
|
56576
|
+
width: crop.raster.width,
|
|
56577
|
+
height: crop.raster.height,
|
|
56578
|
+
box,
|
|
56579
|
+
scale: ctx.scale,
|
|
56580
|
+
clipped: visibleFraction < 1
|
|
56581
|
+
};
|
|
56582
|
+
}
|
|
56583
|
+
var S2 = {
|
|
56584
|
+
fr: {
|
|
56585
|
+
// No `N.N —` sequence: src/check.ts scans the WHOLE document for criterion mentions.
|
|
56586
|
+
alt: (sel, page) => `Capture recadr\xE9e de l'\xE9l\xE9ment ${sel} sur la page ${page}, entour\xE9 d'un cadre`,
|
|
56587
|
+
notImaged: (n) => `${n} occurrence(s) ne sont pas illustr\xE9es :`,
|
|
56588
|
+
reasons: {
|
|
56589
|
+
"no-snapshot": "constat lev\xE9 sur le code source, pas sur une page captur\xE9e \u2014 il n'y a pas de pixels \xE0 montrer",
|
|
56590
|
+
"no-screenshot": "le producteur n'a pas fourni de capture pour cette page, donc le niveau pixel est inactif ici",
|
|
56591
|
+
"unreadable-image": "la capture n'est pas d\xE9codable (le moteur lit le truecolour 8 bits uniquement) \u2014 mieux vaut aucune image qu'une fausse",
|
|
56592
|
+
"no-boxes": "les positions d'\xE9l\xE9ments ne se v\xE9rifient pas contre le DOM s\xE9rialis\xE9, elles ont donc \xE9t\xE9 refus\xE9es en bloc plut\xF4t que d'encadrer le mauvais \xE9l\xE9ment",
|
|
56593
|
+
truncated: "la page d\xE9passe la limite de collecte : aucune position n'a \xE9t\xE9 enregistr\xE9e pour cet \xE9l\xE9ment",
|
|
56594
|
+
"no-offsets": "le constat ne porte pas d'ancrage dans la source, il ne peut pas \xEAtre joint \xE0 une position",
|
|
56595
|
+
unjoinable: "l'ancrage du constat ne correspond \xE0 aucun \xE9l\xE9ment du DOM s\xE9rialis\xE9",
|
|
56596
|
+
"page-scope": "le constat porte sur la page enti\xE8re \u2014 la capture de page ci-dessus est l'illustration",
|
|
56597
|
+
"zero-area": "l'\xE9l\xE9ment n'occupe aucune surface peinte",
|
|
56598
|
+
"below-the-fold": "l'\xE9l\xE9ment est hors de la capture (la capture couvre la fen\xEAtre, pas la page enti\xE8re) \u2014 sa r\xE9f\xE9rence fichier:ligne reste valable",
|
|
56599
|
+
"unknown-scale": "le rapport pixels/CSS est ind\xE9termin\xE9 pour cette page ; un rapport devin\xE9 encadrerait le mauvais endroit",
|
|
56600
|
+
capped: "occurrences regroup\xE9es : une illustration par \xE9l\xE9ment distinct, dans la limite fix\xE9e"
|
|
56601
|
+
}
|
|
56602
|
+
},
|
|
56603
|
+
en: {
|
|
56604
|
+
alt: (sel, page) => `Cropped screenshot of the ${sel} element on the ${page} page, outlined`,
|
|
56605
|
+
notImaged: (n) => `${n} occurrence(s) are not illustrated:`,
|
|
56606
|
+
reasons: {
|
|
56607
|
+
"no-snapshot": "raised on source code, not on a captured page \u2014 there are no pixels to show",
|
|
56608
|
+
"no-screenshot": "the producer supplied no screenshot for this page, so the pixel tier is inactive here",
|
|
56609
|
+
"unreadable-image": "the screenshot does not decode (the engine reads 8-bit truecolour only) \u2014 no image beats a wrong one",
|
|
56610
|
+
"no-boxes": "the element positions do not verify against the serialized DOM, so they were refused wholesale rather than outlining the wrong element",
|
|
56611
|
+
truncated: "the page exceeded the collection cap: no position was recorded for this element",
|
|
56612
|
+
"no-offsets": "the finding carries no source anchor, so it cannot be joined to a position",
|
|
56613
|
+
unjoinable: "the finding's anchor matches no element in the serialized DOM",
|
|
56614
|
+
"page-scope": "the finding is about the whole page \u2014 the page screenshot above is the illustration",
|
|
56615
|
+
"zero-area": "the element paints no box",
|
|
56616
|
+
"below-the-fold": "the element sits outside the capture (the screenshot covers the viewport, not the whole page) \u2014 its file:line reference still holds",
|
|
56617
|
+
"unknown-scale": "the image-pixel to CSS-pixel ratio is indeterminate for this page; a guessed ratio would outline the wrong place",
|
|
56618
|
+
capped: "occurrences grouped: one illustration per distinct element, up to the cap"
|
|
56619
|
+
}
|
|
56620
|
+
}
|
|
56621
|
+
};
|
|
56622
|
+
function evidenceNotice(m, pageId, lang) {
|
|
56623
|
+
const t2 = pageId === null ? m.totals : m.perPage.get(pageId);
|
|
56624
|
+
if (!t2) return [];
|
|
56625
|
+
const missing = t2.located - t2.imaged;
|
|
56626
|
+
if (missing <= 0) return [];
|
|
56627
|
+
const s = S2[lang];
|
|
56628
|
+
const out2 = [s.notImaged(missing)];
|
|
56629
|
+
for (const [reason, n] of Object.entries(t2.skipped).sort((a, b) => (b[1] ?? 0) - (a[1] ?? 0))) {
|
|
56630
|
+
if (!n) continue;
|
|
56631
|
+
out2.push(`- ${n} \u2014 ${s.reasons[reason]}`);
|
|
56632
|
+
}
|
|
56633
|
+
return out2;
|
|
56634
|
+
}
|
|
56635
|
+
function bump(t2, reason) {
|
|
56636
|
+
t2.skipped[reason] = (t2.skipped[reason] ?? 0) + 1;
|
|
56637
|
+
}
|
|
56638
|
+
function cropName(f) {
|
|
56639
|
+
return `${createHash6("sha1").update(findingId(f)).digest("hex").slice(0, 12)}.png`;
|
|
56640
|
+
}
|
|
56641
|
+
function writeEvidence(result, opts) {
|
|
56642
|
+
const root = opts.root ?? process.cwd();
|
|
56643
|
+
const caps = { ...DEFAULT_CAPS, ...opts.caps };
|
|
56644
|
+
const manifest = {
|
|
56645
|
+
crops: /* @__PURE__ */ new Map(),
|
|
56646
|
+
skipped: /* @__PURE__ */ new Map(),
|
|
56647
|
+
perPage: /* @__PURE__ */ new Map(),
|
|
56648
|
+
totals: { located: 0, imaged: 0, skipped: {} }
|
|
56649
|
+
};
|
|
56650
|
+
const byPage = /* @__PURE__ */ new Map();
|
|
56651
|
+
for (const f of [...result.findings, ...result.packFindings ?? []]) {
|
|
56652
|
+
if (f.advisory) continue;
|
|
56653
|
+
const page = pageIdOfSnapshot(f.file);
|
|
56654
|
+
if (!page) continue;
|
|
56655
|
+
const list = byPage.get(page);
|
|
56656
|
+
if (list) list.push(f);
|
|
56657
|
+
else byPage.set(page, [f]);
|
|
56658
|
+
}
|
|
56659
|
+
for (const [pageId, findings] of [...byPage].sort((a, b) => a[0].localeCompare(b[0]))) {
|
|
56660
|
+
const tally = { located: findings.length, imaged: 0, skipped: {} };
|
|
56661
|
+
manifest.perPage.set(pageId, tally);
|
|
56662
|
+
manifest.totals.located += findings.length;
|
|
56663
|
+
const ctx = loadPageEvidence(root, pageId);
|
|
56664
|
+
if ("skip" in ctx) {
|
|
56665
|
+
for (const f of findings) {
|
|
56666
|
+
manifest.skipped.set(findingId(f), ctx.skip);
|
|
56667
|
+
bump(tally, ctx.skip);
|
|
56668
|
+
bump(manifest.totals, ctx.skip);
|
|
56669
|
+
}
|
|
56670
|
+
continue;
|
|
56671
|
+
}
|
|
56672
|
+
const dir = join40(opts.outDir, "assets", pageId);
|
|
56673
|
+
let wrote = false;
|
|
56674
|
+
const perRule = /* @__PURE__ */ new Map();
|
|
56675
|
+
const seen = /* @__PURE__ */ new Set();
|
|
56676
|
+
let onPage = 0;
|
|
56677
|
+
for (const f of findings) {
|
|
56678
|
+
const id = findingId(f);
|
|
56679
|
+
const key = `${f.ruleId}\0${f.selectorHint}`;
|
|
56680
|
+
const ruleCount = perRule.get(f.ruleId) ?? 0;
|
|
56681
|
+
if (seen.has(key) || ruleCount >= caps.perRule || onPage >= caps.perPage || manifest.crops.size >= caps.total) {
|
|
56682
|
+
manifest.skipped.set(id, "capped");
|
|
56683
|
+
bump(tally, "capped");
|
|
56684
|
+
bump(manifest.totals, "capped");
|
|
56685
|
+
continue;
|
|
56686
|
+
}
|
|
56687
|
+
const crop = cropFinding(ctx, f, opts.geometry);
|
|
56688
|
+
if ("skip" in crop) {
|
|
56689
|
+
manifest.skipped.set(id, crop.skip);
|
|
56690
|
+
bump(tally, crop.skip);
|
|
56691
|
+
bump(manifest.totals, crop.skip);
|
|
56692
|
+
continue;
|
|
56693
|
+
}
|
|
56694
|
+
const name2 = cropName(f);
|
|
56695
|
+
const path = join40(dir, name2);
|
|
56696
|
+
try {
|
|
56697
|
+
if (!wrote) {
|
|
56698
|
+
mkdirSync13(dir, { recursive: true });
|
|
56699
|
+
wrote = true;
|
|
56700
|
+
}
|
|
56701
|
+
writeFileSync15(path, crop.png);
|
|
56702
|
+
} catch {
|
|
56703
|
+
manifest.skipped.set(id, "no-screenshot");
|
|
56704
|
+
bump(tally, "no-screenshot");
|
|
56705
|
+
bump(manifest.totals, "no-screenshot");
|
|
56706
|
+
continue;
|
|
56707
|
+
}
|
|
56708
|
+
seen.add(key);
|
|
56709
|
+
perRule.set(f.ruleId, ruleCount + 1);
|
|
56710
|
+
onPage++;
|
|
56711
|
+
tally.imaged++;
|
|
56712
|
+
manifest.totals.imaged++;
|
|
56713
|
+
manifest.crops.set(id, {
|
|
56714
|
+
findingId: id,
|
|
56715
|
+
page: pageId,
|
|
56716
|
+
ruleId: f.ruleId,
|
|
56717
|
+
criteriaId: f.criteriaId,
|
|
56718
|
+
href: `./assets/${pageId}/${name2}`,
|
|
56719
|
+
path,
|
|
56720
|
+
width: crop.width,
|
|
56721
|
+
height: crop.height,
|
|
56722
|
+
box: crop.box,
|
|
56723
|
+
scale: crop.scale,
|
|
56724
|
+
clipped: crop.clipped,
|
|
56725
|
+
alt: { fr: S2.fr.alt(f.selectorHint, pageId), en: S2.en.alt(f.selectorHint, pageId) }
|
|
56726
|
+
});
|
|
56727
|
+
}
|
|
56728
|
+
}
|
|
56729
|
+
return manifest;
|
|
56730
|
+
}
|
|
56731
|
+
|
|
56732
|
+
// src/html-emit.ts
|
|
56733
|
+
import { mkdirSync as mkdirSync14, readFileSync as readFileSync24, statSync as statSync11, writeFileSync as writeFileSync16 } from "fs";
|
|
56734
|
+
import { join as join41 } from "path";
|
|
56735
|
+
|
|
56736
|
+
// src/html.ts
|
|
56737
|
+
var MARK4 = { C: "C", NC: "NC", NA: "\u2014", manual: "?" };
|
|
56738
|
+
var CLASS = { C: "c", NC: "nc", NA: "na", manual: "m" };
|
|
56739
|
+
var T3 = {
|
|
56740
|
+
fr: {
|
|
56741
|
+
skip: "Aller au contenu",
|
|
56742
|
+
nav: "Documents du rapport",
|
|
56743
|
+
printHint: "Pour un PDF : imprimez cette page (Ctrl/Cmd + P) et choisissez \xAB Enregistrer au format PDF \xBB.",
|
|
56744
|
+
generated: "G\xE9n\xE9r\xE9 par ultra11y"
|
|
56745
|
+
},
|
|
56746
|
+
en: {
|
|
56747
|
+
skip: "Skip to content",
|
|
56748
|
+
nav: "Report documents",
|
|
56749
|
+
printHint: "For a PDF: print this page (Ctrl/Cmd + P) and choose \u201CSave as PDF\u201D.",
|
|
56750
|
+
generated: "Generated by ultra11y"
|
|
56751
|
+
}
|
|
56752
|
+
};
|
|
56753
|
+
function esc2(s) {
|
|
56754
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
56755
|
+
}
|
|
56756
|
+
var STYLE = `
|
|
56757
|
+
:root { color-scheme: light dark; --bg:#fff; --fg:#111; --line:#e3e3e6; --mut:#666; --soft:#f6f6f8;
|
|
56758
|
+
--c:#0a7d33; --nc:#b3261e; --m:#b06000; --accent:#0b5fa5; }
|
|
56759
|
+
@media (prefers-color-scheme: dark) { :root { --bg:#141416; --fg:#f2f2f7; --line:#2c2c2e; --mut:#a1a1a6; --soft:#1c1c1f;
|
|
56760
|
+
--c:#3fbf6f; --nc:#ff6b5e; --m:#d99a2b; --accent:#6cb6ff; } }
|
|
56761
|
+
* { box-sizing:border-box; }
|
|
56762
|
+
body { margin:0; background:var(--bg); color:var(--fg); font:15px/1.6 ui-sans-serif,system-ui,-apple-system,sans-serif; }
|
|
56763
|
+
.skip { position:absolute; left:-9999px; top:0; padding:8px 14px; background:var(--bg); color:var(--fg); border:2px solid var(--accent); z-index:9; }
|
|
56764
|
+
.skip:focus { left:8px; top:8px; }
|
|
56765
|
+
.wrap { max-width:78ch; margin:0 auto; padding:32px 20px 64px; }
|
|
56766
|
+
header.top { border-bottom:1px solid var(--line); }
|
|
56767
|
+
h1 { font-size:26px; line-height:1.25; margin:0 0 6px; }
|
|
56768
|
+
h2 { font-size:20px; margin:36px 0 10px; padding-top:8px; border-top:1px solid var(--line); }
|
|
56769
|
+
h3 { font-size:17px; margin:26px 0 8px; }
|
|
56770
|
+
h4 { font-size:15px; margin:20px 0 6px; }
|
|
56771
|
+
p { margin:0 0 12px; } p.sub { color:var(--mut); margin:0 0 4px; }
|
|
56772
|
+
a { color:var(--accent); }
|
|
56773
|
+
a:focus-visible, summary:focus-visible { outline:3px solid var(--accent); outline-offset:2px; }
|
|
56774
|
+
nav.docs ul { list-style:none; display:flex; flex-wrap:wrap; gap:6px 16px; margin:12px 0 0; padding:0; }
|
|
56775
|
+
nav.docs [aria-current="page"] { font-weight:700; }
|
|
56776
|
+
code, .mono { font:13px/1.5 ui-monospace,SFMono-Regular,Menlo,monospace; overflow-wrap:anywhere; }
|
|
56777
|
+
ul.body { margin:0 0 12px; padding-left:22px; }
|
|
56778
|
+
.note { margin:0 0 14px; padding:10px 14px; border-left:4px solid var(--mut); background:var(--soft); }
|
|
56779
|
+
.note.warn { border-left-color:var(--m); }
|
|
56780
|
+
.tw { overflow-x:auto; margin:0 0 18px; }
|
|
56781
|
+
table { border-collapse:collapse; width:100%; font-size:14px; }
|
|
56782
|
+
caption { text-align:left; font-weight:600; padding:0 0 8px; }
|
|
56783
|
+
th, td { padding:6px 10px; border-bottom:1px solid var(--line); text-align:left; vertical-align:top; }
|
|
56784
|
+
thead th { border-bottom:2px solid var(--line); background:var(--soft); }
|
|
56785
|
+
td.end, th.end { text-align:right; }
|
|
56786
|
+
tr.group th { background:var(--soft); font-weight:700; }
|
|
56787
|
+
.st { font-weight:700; }
|
|
56788
|
+
.st-c{color:var(--c)} .st-nc{color:var(--nc)} .st-na{color:var(--mut)} .st-m{color:var(--m)}
|
|
56789
|
+
figure { margin:0 0 18px; }
|
|
56790
|
+
figure img { max-width:100%; height:auto; border:1px solid var(--line); }
|
|
56791
|
+
figcaption { color:var(--mut); font-size:13px; margin-top:6px; }
|
|
56792
|
+
details { margin:0 0 14px; border:1px solid var(--line); padding:8px 12px; }
|
|
56793
|
+
summary { cursor:pointer; font-weight:600; }
|
|
56794
|
+
footer.bot { margin-top:48px; padding-top:12px; border-top:1px solid var(--line); color:var(--mut); font-size:13px; }
|
|
56795
|
+
@media print {
|
|
56796
|
+
:root { --bg:#fff; --fg:#000; --line:#bbb; --mut:#444; --soft:#f2f2f2; --c:#0a7d33; --nc:#b3261e; --m:#8a4b00; --accent:#00449e; }
|
|
56797
|
+
.skip, nav.docs { display:none; }
|
|
56798
|
+
.wrap { max-width:none; padding:0; }
|
|
56799
|
+
body { font-size:11pt; }
|
|
56800
|
+
/* One criterion per block, never split across a page break \u2014 the whole point of a
|
|
56801
|
+
printable deliverable is that a finding and its evidence stay on the same sheet. */
|
|
56802
|
+
h2, h3 { break-after:avoid-page; page-break-after:avoid; }
|
|
56803
|
+
figure, table, .note, details { break-inside:avoid-page; page-break-inside:avoid; }
|
|
56804
|
+
details { border:0; padding:0; } details > summary { list-style:none; }
|
|
56805
|
+
/* A collapsed section would print as a title with nothing under it. */
|
|
56806
|
+
details[open], details:not([open]) > *:not(summary) { display:revert; }
|
|
56807
|
+
figure img { max-width:16cm; }
|
|
56808
|
+
a { color:inherit; text-decoration:underline; }
|
|
56809
|
+
}`;
|
|
56810
|
+
function runHtml(r) {
|
|
56811
|
+
let inner = esc2(r.text);
|
|
56812
|
+
if (r.mono) inner = `<code>${inner}</code>`;
|
|
56813
|
+
if (r.strong) inner = `<strong>${inner}</strong>`;
|
|
56814
|
+
if (r.em) inner = `<em>${inner}</em>`;
|
|
56815
|
+
return r.href ? `<a href="${esc2(r.href)}">${inner}</a>` : inner;
|
|
56816
|
+
}
|
|
56817
|
+
var runsHtml = (runs) => runs.map(runHtml).join("");
|
|
56818
|
+
function cellHtml(c2, tag) {
|
|
56819
|
+
const attrs = [];
|
|
56820
|
+
if (tag === "th") attrs.push('scope="row"');
|
|
56821
|
+
if (c2.align === "end") attrs.push('class="end"');
|
|
56822
|
+
if (c2.colspan && c2.colspan > 1) attrs.push(`colspan="${c2.colspan}"`);
|
|
56823
|
+
let inner;
|
|
56824
|
+
if (c2.status) {
|
|
56825
|
+
inner = `<span class="st st-${CLASS[c2.status]}">${esc2(MARK4[c2.status])}</span>`;
|
|
56826
|
+
} else {
|
|
56827
|
+
inner = c2.text === "" ? "\u2014" : runHtml({ text: c2.text, mono: c2.mono, strong: c2.strong, ...c2.href ? { href: c2.href } : {} });
|
|
56828
|
+
}
|
|
56829
|
+
return `<${tag}${attrs.length ? ` ${attrs.join(" ")}` : ""}>${inner}</${tag}>`;
|
|
56830
|
+
}
|
|
56831
|
+
function blockHtml(b) {
|
|
56832
|
+
switch (b.kind) {
|
|
56833
|
+
case "heading":
|
|
56834
|
+
return [`<h${b.level}${b.id ? ` id="${esc2(b.id)}"` : ""}>${esc2(b.text)}</h${b.level}>`];
|
|
56835
|
+
case "para":
|
|
56836
|
+
return [`<p>${runsHtml(b.runs)}</p>`];
|
|
56837
|
+
case "note":
|
|
56838
|
+
return [`<div class="note${b.tone === "warn" ? " warn" : ""}">${runsHtml(b.runs)}</div>`];
|
|
56839
|
+
case "list":
|
|
56840
|
+
return [`<ul class="body">${b.items.map((i2) => `<li>${runsHtml(i2)}</li>`).join("")}</ul>`];
|
|
56841
|
+
case "figure":
|
|
56842
|
+
return [`<figure><img src="${esc2(b.src)}" alt="${esc2(b.alt)}">${b.caption ? `<figcaption>${esc2(b.caption)}</figcaption>` : ""}</figure>`];
|
|
56843
|
+
case "details":
|
|
56844
|
+
return [`<details open><summary>${esc2(b.summary)}</summary>`, ...b.blocks.flatMap(blockHtml), "</details>"];
|
|
56845
|
+
case "table": {
|
|
56846
|
+
const out2 = ['<div class="tw">', "<table>", `<caption>${esc2(b.caption)}</caption>`, "<thead><tr>"];
|
|
56847
|
+
for (const col of b.columns) out2.push(`<th scope="col"${col.align === "end" ? ' class="end"' : ""}>${esc2(col.text)}</th>`);
|
|
56848
|
+
out2.push("</tr></thead><tbody>");
|
|
56849
|
+
for (const row of b.rows) {
|
|
56850
|
+
const group = row.length === 1 && (row[0]?.colspan ?? 0) > 1;
|
|
56851
|
+
if (group) {
|
|
56852
|
+
const c2 = row[0];
|
|
56853
|
+
out2.push(`<tr class="group"><th scope="colgroup" colspan="${c2.colspan}">${esc2(c2.text)}</th></tr>`);
|
|
56854
|
+
continue;
|
|
56855
|
+
}
|
|
56856
|
+
out2.push(`<tr>${row.map((c2, i2) => cellHtml(c2, i2 === 0 || c2.header ? "th" : "td")).join("")}</tr>`);
|
|
56857
|
+
}
|
|
56858
|
+
out2.push("</tbody></table>", "</div>");
|
|
56859
|
+
return out2;
|
|
56860
|
+
}
|
|
56861
|
+
}
|
|
56862
|
+
}
|
|
56863
|
+
function renderHtmlDocument(doc) {
|
|
56864
|
+
const t2 = T3[doc.lang];
|
|
56865
|
+
const out2 = [];
|
|
56866
|
+
out2.push(`<!doctype html>`, `<html lang="${doc.lang}">`, "<head>");
|
|
56867
|
+
out2.push('<meta charset="utf-8">', '<meta name="viewport" content="width=device-width, initial-scale=1">');
|
|
56868
|
+
out2.push(`<title>${esc2(doc.title)}</title>`);
|
|
56869
|
+
out2.push(`<style>${STYLE}</style>`);
|
|
56870
|
+
out2.push("</head>", "<body>");
|
|
56871
|
+
out2.push(`<a class="skip" href="#content">${esc2(t2.skip)}</a>`);
|
|
56872
|
+
out2.push('<header class="top"><div class="wrap">');
|
|
56873
|
+
out2.push(`<h1>${esc2(doc.title)}</h1>`);
|
|
56874
|
+
if (doc.subtitle?.length) out2.push(`<p class="sub">${runsHtml(doc.subtitle)}</p>`);
|
|
56875
|
+
if (doc.nav?.length) {
|
|
56876
|
+
out2.push(`<nav class="docs" aria-label="${esc2(t2.nav)}"><ul>`);
|
|
56877
|
+
for (const l of doc.nav) out2.push(`<li><a href="${esc2(l.href)}"${l.current ? ' aria-current="page"' : ""}>${esc2(l.text)}</a></li>`);
|
|
56878
|
+
out2.push("</ul></nav>");
|
|
56879
|
+
}
|
|
56880
|
+
out2.push("</div></header>");
|
|
56881
|
+
out2.push('<main id="content" class="wrap">');
|
|
56882
|
+
for (const b of doc.blocks) out2.push(...blockHtml(b));
|
|
56883
|
+
out2.push("</main>");
|
|
56884
|
+
out2.push(`<footer class="bot"><div class="wrap"><p>${esc2(t2.generated)} \xB7 ${esc2(t2.printHint)}</p></div></footer>`);
|
|
56885
|
+
out2.push("</body>", "</html>");
|
|
56886
|
+
return `${out2.join("\n")}
|
|
56887
|
+
`;
|
|
56888
|
+
}
|
|
56889
|
+
|
|
56890
|
+
// src/html-report.ts
|
|
56891
|
+
var T4 = {
|
|
56892
|
+
fr: {
|
|
56893
|
+
reportTitle: "Rapport de conformit\xE9",
|
|
56894
|
+
indexTitle: "Rapport d'accessibilit\xE9",
|
|
56895
|
+
pagesTitle: "Rapport page par page",
|
|
56896
|
+
date: "Date",
|
|
56897
|
+
files: "fichiers",
|
|
56898
|
+
rate: "r\xE9ussite automatique",
|
|
56899
|
+
synthesis: "Synth\xE8se",
|
|
56900
|
+
synthCaption: (h) => `Nombre de crit\xE8res par statut, regroup\xE9s par ${h}.`,
|
|
56901
|
+
byGuideline: "r\xE8gle WCAG",
|
|
56902
|
+
byTheme: "th\xE9matique",
|
|
56903
|
+
total: "Total",
|
|
56904
|
+
conforming: "C",
|
|
56905
|
+
nonConforming: "NC",
|
|
56906
|
+
notApplicable: "NA",
|
|
56907
|
+
toAssess: "\xC0 \xE9valuer",
|
|
56908
|
+
group: "Groupe",
|
|
56909
|
+
coverage: (d, t2) => `Couverture : ${d}/${t2} crit\xE8re(s) d\xE9cid\xE9(s). Le taux ne porte que sur eux et ne dit rien des ${t2 - d} autres.`,
|
|
56910
|
+
ncTitle: "Non-conformit\xE9s",
|
|
56911
|
+
ncCaption: "Une entr\xE9e par crit\xE8re non conforme.",
|
|
56912
|
+
noNc: "Aucune non-conformit\xE9 relev\xE9e par le moteur statique. Les crit\xE8res \xAB \xE0 \xE9valuer \xBB restent \xE0 trancher.",
|
|
56913
|
+
recTitle: "Recommandations (non normatives)",
|
|
56914
|
+
recNote: "Bonnes pratiques sans test normatif en \xE9chec : elles ne rendent aucun crit\xE8re non conforme et n'entrent pas dans le taux.",
|
|
56915
|
+
occurrences: "Occurrences",
|
|
56916
|
+
occCaption: (id) => `Occurrences du crit\xE8re ${id}.`,
|
|
56917
|
+
where: "Emplacement",
|
|
56918
|
+
selector: "S\xE9lecteur",
|
|
56919
|
+
what: "Constat",
|
|
56920
|
+
evidence: "Preuve",
|
|
56921
|
+
perPage: "Bilan page par page",
|
|
56922
|
+
perPageCaption: "Taux et constats par page, avec la base sur laquelle chaque page a \xE9t\xE9 jug\xE9e.",
|
|
56923
|
+
page: "Page",
|
|
56924
|
+
url: "URL",
|
|
56925
|
+
basis: "Base",
|
|
56926
|
+
pageRate: "Taux",
|
|
56927
|
+
sheet: "Fiche",
|
|
56928
|
+
grid: "Grille des crit\xE8res",
|
|
56929
|
+
gridCaption: (n) => `Statut de chaque crit\xE8re pour la page ${n}.`,
|
|
56930
|
+
crossGridCaption: "Statut de chaque crit\xE8re, page par page.",
|
|
56931
|
+
criterion: "Crit\xE8re",
|
|
56932
|
+
status: "Statut",
|
|
56933
|
+
legend: "`C` conforme \xB7 `NC` non conforme \xB7 `\u2014` non applicable \xB7 `?` \xE0 \xE9valuer.",
|
|
56934
|
+
tally: (c2, nc, na, m) => `${c2} conforme(s) \xB7 ${nc} non conforme(s) \xB7 ${na} non applicable(s) \xB7 ${m} \xE0 \xE9valuer`,
|
|
56935
|
+
manualWarn: "Un crit\xE8re \xAB \xE0 \xE9valuer \xBB n'est ni conforme ni non conforme : personne ne l'a encore tranch\xE9. Les crit\xE8res de jugement s'adjugent avec `verify --manual`, ceux \xAB \xE0 restituer \xBB avec `scan`.",
|
|
56936
|
+
documents: "Documents",
|
|
56937
|
+
openComposite: "Rapport complet, en un seul fichier (imprimable en PDF)",
|
|
56938
|
+
openPages: "Rapport page par page",
|
|
56939
|
+
noScreenshot: "Aucune capture d'\xE9cran pour cette page.",
|
|
56940
|
+
screenshotAlt: (n) => `Capture d'\xE9cran de la page ${n}`,
|
|
56941
|
+
notImaged: (n) => `${n} occurrence(s) n'ont pas pu \xEAtre illustr\xE9es.`
|
|
56942
|
+
},
|
|
56943
|
+
en: {
|
|
56944
|
+
reportTitle: "Conformance report",
|
|
56945
|
+
indexTitle: "Accessibility report",
|
|
56946
|
+
pagesTitle: "Page-by-page report",
|
|
56947
|
+
date: "Date",
|
|
56948
|
+
files: "files",
|
|
56949
|
+
rate: "automatic pass rate",
|
|
56950
|
+
synthesis: "Synthesis",
|
|
56951
|
+
synthCaption: (h) => `Criteria count per status, grouped by ${h}.`,
|
|
56952
|
+
byGuideline: "WCAG guideline",
|
|
56953
|
+
byTheme: "theme",
|
|
56954
|
+
total: "Total",
|
|
56955
|
+
conforming: "C",
|
|
56956
|
+
nonConforming: "NC",
|
|
56957
|
+
notApplicable: "NA",
|
|
56958
|
+
toAssess: "To assess",
|
|
56959
|
+
group: "Group",
|
|
56960
|
+
coverage: (d, t2) => `Coverage: ${d}/${t2} criteria decided. The rate covers only those and says nothing about the other ${t2 - d}.`,
|
|
56961
|
+
ncTitle: "Non-conformities",
|
|
56962
|
+
ncCaption: "One entry per non-conforming criterion.",
|
|
56963
|
+
noNc: "No non-conformity found by the static engine. The criteria left to assess are still open.",
|
|
56964
|
+
recTitle: "Recommendations (non-normative)",
|
|
56965
|
+
recNote: "Good practices with no failing normative test: they never make a criterion non-conforming and do not enter the rate.",
|
|
56966
|
+
occurrences: "Occurrences",
|
|
56967
|
+
occCaption: (id) => `Occurrences of criterion ${id}.`,
|
|
56968
|
+
where: "Location",
|
|
56969
|
+
selector: "Selector",
|
|
56970
|
+
what: "Finding",
|
|
56971
|
+
evidence: "Evidence",
|
|
56972
|
+
perPage: "Page-by-page scoreboard",
|
|
56973
|
+
perPageCaption: "Rate and findings per page, with the basis each page was judged on.",
|
|
56974
|
+
page: "Page",
|
|
56975
|
+
url: "URL",
|
|
56976
|
+
basis: "Basis",
|
|
56977
|
+
pageRate: "Rate",
|
|
56978
|
+
sheet: "Sheet",
|
|
56979
|
+
grid: "Criteria grid",
|
|
56980
|
+
gridCaption: (n) => `Each criterion's status for the ${n} page.`,
|
|
56981
|
+
crossGridCaption: "Each criterion's status, page by page.",
|
|
56982
|
+
criterion: "Criterion",
|
|
56983
|
+
status: "Status",
|
|
56984
|
+
legend: "`C` conforming \xB7 `NC` non-conforming \xB7 `\u2014` not applicable \xB7 `?` to assess.",
|
|
56985
|
+
tally: (c2, nc, na, m) => `${c2} conforming \xB7 ${nc} non-conforming \xB7 ${na} not applicable \xB7 ${m} to assess`,
|
|
56986
|
+
manualWarn: 'A criterion "to assess" is neither conforming nor non-conforming: nobody has ruled on it yet. Judgment criteria are adjudicated with `verify --manual`, rendering ones with `scan`.',
|
|
56987
|
+
documents: "Documents",
|
|
56988
|
+
openComposite: "Full report, in a single file (printable to PDF)",
|
|
56989
|
+
openPages: "Page-by-page report",
|
|
56990
|
+
noScreenshot: "No screenshot for this page.",
|
|
56991
|
+
screenshotAlt: (n) => `Screenshot of the ${n} page`,
|
|
56992
|
+
notImaged: (n) => `${n} occurrence(s) could not be illustrated.`
|
|
56993
|
+
}
|
|
56994
|
+
};
|
|
56995
|
+
function ticks(text) {
|
|
56996
|
+
return text.split(/`([^`]+)`/).flatMap((part, i2) => part === "" ? [] : i2 % 2 === 1 ? [{ text: part, mono: true }] : [{ text: part }]);
|
|
56997
|
+
}
|
|
56998
|
+
var stdName = (standard) => isCore(standard) ? "WCAG 2.2 AA" : loadPack(standard).name;
|
|
56999
|
+
function headline(result, standard, lang) {
|
|
57000
|
+
const t2 = T4[lang];
|
|
57001
|
+
const groups = isCore(standard) ? reportGroups(result, lang) : packReportGroups(result, loadPack(standard), lang);
|
|
57002
|
+
const { decided, total } = reportCoverage(groups);
|
|
57003
|
+
const agentRuled = groups.some((g) => g.rows.some((r) => r.decidedBy === "agent" && r.status === "C"));
|
|
57004
|
+
return {
|
|
57005
|
+
runs: [
|
|
57006
|
+
{ text: result.date, mono: true },
|
|
57007
|
+
{ text: ` \xB7 ${result.scope.files} ${t2.files} \xB7 ` },
|
|
57008
|
+
{ text: `${formatRate(decided === 0 ? null : result.conformancePct, decided, total)}${agentRuled ? "*" : ""}`, strong: true },
|
|
57009
|
+
{ text: ` ${t2.rate}` }
|
|
57010
|
+
],
|
|
57011
|
+
agentRuled,
|
|
57012
|
+
decided,
|
|
57013
|
+
total
|
|
57014
|
+
};
|
|
57015
|
+
}
|
|
57016
|
+
function synthesisBlocks(result, standard, lang) {
|
|
57017
|
+
const t2 = T4[lang];
|
|
57018
|
+
const core = isCore(standard);
|
|
57019
|
+
const groups = core ? reportGroups(result, lang) : packReportGroups(result, loadPack(standard), lang);
|
|
57020
|
+
const tot = reportTotals(groups);
|
|
57021
|
+
const { decided, total } = reportCoverage(groups);
|
|
57022
|
+
const rows = groups.map((g) => {
|
|
57023
|
+
const x = tallyRows(g.rows);
|
|
57024
|
+
return [
|
|
57025
|
+
{ text: `${g.key} ${g.title}`.trim() },
|
|
57026
|
+
{ text: String(x.c), align: "end" },
|
|
57027
|
+
{ text: String(x.nc), align: "end" },
|
|
57028
|
+
{ text: String(x.na), align: "end" },
|
|
57029
|
+
{ text: String(x.manual), align: "end" }
|
|
57030
|
+
];
|
|
57031
|
+
});
|
|
57032
|
+
rows.push([
|
|
57033
|
+
{ text: t2.total, strong: true },
|
|
57034
|
+
{ text: String(tot.c), align: "end", strong: true },
|
|
57035
|
+
{ text: String(tot.nc), align: "end", strong: true },
|
|
57036
|
+
{ text: String(tot.na), align: "end", strong: true },
|
|
57037
|
+
{ text: String(tot.manual), align: "end", strong: true }
|
|
57038
|
+
]);
|
|
57039
|
+
return [
|
|
57040
|
+
{ kind: "heading", level: 2, text: t2.synthesis, id: "synthesis" },
|
|
57041
|
+
{
|
|
57042
|
+
kind: "table",
|
|
57043
|
+
caption: t2.synthCaption(core ? t2.byGuideline : t2.byTheme),
|
|
57044
|
+
columns: [
|
|
57045
|
+
{ text: t2.group },
|
|
57046
|
+
{ text: t2.conforming, align: "end" },
|
|
57047
|
+
{ text: t2.nonConforming, align: "end" },
|
|
57048
|
+
{ text: t2.notApplicable, align: "end" },
|
|
57049
|
+
{ text: t2.toAssess, align: "end" }
|
|
57050
|
+
],
|
|
57051
|
+
rows
|
|
57052
|
+
},
|
|
57053
|
+
{ kind: "note", tone: "warn", runs: ticks(t2.coverage(decided, total)) },
|
|
57054
|
+
{ kind: "note", tone: "warn", runs: ticks(t2.manualWarn) }
|
|
57055
|
+
];
|
|
57056
|
+
}
|
|
57057
|
+
function criterionBlocks(unit, standard, lang, level, crops) {
|
|
57058
|
+
const t2 = T4[lang];
|
|
57059
|
+
const m = auditorUnitModel(unit, standard, lang, { collapse: true });
|
|
57060
|
+
const out2 = [{ kind: "heading", level, text: `${m.icon} ${m.label}`, id: `c-${unit.criteriaId}` }];
|
|
57061
|
+
out2.push({ kind: "note", tone: "info", runs: ticks(m.normativeNote) });
|
|
57062
|
+
out2.push({ kind: "list", items: m.fields.map((f) => [{ text: `${f.label} : `, strong: true }, ...ticks(f.value)]) });
|
|
57063
|
+
out2.push({
|
|
57064
|
+
kind: "para",
|
|
57065
|
+
runs: [{ text: `${t2.what} (${m.conformanceTerms.nonConformant}) : `, strong: true }, { text: `${m.occurrences} \u2014 ${m.messages.join(" ; ")}` }]
|
|
57066
|
+
});
|
|
57067
|
+
if (m.fixes.length) out2.push({ kind: "para", runs: [{ text: `${m.conformanceTerms.conformant} : `, strong: true }, { text: m.fixes.join(" ; ") }] });
|
|
57068
|
+
const hasEvidence = crops ? m.normative.some((f) => crops(f)) : false;
|
|
57069
|
+
const columns = [{ text: t2.where }, { text: t2.selector }, { text: t2.what }, ...hasEvidence ? [{ text: t2.evidence }] : []];
|
|
57070
|
+
const rows = m.normative.map((f) => {
|
|
57071
|
+
const cells = [{ text: `${f.file}:${f.line}`, mono: true }, { text: f.selectorHint, mono: true }, { text: resolveOccurrence(f, lang) }];
|
|
57072
|
+
if (hasEvidence) cells.push({ text: crops?.(f) ? "\u25A3" : "" });
|
|
57073
|
+
return cells;
|
|
57074
|
+
});
|
|
57075
|
+
if (rows.length) out2.push({ kind: "table", caption: t2.occCaption(unit.criteriaId), columns, rows });
|
|
57076
|
+
if (crops) {
|
|
57077
|
+
for (const f of m.normative) {
|
|
57078
|
+
const c2 = crops(f);
|
|
57079
|
+
if (c2) out2.push({ kind: "figure", src: c2.href, alt: c2.alt, caption: `${f.file}:${f.line} \u2014 ${f.selectorHint}` });
|
|
57080
|
+
}
|
|
57081
|
+
}
|
|
57082
|
+
return out2;
|
|
57083
|
+
}
|
|
57084
|
+
function resolveOccurrence(f, lang) {
|
|
57085
|
+
return f.msg ? auditorUnitModel({ criteriaId: f.criteriaId, title: "", label: "", refs: [], severity: f.severity, findings: [f] }, CORE2, lang).messages[0] ?? f.message : f.message;
|
|
57086
|
+
}
|
|
57087
|
+
function findingsBlocks(result, standard, lang, level, crops) {
|
|
57088
|
+
const t2 = T4[lang];
|
|
57089
|
+
const { nc, advisory } = partitionUnits(prdUnits(result, standard, lang));
|
|
57090
|
+
const out2 = [{ kind: "heading", level: 2, text: t2.ncTitle, id: "nc" }];
|
|
57091
|
+
if (!nc.length) out2.push({ kind: "para", runs: [{ text: t2.noNc }] });
|
|
57092
|
+
for (const u of nc) out2.push(...criterionBlocks(u, standard, lang, level === 2 ? 3 : 4, crops));
|
|
57093
|
+
if (advisory.length) {
|
|
57094
|
+
out2.push({ kind: "heading", level: 2, text: t2.recTitle, id: "rec" });
|
|
57095
|
+
out2.push({ kind: "note", tone: "info", runs: ticks(t2.recNote) });
|
|
57096
|
+
for (const u of advisory) out2.push(...criterionBlocks(u, standard, lang, level === 2 ? 3 : 4, crops));
|
|
57097
|
+
}
|
|
57098
|
+
return out2;
|
|
57099
|
+
}
|
|
57100
|
+
function scoreboardBlocks(result, standard, lang, sheetHref) {
|
|
57101
|
+
const t2 = T4[lang];
|
|
57102
|
+
const scope = pagesOf(result);
|
|
57103
|
+
if (!scope.length) return [];
|
|
57104
|
+
const derived = derivePages(result, scope);
|
|
57105
|
+
const rows = derived.map((p) => [
|
|
57106
|
+
{ text: `${p.name}${p.auth ? " \u{1F512}" : ""}`, ...sheetHref ? { href: sheetHref(p.id) } : {} },
|
|
57107
|
+
{ text: p.url, mono: true },
|
|
57108
|
+
{ text: basisLabel(p.basis, lang) },
|
|
57109
|
+
{ text: formatRate(p.conformancePct, p.decided, p.total), align: "end" }
|
|
57110
|
+
]);
|
|
57111
|
+
const out2 = [
|
|
57112
|
+
{ kind: "heading", level: 2, text: t2.perPage, id: "pages" },
|
|
57113
|
+
{
|
|
57114
|
+
kind: "table",
|
|
57115
|
+
caption: t2.perPageCaption,
|
|
57116
|
+
columns: [{ text: t2.page }, { text: t2.url }, { text: t2.basis }, { text: t2.pageRate, align: "end" }],
|
|
57117
|
+
rows
|
|
57118
|
+
}
|
|
57119
|
+
];
|
|
57120
|
+
const orphans = unattributedFindings(result).filter((f) => !f.advisory).length;
|
|
57121
|
+
if (orphans) out2.push({ kind: "note", tone: "warn", runs: ticks(unattributedNote(orphans, lang)) });
|
|
57122
|
+
for (const basis of ["attributed", "not-audited"]) {
|
|
57123
|
+
const note = derived.some((p) => p.basis === basis) ? pageBasisWarning(basis, lang) : void 0;
|
|
57124
|
+
if (note) out2.push({ kind: "note", tone: "warn", runs: ticks(note) });
|
|
57125
|
+
}
|
|
57126
|
+
return out2;
|
|
57127
|
+
}
|
|
57128
|
+
function crossGridBlocks(result, derived, standard, lang) {
|
|
57129
|
+
const t2 = T4[lang];
|
|
57130
|
+
if (!derived.length) return [];
|
|
57131
|
+
const { rows, status } = pageGridModel(result, derived, standard, lang);
|
|
57132
|
+
if (!rows.length) return [];
|
|
57133
|
+
const table = {
|
|
57134
|
+
kind: "table",
|
|
57135
|
+
caption: t2.crossGridCaption,
|
|
57136
|
+
columns: [{ text: t2.criterion }, ...derived.map((p) => ({ text: `${p.name}${p.auth ? " \u{1F512}" : ""}` }))],
|
|
57137
|
+
rows: []
|
|
57138
|
+
};
|
|
57139
|
+
let group = "";
|
|
57140
|
+
for (const row of rows) {
|
|
57141
|
+
if (row.group !== group) {
|
|
57142
|
+
group = row.group;
|
|
57143
|
+
table.rows.push([{ text: group, colspan: derived.length + 1 }]);
|
|
57144
|
+
}
|
|
57145
|
+
table.rows.push([{ text: row.label }, ...derived.map((p) => ({ status: status.get(row.id)?.get(p.id) ?? "manual", text: "" }))]);
|
|
57146
|
+
}
|
|
57147
|
+
return [{ kind: "heading", level: 2, text: t2.grid, id: "grid" }, { kind: "note", tone: "info", runs: ticks(t2.legend) }, table];
|
|
57148
|
+
}
|
|
57149
|
+
function pageGridBlocks(result, page, standard, lang) {
|
|
57150
|
+
const t2 = T4[lang];
|
|
57151
|
+
const rows = pageCriterionRows(result, page, standard, lang);
|
|
57152
|
+
if (!rows.length) return [];
|
|
57153
|
+
const cov = pageCoverage(rows);
|
|
57154
|
+
const tally = pageTally(rows);
|
|
57155
|
+
const table = { kind: "table", caption: t2.gridCaption(page.name), columns: [{ text: t2.criterion }, { text: t2.status }], rows: [] };
|
|
57156
|
+
let group = "";
|
|
57157
|
+
for (const row of rows) {
|
|
57158
|
+
if (row.group !== group) {
|
|
57159
|
+
group = row.group;
|
|
57160
|
+
table.rows.push([{ text: group, colspan: 2 }]);
|
|
57161
|
+
}
|
|
57162
|
+
table.rows.push([{ text: row.label }, { status: row.status, text: row.decidedBy === "agent" && row.status === "C" ? "*" : "" }]);
|
|
57163
|
+
}
|
|
57164
|
+
const out2 = [
|
|
57165
|
+
{ kind: "heading", level: 2, text: t2.grid },
|
|
57166
|
+
{
|
|
57167
|
+
kind: "para",
|
|
57168
|
+
runs: [
|
|
57169
|
+
{ text: `${t2.pageRate} : `, strong: true },
|
|
57170
|
+
{ text: formatRate(pageRatePct(rows), cov.decided, cov.total), strong: true }
|
|
57171
|
+
]
|
|
57172
|
+
},
|
|
57173
|
+
{ kind: "para", runs: [{ text: t2.tally(tally.c, tally.nc, tally.na, tally.manual) }] },
|
|
57174
|
+
{ kind: "note", tone: "info", runs: ticks(t2.legend) },
|
|
57175
|
+
table
|
|
57176
|
+
];
|
|
57177
|
+
if (rows.some((r) => r.decidedBy === "agent" && r.status === "C")) out2.push({ kind: "note", tone: "warn", runs: ticks(agentMarkNote(lang)) });
|
|
57178
|
+
return out2;
|
|
57179
|
+
}
|
|
57180
|
+
function indexDoc(result, opts = {}) {
|
|
57181
|
+
const standard = opts.standard ?? CORE2;
|
|
57182
|
+
const lang = opts.lang ?? "en";
|
|
57183
|
+
const t2 = T4[lang];
|
|
57184
|
+
const h = headline(result, standard, lang);
|
|
57185
|
+
const blocks = [];
|
|
57186
|
+
if (opts.links?.length) {
|
|
57187
|
+
blocks.push({ kind: "heading", level: 2, text: t2.documents, id: "documents" });
|
|
57188
|
+
blocks.push({ kind: "list", items: opts.links.map((l) => [{ text: l.text, href: l.href }]) });
|
|
57189
|
+
}
|
|
57190
|
+
blocks.push(...synthesisBlocks(result, standard, lang));
|
|
57191
|
+
if (h.agentRuled) blocks.push({ kind: "note", tone: "warn", runs: ticks(agentMarkNote(lang)) });
|
|
57192
|
+
blocks.push(...scoreboardBlocks(result, standard, lang));
|
|
57193
|
+
return { lang, title: `${t2.indexTitle} \u2014 ${stdName(standard)}`, subtitle: h.runs, ...opts.nav ? { nav: opts.nav } : {}, blocks };
|
|
57194
|
+
}
|
|
57195
|
+
function compositeDoc(result, opts = {}) {
|
|
57196
|
+
const standard = opts.standard ?? CORE2;
|
|
57197
|
+
const lang = opts.lang ?? "en";
|
|
57198
|
+
const t2 = T4[lang];
|
|
57199
|
+
const h = headline(result, standard, lang);
|
|
57200
|
+
const blocks = [...synthesisBlocks(result, standard, lang)];
|
|
57201
|
+
if (h.agentRuled) blocks.push({ kind: "note", tone: "warn", runs: ticks(agentMarkNote(lang)) });
|
|
57202
|
+
blocks.push(...findingsBlocks(result, standard, lang, 2, opts.crops));
|
|
57203
|
+
blocks.push(...scoreboardBlocks(result, standard, lang));
|
|
57204
|
+
const scope = pagesOf(result);
|
|
57205
|
+
if (scope.length) blocks.push(...crossGridBlocks(result, derivePages(result, scope), standard, lang));
|
|
57206
|
+
return { lang, title: `${t2.reportTitle} ${standardLabel(standard)} \u2014 ${result.date}`, subtitle: h.runs, ...opts.nav ? { nav: opts.nav } : {}, blocks };
|
|
57207
|
+
}
|
|
57208
|
+
function pagesIndexDoc(result, opts = { sheetHref: (id) => `./page-${id}.html` }) {
|
|
57209
|
+
const standard = opts.standard ?? CORE2;
|
|
57210
|
+
const lang = opts.lang ?? "en";
|
|
57211
|
+
const t2 = T4[lang];
|
|
57212
|
+
const h = headline(result, standard, lang);
|
|
57213
|
+
const scope = pagesOf(result);
|
|
57214
|
+
const blocks = [...scoreboardBlocks(result, standard, lang, opts.sheetHref)];
|
|
57215
|
+
if (scope.length) blocks.push(...crossGridBlocks(result, derivePages(result, scope), standard, lang));
|
|
57216
|
+
return { lang, title: `${t2.pagesTitle} \u2014 ${stdName(standard)}`, subtitle: h.runs, ...opts.nav ? { nav: opts.nav } : {}, blocks };
|
|
57217
|
+
}
|
|
57218
|
+
function pageDoc(result, page, opts = {}) {
|
|
57219
|
+
const standard = opts.standard ?? CORE2;
|
|
57220
|
+
const lang = opts.lang ?? "en";
|
|
57221
|
+
const t2 = T4[lang];
|
|
57222
|
+
const blocks = [
|
|
57223
|
+
{
|
|
57224
|
+
kind: "list",
|
|
57225
|
+
items: [
|
|
57226
|
+
[
|
|
57227
|
+
{ text: `${t2.url} : `, strong: true },
|
|
57228
|
+
{ text: page.url, mono: true }
|
|
57229
|
+
],
|
|
57230
|
+
[{ text: `${t2.basis} : `, strong: true }, { text: basisLabel(page.basis, lang) }]
|
|
57231
|
+
]
|
|
57232
|
+
}
|
|
57233
|
+
];
|
|
57234
|
+
const warn = pageBasisWarning(page.basis, lang);
|
|
57235
|
+
if (warn) blocks.push({ kind: "note", tone: "warn", runs: ticks(warn) });
|
|
57236
|
+
blocks.push(
|
|
57237
|
+
opts.screenshot ? { kind: "figure", src: opts.screenshot, alt: t2.screenshotAlt(page.name) } : { kind: "para", runs: [{ text: t2.noScreenshot, em: true }] }
|
|
57238
|
+
);
|
|
57239
|
+
blocks.push(...pageGridBlocks(result, page, standard, lang));
|
|
57240
|
+
const view = { ...result, criteria: page.criteria, findings: page.findings };
|
|
57241
|
+
const { nc, advisory } = partitionUnits(prdUnits(view, standard, lang));
|
|
57242
|
+
blocks.push({ kind: "heading", level: 2, text: t2.ncTitle });
|
|
57243
|
+
if (!nc.length) blocks.push({ kind: "para", runs: [{ text: t2.noNc }] });
|
|
57244
|
+
for (const u of nc) blocks.push(...criterionBlocks(u, standard, lang, 3, opts.crops));
|
|
57245
|
+
if (advisory.length) {
|
|
57246
|
+
blocks.push({ kind: "heading", level: 2, text: t2.recTitle });
|
|
57247
|
+
blocks.push({ kind: "note", tone: "info", runs: ticks(t2.recNote) });
|
|
57248
|
+
for (const u of advisory) blocks.push(...criterionBlocks(u, standard, lang, 3, opts.crops));
|
|
57249
|
+
}
|
|
57250
|
+
return {
|
|
57251
|
+
lang,
|
|
57252
|
+
title: `${page.name} \u2014 ${stdName(standard)}`,
|
|
57253
|
+
subtitle: [{ text: result.date, mono: true }, { text: ` \xB7 ${findingsForStandard(view, standard).length} ${t2.what.toLowerCase()}` }],
|
|
57254
|
+
...opts.nav ? { nav: opts.nav } : {},
|
|
57255
|
+
blocks
|
|
57256
|
+
};
|
|
57257
|
+
}
|
|
57258
|
+
|
|
57259
|
+
// src/html-emit.ts
|
|
57260
|
+
var DEFAULT_INLINE_BUDGET = 12 * 1024 * 1024;
|
|
57261
|
+
var T5 = {
|
|
57262
|
+
fr: {
|
|
57263
|
+
overComposite: (mb2, budget) => `Les images d\xE9passent le budget d'inclusion (${mb2} Mo pour ${budget} Mo) : les captures de page ne sont pas incluses dans le fichier composite. Elles restent dans le rapport page par page.`,
|
|
57264
|
+
overCrops: (mb2, budget) => `Les images d\xE9passent encore le budget (${mb2} Mo pour ${budget} Mo) : une seule vignette par crit\xE8re est incluse. Le rapport page par page les porte toutes.`,
|
|
57265
|
+
overNone: (budget) => `Aucune image ne tient dans le budget de ${budget} Mo : le fichier composite est sans illustration. Les non-conformit\xE9s, elles, y sont toutes. Augmentez \`--inline-budget\`, ou ouvrez le rapport page par page, qui r\xE9f\xE9rence les images au lieu de les inclure.`,
|
|
57266
|
+
docTitle: "Rapport complet, en un seul fichier (imprimable en PDF)",
|
|
57267
|
+
pagesTitle: "Rapport page par page",
|
|
57268
|
+
indexTitle: "Tableau de bord"
|
|
57269
|
+
},
|
|
57270
|
+
en: {
|
|
57271
|
+
overComposite: (mb2, budget) => `Images exceed the inline budget (${mb2} MB for ${budget} MB): page screenshots are not embedded in the composite file. They remain in the page-by-page report.`,
|
|
57272
|
+
overCrops: (mb2, budget) => `Images still exceed the budget (${mb2} MB for ${budget} MB): one crop per criterion is embedded. The page-by-page report carries them all.`,
|
|
57273
|
+
overNone: (budget) => `No image fits within the ${budget} MB budget: the composite file carries no illustration. Every non-conformity is still in it. Raise \`--inline-budget\`, or open the page-by-page report, which references images instead of embedding them.`,
|
|
57274
|
+
docTitle: "Full report, in a single file (printable to PDF)",
|
|
57275
|
+
pagesTitle: "Page-by-page report",
|
|
57276
|
+
indexTitle: "Dashboard"
|
|
57277
|
+
}
|
|
57278
|
+
};
|
|
57279
|
+
var mb = (n) => (n / (1024 * 1024)).toFixed(1);
|
|
57280
|
+
function inlineSize(path) {
|
|
57281
|
+
try {
|
|
57282
|
+
return Math.ceil(statSync11(path).size / 3) * 4;
|
|
57283
|
+
} catch {
|
|
57284
|
+
return 0;
|
|
57285
|
+
}
|
|
57286
|
+
}
|
|
57287
|
+
function dataUri(path) {
|
|
57288
|
+
try {
|
|
57289
|
+
return `data:image/png;base64,${readFileSync24(path).toString("base64")}`;
|
|
57290
|
+
} catch {
|
|
57291
|
+
return void 0;
|
|
57292
|
+
}
|
|
57293
|
+
}
|
|
57294
|
+
function cropLookup(m, lang, hrefOf) {
|
|
57295
|
+
if (!m) return () => void 0;
|
|
57296
|
+
return (f) => {
|
|
57297
|
+
const c2 = m.crops.get(findingId(f));
|
|
57298
|
+
if (!c2) return void 0;
|
|
57299
|
+
const href = hrefOf(c2.path, c2.href);
|
|
57300
|
+
return href ? { href, alt: c2.alt[lang] } : void 0;
|
|
57301
|
+
};
|
|
57302
|
+
}
|
|
57303
|
+
function pickRung(cropBytes, shotBytes, budget) {
|
|
57304
|
+
const sum = (xs) => xs.reduce((a, b) => a + b, 0);
|
|
57305
|
+
if (sum(cropBytes) + sum(shotBytes) <= budget) return { steps: [], cropsPerCriterion: Number.POSITIVE_INFINITY, shots: true };
|
|
57306
|
+
if (sum(cropBytes) <= budget) return { steps: ["screenshots"], cropsPerCriterion: Number.POSITIVE_INFINITY, shots: false };
|
|
57307
|
+
const smallest = [...cropBytes].sort((a, b) => a - b);
|
|
57308
|
+
let kept = 0;
|
|
57309
|
+
let total = 0;
|
|
57310
|
+
for (const b of smallest) {
|
|
57311
|
+
if (total + b > budget) break;
|
|
57312
|
+
total += b;
|
|
57313
|
+
kept++;
|
|
57314
|
+
}
|
|
57315
|
+
if (!kept) return { steps: ["screenshots", "crops", "none"], cropsPerCriterion: 0, shots: false };
|
|
57316
|
+
return { steps: ["screenshots", "crops"], cropsPerCriterion: 1, shots: false };
|
|
57317
|
+
}
|
|
57318
|
+
function writeHtml(result, opts) {
|
|
57319
|
+
const standard = opts.standard ?? CORE2;
|
|
57320
|
+
const lang = opts.lang ?? "en";
|
|
57321
|
+
const t2 = T5[lang];
|
|
57322
|
+
const budget = opts.inlineBudget ?? DEFAULT_INLINE_BUDGET;
|
|
57323
|
+
const stdKey = isCore(standard) ? "wcag" : loadPack(standard).key;
|
|
57324
|
+
mkdirSync14(opts.outDir, { recursive: true });
|
|
57325
|
+
const crops = [...opts.evidence?.crops.values() ?? []];
|
|
57326
|
+
const shots = [...opts.screenshots?.values() ?? []];
|
|
57327
|
+
const rung = pickRung(
|
|
57328
|
+
crops.map((c2) => inlineSize(c2.path)),
|
|
57329
|
+
shots.map(inlineSize),
|
|
57330
|
+
budget
|
|
57331
|
+
);
|
|
57332
|
+
const notices = [];
|
|
57333
|
+
if (rung.steps.includes("none")) notices.push(t2.overNone(mb(budget)));
|
|
57334
|
+
else if (rung.steps.includes("crops")) notices.push(t2.overCrops(mb(crops.reduce((a, c2) => a + inlineSize(c2.path), 0)), mb(budget)));
|
|
57335
|
+
else if (rung.steps.includes("screenshots"))
|
|
57336
|
+
notices.push(t2.overComposite(mb(crops.reduce((a, c2) => a + inlineSize(c2.path), 0) + shots.reduce((a, s) => a + inlineSize(s), 0)), mb(budget)));
|
|
57337
|
+
let inlinedBytes = 0;
|
|
57338
|
+
const perCriterion = /* @__PURE__ */ new Map();
|
|
57339
|
+
const inlineCrops = cropLookup(opts.evidence, lang, (path) => {
|
|
57340
|
+
if (rung.cropsPerCriterion === 0) return void 0;
|
|
57341
|
+
const uri = dataUri(path);
|
|
57342
|
+
if (!uri) return void 0;
|
|
57343
|
+
inlinedBytes += uri.length;
|
|
57344
|
+
return uri;
|
|
57345
|
+
});
|
|
57346
|
+
const budgetedCrops = (f) => {
|
|
57347
|
+
const c2 = opts.evidence?.crops.get(findingId(f));
|
|
57348
|
+
if (!c2) return void 0;
|
|
57349
|
+
const n = perCriterion.get(c2.criteriaId) ?? 0;
|
|
57350
|
+
if (n >= rung.cropsPerCriterion) return void 0;
|
|
57351
|
+
const drawn = inlineCrops(f);
|
|
57352
|
+
if (drawn) perCriterion.set(c2.criteriaId, n + 1);
|
|
57353
|
+
return drawn;
|
|
57354
|
+
};
|
|
57355
|
+
const flat = opts.layout === "pages";
|
|
57356
|
+
const sheetDir = flat ? opts.outDir : join41(opts.outDir, "pages");
|
|
57357
|
+
const up = flat ? "./" : "../";
|
|
57358
|
+
const compositeName = `ultra11y-${stdKey}-${result.date}.html`;
|
|
57359
|
+
const nav = flat ? [{ href: "./index.html", text: t2.pagesTitle }] : [
|
|
57360
|
+
{ href: "./index.html", text: t2.indexTitle },
|
|
57361
|
+
{ href: `./${compositeName}`, text: t2.docTitle },
|
|
57362
|
+
...opts.pages ? [{ href: "./pages/index.html", text: t2.pagesTitle }] : []
|
|
57363
|
+
];
|
|
57364
|
+
let compositePath;
|
|
57365
|
+
if (!flat) {
|
|
57366
|
+
const composite = compositeDoc(result, { standard, lang, crops: budgetedCrops, nav: nav.map((n) => ({ ...n, current: n.href === `./${compositeName}` })) });
|
|
57367
|
+
if (notices.length) composite.blocks.unshift({ kind: "note", tone: "warn", runs: noticeRuns(notices) });
|
|
57368
|
+
compositePath = join41(opts.outDir, compositeName);
|
|
57369
|
+
writeFileSync16(compositePath, renderHtmlDocument(composite));
|
|
57370
|
+
}
|
|
57371
|
+
const fileCrops = cropLookup(opts.evidence, lang, (_p, href) => href.replace(/^\.\//, up));
|
|
57372
|
+
const indexNav = nav.map((n) => ({ ...n, current: n.href === "./index.html" }));
|
|
57373
|
+
const index = flat ? pagesIndexDoc(result, { standard, lang, nav: indexNav, sheetHref: (id) => `./page-${id}.html` }) : indexDoc(result, { standard, lang, nav: indexNav, links: nav.filter((n) => n.href !== "./index.html") });
|
|
57374
|
+
const indexPath = join41(opts.outDir, "index.html");
|
|
57375
|
+
writeFileSync16(indexPath, renderHtmlDocument(index));
|
|
57376
|
+
const sheets = [];
|
|
57377
|
+
if (opts.pages) {
|
|
57378
|
+
const derived = derivePages(result, pagesOf(result));
|
|
57379
|
+
mkdirSync14(sheetDir, { recursive: true });
|
|
57380
|
+
const sheetNav = flat ? [{ href: "./index.html", text: t2.pagesTitle }] : [
|
|
57381
|
+
{ href: `${up}index.html`, text: t2.indexTitle },
|
|
57382
|
+
{ href: `${up}${compositeName}`, text: t2.docTitle },
|
|
57383
|
+
{ href: "./index.html", text: t2.pagesTitle }
|
|
57384
|
+
];
|
|
57385
|
+
if (!flat) {
|
|
57386
|
+
writeFileSync16(
|
|
57387
|
+
join41(sheetDir, "index.html"),
|
|
57388
|
+
renderHtmlDocument(
|
|
57389
|
+
pagesIndexDoc(result, {
|
|
57390
|
+
standard,
|
|
57391
|
+
lang,
|
|
57392
|
+
nav: sheetNav.map((n) => ({ ...n, current: n.href === "./index.html" })),
|
|
57393
|
+
sheetHref: (id) => `./page-${id}.html`
|
|
57394
|
+
})
|
|
57395
|
+
)
|
|
57396
|
+
);
|
|
57397
|
+
sheets.push(join41(sheetDir, "index.html"));
|
|
57398
|
+
}
|
|
57399
|
+
for (const p of derived) {
|
|
57400
|
+
const doc = pageDoc(result, p, {
|
|
57401
|
+
standard,
|
|
57402
|
+
lang,
|
|
57403
|
+
crops: fileCrops,
|
|
57404
|
+
nav: sheetNav,
|
|
57405
|
+
// Where `cmdPages`' screenshot copier already put the capture: `assets/<id>.png`,
|
|
57406
|
+
// beside the entry point, seen from wherever this sheet sits.
|
|
57407
|
+
...opts.screenshots?.has(p.id) ? { screenshot: `${up}assets/${p.id}.png` } : {}
|
|
57408
|
+
});
|
|
57409
|
+
const path = join41(sheetDir, `page-${p.id}.html`);
|
|
57410
|
+
writeFileSync16(path, renderHtmlDocument(doc));
|
|
57411
|
+
sheets.push(path);
|
|
57412
|
+
}
|
|
57413
|
+
}
|
|
57414
|
+
return {
|
|
57415
|
+
index: indexPath,
|
|
57416
|
+
...compositePath ? { composite: compositePath } : {},
|
|
57417
|
+
sheets,
|
|
57418
|
+
inlinedBytes,
|
|
57419
|
+
degraded: rung.steps,
|
|
57420
|
+
imagesDropped: rung.steps.includes("none"),
|
|
57421
|
+
notices
|
|
57422
|
+
};
|
|
57423
|
+
}
|
|
57424
|
+
function noticeRuns(notices) {
|
|
57425
|
+
return notices.flatMap((n, i2) => i2 ? [{ text: " " }, { text: n }] : [{ text: n }]);
|
|
57426
|
+
}
|
|
57427
|
+
|
|
56189
57428
|
// src/dev.ts
|
|
56190
57429
|
import { createServer } from "http";
|
|
56191
|
-
import { mkdirSync as
|
|
56192
|
-
import { dirname as dirname12, join as
|
|
57430
|
+
import { mkdirSync as mkdirSync15, readFileSync as readFileSync25, writeFileSync as writeFileSync17 } from "fs";
|
|
57431
|
+
import { dirname as dirname12, join as join42 } from "path";
|
|
56193
57432
|
var DEV_DEFAULT_PORT = 4111;
|
|
56194
57433
|
function criterionLabel3(f, standard) {
|
|
56195
57434
|
if (isCore(standard)) return `WCAG ${f.criteriaId}`;
|
|
@@ -56367,71 +57606,19 @@ export function Ultra11yOverlay({ endpoint = "http://127.0.0.1:${port}" }) {
|
|
|
56367
57606
|
export default Ultra11yOverlay;
|
|
56368
57607
|
`;
|
|
56369
57608
|
}
|
|
56370
|
-
var MARK3 = { C: "C", NC: "NC", NA: "\u2014", manual: "?" };
|
|
56371
|
-
var CLASS = { C: "c", NC: "nc", NA: "na", manual: "m" };
|
|
56372
|
-
function esc2(s) {
|
|
56373
|
-
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
56374
|
-
}
|
|
56375
57609
|
function dashboardHtml(result, pages, standard, lang) {
|
|
56376
57610
|
const fr = lang === "fr";
|
|
56377
|
-
const
|
|
56378
|
-
const head = `<!doctype html><html lang="${fr ? "fr" : "en"}"><head><meta charset="utf-8">
|
|
56379
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
56380
|
-
<title>ultra11y \u2014 ${esc2(stdLabel)}</title>
|
|
56381
|
-
<style>
|
|
56382
|
-
:root { color-scheme: light dark; --bg:#fff; --fg:#111; --line:#e3e3e6; --mut:#666; }
|
|
56383
|
-
@media (prefers-color-scheme: dark) { :root { --bg:#141416; --fg:#f2f2f7; --line:#2c2c2e; --mut:#a1a1a6; } }
|
|
56384
|
-
body { margin:0; padding:24px; background:var(--bg); color:var(--fg); font:14px/1.5 ui-sans-serif,system-ui,sans-serif; }
|
|
56385
|
-
h1 { font-size:18px; margin:0 0 4px; } p.sub { color:var(--mut); margin:0 0 20px; }
|
|
56386
|
-
table { border-collapse:collapse; width:100%; } th,td { padding:5px 8px; border-bottom:1px solid var(--line); text-align:left; }
|
|
56387
|
-
th { position:sticky; top:0; background:var(--bg); }
|
|
56388
|
-
td.c,td.nc,td.na,td.m { text-align:center; font-weight:600; width:80px; }
|
|
56389
|
-
td.c{color:#0a7d33} td.nc{color:#b3261e} td.na{color:var(--mut)} td.m{color:#b06000}
|
|
56390
|
-
tr.theme td { font-weight:700; background:color-mix(in srgb, var(--fg) 6%, transparent); }
|
|
56391
|
-
.rate { font-size:22px; font-weight:700; }
|
|
56392
|
-
.empty { color:var(--mut); max-width:60ch; }
|
|
56393
|
-
code { font:12px ui-monospace,monospace; color:var(--mut); }
|
|
56394
|
-
</style></head><body>`;
|
|
57611
|
+
const title2 = `ultra11y \u2014 ${isCore(standard) ? "WCAG 2.2 AA" : loadPack(standard).name}`;
|
|
56395
57612
|
if (!result || !pages.length) {
|
|
56396
|
-
|
|
56397
|
-
|
|
56398
|
-
}
|
|
56399
|
-
const rows = [];
|
|
56400
|
-
const status = /* @__PURE__ */ new Map();
|
|
56401
|
-
if (isCore(standard)) {
|
|
56402
|
-
for (const c2 of result.criteria) rows.push({ id: c2.id, label: c2.id, group: c2.guideline });
|
|
56403
|
-
for (const p of pages) for (const c2 of p.criteria) (status.get(c2.id) ?? status.set(c2.id, /* @__PURE__ */ new Map()).get(c2.id)).set(p.id, c2.status);
|
|
56404
|
-
} else {
|
|
56405
|
-
const pack = loadPack(standard);
|
|
56406
|
-
for (const pc of pack.criteria) rows.push({ id: pc.id, label: pc.id, group: `${pc.theme}. ${themeName(pack, pc.theme, lang) ?? ""}` });
|
|
56407
|
-
for (const p of pages) {
|
|
56408
|
-
const view = { ...result, criteria: p.criteria, findings: p.findings };
|
|
56409
|
-
for (const pc of derivePackResults(view, standard)) (status.get(pc.id) ?? status.set(pc.id, /* @__PURE__ */ new Map()).get(pc.id)).set(p.id, pc.status);
|
|
56410
|
-
}
|
|
56411
|
-
}
|
|
56412
|
-
const out2 = [head];
|
|
56413
|
-
out2.push(`<h1>ultra11y \u2014 ${esc2(stdLabel)}</h1>`);
|
|
56414
|
-
out2.push(`<p class="sub">${pages.length} ${fr ? "page(s) captur\xE9e(s)" : "page(s) captured"} \xB7 <code>${esc2(result.date)}</code></p>`);
|
|
56415
|
-
out2.push("<table><thead><tr><th></th>");
|
|
56416
|
-
for (const p of pages) out2.push(`<th>${esc2(p.name)}${p.auth ? " \u{1F512}" : ""}<br><code>${esc2(p.url)}</code></th>`);
|
|
56417
|
-
out2.push("</tr></thead><tbody>");
|
|
56418
|
-
out2.push(
|
|
56419
|
-
`<tr><td>${fr ? "Taux" : "Rate"}</td>${pages.map((p) => `<td class="rate">${esc2(formatRate(p.conformancePct, p.decided, p.total))}</td>`).join("")}</tr>`
|
|
56420
|
-
);
|
|
56421
|
-
let group = "";
|
|
56422
|
-
for (const row of rows) {
|
|
56423
|
-
if (row.group !== group) {
|
|
56424
|
-
group = row.group;
|
|
56425
|
-
out2.push(`<tr class="theme"><td colspan="${pages.length + 1}">${esc2(group)}</td></tr>`);
|
|
56426
|
-
}
|
|
56427
|
-
const cells = pages.map((p) => {
|
|
56428
|
-
const s = status.get(row.id)?.get(p.id) ?? "manual";
|
|
56429
|
-
return `<td class="${CLASS[s]}">${MARK3[s]}</td>`;
|
|
56430
|
-
});
|
|
56431
|
-
out2.push(`<tr><td>${esc2(row.label)}</td>${cells.join("")}</tr>`);
|
|
57613
|
+
const empty = fr ? "Aucune page captur\xE9e pour l'instant. Ouvrez votre application avec l'overlay actif : chaque page visit\xE9e appara\xEEtra ici." : "No page captured yet. Open your app with the overlay active: every page you visit shows up here.";
|
|
57614
|
+
return renderHtmlDocument({ lang, title: title2, blocks: [{ kind: "para", runs: [{ text: empty }] }] });
|
|
56432
57615
|
}
|
|
56433
|
-
|
|
56434
|
-
|
|
57616
|
+
return renderHtmlDocument({
|
|
57617
|
+
lang,
|
|
57618
|
+
title: title2,
|
|
57619
|
+
subtitle: [{ text: `${pages.length} ${fr ? "page(s) captur\xE9e(s)" : "page(s) captured"} \xB7 ` }, { text: result.date, mono: true }],
|
|
57620
|
+
blocks: [...scoreboardBlocks(result, standard, lang), ...crossGridBlocks(result, pages, standard, lang)]
|
|
57621
|
+
});
|
|
56435
57622
|
}
|
|
56436
57623
|
function cors(res) {
|
|
56437
57624
|
res.setHeader("access-control-allow-origin", "*");
|
|
@@ -56453,21 +57640,23 @@ function auditCollected(root, payload) {
|
|
|
56453
57640
|
const v = validateSnapshotMeta(payload.meta);
|
|
56454
57641
|
if (!v.ok || !v.meta) return { ok: false, error: v.issues.map((i2) => `${i2.path}: ${i2.message}`).join("; ") };
|
|
56455
57642
|
if (typeof payload.dom !== "string" || !payload.dom.trim()) return { ok: false, error: "dom is required" };
|
|
57643
|
+
const shot = typeof payload.screenshot === "string" && payload.screenshot.trim() ? payload.screenshot : void 0;
|
|
56456
57644
|
const dir = writeSnapshot(root, {
|
|
56457
57645
|
meta: v.meta,
|
|
56458
57646
|
dom: payload.dom,
|
|
56459
57647
|
...payload.styles ? { styles: payload.styles } : {},
|
|
56460
57648
|
...payload.boxes ? { boxes: payload.boxes } : {},
|
|
56461
57649
|
...payload.axtree ? { axtree: payload.axtree } : {},
|
|
56462
|
-
...payload.css ? { css: payload.css } : {}
|
|
57650
|
+
...payload.css ? { css: payload.css } : {},
|
|
57651
|
+
...shot ? { screenshotBase64: shot } : {}
|
|
56463
57652
|
});
|
|
56464
|
-
return { ok: true, result: runAudit({ inputs: [
|
|
57653
|
+
return { ok: true, result: runAudit({ inputs: [join42(dir, "dom.html")] }) };
|
|
56465
57654
|
}
|
|
56466
57655
|
function projectPages(root) {
|
|
56467
57656
|
const snaps = readSnapshots(root);
|
|
56468
57657
|
if (!snaps.length) return { result: null, pages: [] };
|
|
56469
57658
|
const scope = pageScopesFrom(snaps);
|
|
56470
|
-
const result = runAudit({ inputs: [
|
|
57659
|
+
const result = runAudit({ inputs: [join42(root, ".ultra11y/pages")] });
|
|
56471
57660
|
result.scope.pages = scope;
|
|
56472
57661
|
attributePages(result, scope);
|
|
56473
57662
|
foldRecordedAdjudication(root, result);
|
|
@@ -56481,7 +57670,7 @@ function judgePages(result) {
|
|
|
56481
57670
|
function foldRecordedAdjudication(root, fresh) {
|
|
56482
57671
|
let prior;
|
|
56483
57672
|
try {
|
|
56484
|
-
prior = JSON.parse(
|
|
57673
|
+
prior = JSON.parse(readFileSync25(join42(root, "audits", "audit-latest.json"), "utf8"));
|
|
56485
57674
|
} catch {
|
|
56486
57675
|
return;
|
|
56487
57676
|
}
|
|
@@ -56596,9 +57785,9 @@ function startDevServer(opts) {
|
|
|
56596
57785
|
});
|
|
56597
57786
|
let auditPath;
|
|
56598
57787
|
if (applied.ok) {
|
|
56599
|
-
auditPath =
|
|
56600
|
-
|
|
56601
|
-
|
|
57788
|
+
auditPath = join42(opts.root, "audits", "audit-latest.json");
|
|
57789
|
+
mkdirSync15(dirname12(auditPath), { recursive: true });
|
|
57790
|
+
writeFileSync17(auditPath, `${JSON.stringify(applied.audit, null, 2)}
|
|
56602
57791
|
`);
|
|
56603
57792
|
}
|
|
56604
57793
|
res.writeHead(200, { "content-type": "application/json" }).end(
|
|
@@ -56912,11 +58101,11 @@ function e2eSetupPlan(runners, paths, lang = "en") {
|
|
|
56912
58101
|
}
|
|
56913
58102
|
|
|
56914
58103
|
// src/orchestrate.ts
|
|
56915
|
-
import { existsSync as
|
|
56916
|
-
import { join as
|
|
58104
|
+
import { existsSync as existsSync29, mkdirSync as mkdirSync16, readFileSync as readFileSync26, rmSync as rmSync7, writeFileSync as writeFileSync18 } from "fs";
|
|
58105
|
+
import { join as join44, resolve as resolve10 } from "path";
|
|
56917
58106
|
|
|
56918
58107
|
// src/orchestrate-templates.ts
|
|
56919
|
-
import { join as
|
|
58108
|
+
import { join as join43 } from "path";
|
|
56920
58109
|
var ONE_WRITER_FOOTER = `
|
|
56921
58110
|
## Return, don't write
|
|
56922
58111
|
|
|
@@ -56988,7 +58177,7 @@ var PHASE_SPECS = {
|
|
|
56988
58177
|
title: "Adjudicate",
|
|
56989
58178
|
schema: ADJUDICATE_SCHEMA,
|
|
56990
58179
|
description: (n) => `Adjudicate the ${n} residual judgment criterion(ia) of an ultra11y audit (fan-out, fail-closed fold)`,
|
|
56991
|
-
applyHint: (engine, worklist, run2) => `node ${engine} verify --apply ${worklist} --in ${
|
|
58180
|
+
applyHint: (engine, worklist, run2) => `node ${engine} verify --apply ${worklist} --in ${join43(run2, "audit-latest.json")} --out ${run2}`
|
|
56992
58181
|
},
|
|
56993
58182
|
"verify-report": {
|
|
56994
58183
|
role: "refuter",
|
|
@@ -57010,7 +58199,7 @@ function toBatches(ids, batchSize) {
|
|
|
57010
58199
|
}
|
|
57011
58200
|
function phaseWorkflowScript(ph, runAbs, engineAbs, batchSize) {
|
|
57012
58201
|
const spec = phaseSpec(ph.name);
|
|
57013
|
-
const scriptPath =
|
|
58202
|
+
const scriptPath = join43(runAbs, "orchestration", `${ph.name}.workflow.mjs`);
|
|
57014
58203
|
const meta2 = { name: `ultra11y-${ph.name}`, description: spec.description(ph.items), phases: [{ title: spec.title }] };
|
|
57015
58204
|
return [
|
|
57016
58205
|
`export const meta = ${JSON.stringify(meta2)}`,
|
|
@@ -57054,7 +58243,7 @@ function agentContracts(runAbs, engineAbs) {
|
|
|
57054
58243
|
|
|
57055
58244
|
You adjudicate the residual judgment criteria of an ultra11y audit \u2014 the ones the deterministic engine could not decide (alt-text relevance, link purpose in context, reading order\u2026). The ACTIVE STANDARD is recorded in the worklist's \`standard\` field: under a country standard (e.g. \`rgaa\`) the items are that standard's OWN criteria, each carrying its numbered tests \u2014 not WCAG success criteria.
|
|
57056
58245
|
|
|
57057
|
-
Worklist: \`${
|
|
58246
|
+
Worklist: \`${join43(runAbs, "ADJUDICATE.todo.json")}\` (an object with \`kind: "adjudication"\` and \`items[]\`). Handle ONLY the criteria whose \`criteriaId\` is named in your prompt (\`ITEMS=<id,\u2026>\`).
|
|
57058
58247
|
|
|
57059
58248
|
For EACH of your criteria:
|
|
57060
58249
|
|
|
@@ -57072,7 +58261,7 @@ ${footer}`,
|
|
|
57072
58261
|
|
|
57073
58262
|
You are an adversarial skeptic verifying the non-conformities of an ultra11y report. Your job is to try to REFUTE each claim: assume it is wrong until the source proves it.
|
|
57074
58263
|
|
|
57075
|
-
Worklist: \`${
|
|
58264
|
+
Worklist: \`${join43(runAbs, "VERIFY.todo.json")}\` (a JSON array; each entry has \`n\`, \`criteriaId\`, \`file\`, \`line\`, \`selector\`, \`claim\`). Handle ONLY the entries whose \`n\` is named in your prompt (\`ITEMS=<n,\u2026>\`).
|
|
57076
58265
|
|
|
57077
58266
|
For EACH of your entries:
|
|
57078
58267
|
|
|
@@ -57108,14 +58297,14 @@ ${status}
|
|
|
57108
58297
|
|
|
57109
58298
|
## The loop (play every role yourself, one item at a time)
|
|
57110
58299
|
|
|
57111
|
-
1. **Audit** (if not done): \`${engine} audit "<globs>" --graph --out ${runAbs}\` \u2192 \`${
|
|
57112
|
-
2. **Adjudicate the residual criteria** \u2014 \`${engine} verify --manual --in ${
|
|
57113
|
-
3. **Report**: \`${engine} report --in ${
|
|
57114
|
-
4. **Verify the report's claims** \u2014 \`${engine} verify --report <the report .md> --out ${runAbs}\` writes \`${
|
|
58300
|
+
1. **Audit** (if not done): \`${engine} audit "<globs>" --graph --out ${runAbs}\` \u2192 \`${join43(runAbs, "audit-latest.json")}\`.
|
|
58301
|
+
2. **Adjudicate the residual criteria** \u2014 \`${engine} verify --manual --in ${join43(runAbs, "audit-latest.json")} --out ${runAbs}\` writes \`${join43(runAbs, "ADJUDICATE.todo.json")}\`. For EVERY item, apply \`${join43(runAbs, "orchestration", "agents", "adjudicator.md")}\` yourself (read the evidence, rule C/NC/NA/manual, fill the required justification/findings/reason IN the todo file). Then fold, fail-closed: \`${engine} verify --apply ${join43(runAbs, "ADJUDICATE.todo.json")} --in ${join43(runAbs, "audit-latest.json")} --out ${runAbs}\`.
|
|
58302
|
+
3. **Report**: \`${engine} report --in ${join43(runAbs, "audit-latest.json")} --out ${runAbs}\`.
|
|
58303
|
+
4. **Verify the report's claims** \u2014 \`${engine} verify --report <the report .md> --out ${runAbs}\` writes \`${join43(runAbs, "VERIFY.todo.json")}\`. For EVERY entry, apply \`${join43(runAbs, "orchestration", "agents", "refuter.md")}\` yourself (open file:line, verdict supported/partial/refuted/unsupported + note IN the todo file). Then: \`${engine} verify --apply ${join43(runAbs, "VERIFY.todo.json")} --report <the report .md>\`.
|
|
57115
58304
|
5. **Gate**: \`${engine} check --report <the report .md> --semantic\` must exit 0 before presenting anything.
|
|
57116
58305
|
6. **Fix & re-audit**: \`${engine} fix <globs> --write --iterate\`, hand-apply the judgment fixes, then loop from step 1 until the gate stays green.
|
|
57117
58306
|
|
|
57118
|
-
With subagents available, prefer the emitted workflows instead: \`orchestrate --run ${runAbs} --phase <p>\` then \`Workflow({ scriptPath: "${
|
|
58307
|
+
With subagents available, prefer the emitted workflows instead: \`orchestrate --run ${runAbs} --phase <p>\` then \`Workflow({ scriptPath: "${join43(runAbs, "orchestration", "<p>.workflow.mjs")}" })\` \u2014 you stay the sole writer either way.
|
|
57119
58308
|
`;
|
|
57120
58309
|
}
|
|
57121
58310
|
|
|
@@ -57125,12 +58314,12 @@ var SMALL_WORKLIST = 3;
|
|
|
57125
58314
|
var BATCH_SIZE2 = 8;
|
|
57126
58315
|
function listPhases(runDir, engineAbs) {
|
|
57127
58316
|
const run2 = resolve10(runDir);
|
|
57128
|
-
const adjPath =
|
|
58317
|
+
const adjPath = join44(run2, "ADJUDICATE.todo.json");
|
|
57129
58318
|
let adjIds = [];
|
|
57130
58319
|
let adjReady = false;
|
|
57131
|
-
if (
|
|
58320
|
+
if (existsSync29(adjPath)) {
|
|
57132
58321
|
try {
|
|
57133
|
-
const f = JSON.parse(
|
|
58322
|
+
const f = JSON.parse(readFileSync26(adjPath, "utf8"));
|
|
57134
58323
|
if (f && f.kind === "adjudication" && Array.isArray(f.items)) {
|
|
57135
58324
|
adjReady = true;
|
|
57136
58325
|
adjIds = f.items.map((i2) => i2.criteriaId);
|
|
@@ -57138,12 +58327,12 @@ function listPhases(runDir, engineAbs) {
|
|
|
57138
58327
|
} catch {
|
|
57139
58328
|
}
|
|
57140
58329
|
}
|
|
57141
|
-
const verPath =
|
|
58330
|
+
const verPath = join44(run2, "VERIFY.todo.json");
|
|
57142
58331
|
let verIds = [];
|
|
57143
58332
|
let verReady = false;
|
|
57144
|
-
if (
|
|
58333
|
+
if (existsSync29(verPath)) {
|
|
57145
58334
|
try {
|
|
57146
|
-
const items = JSON.parse(
|
|
58335
|
+
const items = JSON.parse(readFileSync26(verPath, "utf8"));
|
|
57147
58336
|
if (Array.isArray(items)) {
|
|
57148
58337
|
verReady = true;
|
|
57149
58338
|
verIds = items.map((i2) => String(i2.n));
|
|
@@ -57158,7 +58347,7 @@ function listPhases(runDir, engineAbs) {
|
|
|
57158
58347
|
worklist: adjPath,
|
|
57159
58348
|
items: adjIds.length,
|
|
57160
58349
|
ids: adjIds,
|
|
57161
|
-
prerequisite: `node ${engineAbs} verify --manual --in ${
|
|
58350
|
+
prerequisite: `node ${engineAbs} verify --manual --in ${join44(run2, "audit-latest.json")} --out ${run2}`
|
|
57162
58351
|
},
|
|
57163
58352
|
{
|
|
57164
58353
|
name: "verify-report",
|
|
@@ -57172,7 +58361,7 @@ function listPhases(runDir, engineAbs) {
|
|
|
57172
58361
|
}
|
|
57173
58362
|
function orchestrateRun(runDir, engineAbs, opts = {}) {
|
|
57174
58363
|
const run2 = resolve10(runDir);
|
|
57175
|
-
if (!
|
|
58364
|
+
if (!existsSync29(run2)) {
|
|
57176
58365
|
return { exitCode: 2, written: [], notices: [], errors: [`run dir not found: ${run2}`], phases: [] };
|
|
57177
58366
|
}
|
|
57178
58367
|
const phases = listPhases(run2, engineAbs);
|
|
@@ -57199,22 +58388,22 @@ function orchestrateRun(runDir, engineAbs, opts = {}) {
|
|
|
57199
58388
|
}
|
|
57200
58389
|
selected = [ph];
|
|
57201
58390
|
}
|
|
57202
|
-
const orchDir =
|
|
57203
|
-
const agentsDir =
|
|
57204
|
-
|
|
57205
|
-
|
|
58391
|
+
const orchDir = join44(run2, "orchestration");
|
|
58392
|
+
const agentsDir = join44(orchDir, "agents");
|
|
58393
|
+
mkdirSync16(join44(orchDir, "out"), { recursive: true });
|
|
58394
|
+
mkdirSync16(agentsDir, { recursive: true });
|
|
57206
58395
|
const written = [];
|
|
57207
58396
|
const notices = [];
|
|
57208
58397
|
for (const [name2, content] of Object.entries(agentContracts(run2, engineAbs))) {
|
|
57209
|
-
const p =
|
|
57210
|
-
|
|
58398
|
+
const p = join44(agentsDir, `${name2}.md`);
|
|
58399
|
+
writeFileSync18(p, content);
|
|
57211
58400
|
written.push(p);
|
|
57212
58401
|
}
|
|
57213
58402
|
const emitted = new Set(opts.eco ? [] : selected.filter((p) => p.items > 0).map((p) => p.name));
|
|
57214
58403
|
for (const ph of phases) {
|
|
57215
58404
|
if (emitted.has(ph.name)) continue;
|
|
57216
|
-
const stale =
|
|
57217
|
-
if (
|
|
58405
|
+
const stale = join44(orchDir, `${ph.name}.workflow.mjs`);
|
|
58406
|
+
if (existsSync29(stale)) {
|
|
57218
58407
|
rmSync7(stale, { force: true });
|
|
57219
58408
|
notices.push(`phase "${ph.name}": stale workflow removed \u2014 its worklist ${ph.ready ? "is now empty" : "no longer exists"}.`);
|
|
57220
58409
|
}
|
|
@@ -57228,13 +58417,13 @@ function orchestrateRun(runDir, engineAbs, opts = {}) {
|
|
|
57228
58417
|
if (ph.items <= SMALL_WORKLIST) {
|
|
57229
58418
|
notices.push(`phase "${ph.name}": only ${ph.items} item(s) \u2014 the sequential --eco path is equivalent and cheaper.`);
|
|
57230
58419
|
}
|
|
57231
|
-
const p =
|
|
57232
|
-
|
|
58420
|
+
const p = join44(orchDir, `${ph.name}.workflow.mjs`);
|
|
58421
|
+
writeFileSync18(p, phaseWorkflowScript(ph, run2, engineAbs, BATCH_SIZE2));
|
|
57233
58422
|
written.push(p);
|
|
57234
58423
|
}
|
|
57235
58424
|
}
|
|
57236
|
-
const rb =
|
|
57237
|
-
|
|
58425
|
+
const rb = join44(orchDir, "RUNBOOK.md");
|
|
58426
|
+
writeFileSync18(rb, runbookMd(phases, run2, engineAbs));
|
|
57238
58427
|
written.push(rb);
|
|
57239
58428
|
return { exitCode: 0, written, notices, errors: [], phases };
|
|
57240
58429
|
}
|
|
@@ -57243,8 +58432,8 @@ function orchestrateRun(runDir, engineAbs, opts = {}) {
|
|
|
57243
58432
|
import { createInterface as createInterface2 } from "readline";
|
|
57244
58433
|
|
|
57245
58434
|
// src/mcp/handlers.ts
|
|
57246
|
-
import { existsSync as
|
|
57247
|
-
import { isAbsolute as isAbsolute3, join as
|
|
58435
|
+
import { existsSync as existsSync30, readFileSync as readFileSync27, realpathSync as realpathSync4, statSync as statSync12 } from "fs";
|
|
58436
|
+
import { isAbsolute as isAbsolute3, join as join45, resolve as resolve11, sep as sep5 } from "path";
|
|
57248
58437
|
|
|
57249
58438
|
// src/project-lock.ts
|
|
57250
58439
|
var chains = /* @__PURE__ */ new Map();
|
|
@@ -57292,8 +58481,8 @@ function requiredCwd(args2, defaults) {
|
|
|
57292
58481
|
const cwd = str2(args2.cwd) ?? defaults.defaultCwd;
|
|
57293
58482
|
if (!cwd) throw new ToolError("`cwd` is required: an absolute path to the project root.");
|
|
57294
58483
|
const abs = resolve11(cwd);
|
|
57295
|
-
if (!
|
|
57296
|
-
if (!
|
|
58484
|
+
if (!existsSync30(abs)) throw new ToolError(`project root not found: ${abs}`);
|
|
58485
|
+
if (!statSync12(abs).isDirectory()) throw new ToolError(`\`cwd\` is not a directory: ${abs}`);
|
|
57297
58486
|
return abs;
|
|
57298
58487
|
}
|
|
57299
58488
|
function standardOf(args2) {
|
|
@@ -57358,7 +58547,7 @@ function audit(args2, cwd) {
|
|
|
57358
58547
|
process.chdir(cwd);
|
|
57359
58548
|
const inputs = strArray2(args2.globs) ?? DEFAULT_GLOBS;
|
|
57360
58549
|
const scopedToDiff = bool(args2.changed) || bool(args2.staged) || str2(args2.since) !== void 0;
|
|
57361
|
-
const extra = [CAPTURES_DIR, PAGES_DIR].filter((d) => !scopedToDiff &&
|
|
58550
|
+
const extra = [CAPTURES_DIR, PAGES_DIR].filter((d) => !scopedToDiff && existsSync30(d) && !inputs.includes(d));
|
|
57362
58551
|
const result = runAudit({
|
|
57363
58552
|
inputs: [...inputs, ...extra],
|
|
57364
58553
|
include: strArray2(args2.include),
|
|
@@ -57396,7 +58585,14 @@ function handleReport(args2, cwd) {
|
|
|
57396
58585
|
const lang = langOf(args2);
|
|
57397
58586
|
const r = audit(args2, cwd);
|
|
57398
58587
|
const md = standard === "wcag" ? renderReport(r, lang) : renderPackReport(r, loadPack(standard), lang);
|
|
57399
|
-
return {
|
|
58588
|
+
return {
|
|
58589
|
+
cwd,
|
|
58590
|
+
standard,
|
|
58591
|
+
lang,
|
|
58592
|
+
report: md,
|
|
58593
|
+
...args2.html === true ? { html: renderHtmlDocument(compositeDoc(r, { standard, lang })) } : {},
|
|
58594
|
+
next: "Validate it with ultra11y_check before presenting it."
|
|
58595
|
+
};
|
|
57400
58596
|
}
|
|
57401
58597
|
function handlePrd(args2, cwd) {
|
|
57402
58598
|
const standard = standardOf(args2);
|
|
@@ -57470,13 +58666,13 @@ function handlePackCheck(args2, cwd) {
|
|
|
57470
58666
|
return { cwd, ...res };
|
|
57471
58667
|
}
|
|
57472
58668
|
function handleSampleCheck(cwd, standard) {
|
|
57473
|
-
const file =
|
|
57474
|
-
if (!
|
|
58669
|
+
const file = join45(cwd, ".ultra11yrc.json");
|
|
58670
|
+
if (!existsSync30(file)) {
|
|
57475
58671
|
throw new ToolError(`no .ultra11yrc.json at ${cwd} \u2014 a page sample must be declared before it can be linted.`);
|
|
57476
58672
|
}
|
|
57477
58673
|
let raw;
|
|
57478
58674
|
try {
|
|
57479
|
-
raw = JSON.parse(
|
|
58675
|
+
raw = JSON.parse(readFileSync27(file, "utf8"));
|
|
57480
58676
|
} catch (e) {
|
|
57481
58677
|
throw new ToolError(`.ultra11yrc.json is not valid JSON: ${e.message}`);
|
|
57482
58678
|
}
|
|
@@ -57520,6 +58716,7 @@ function handlePages(args2, cwd) {
|
|
|
57520
58716
|
standard,
|
|
57521
58717
|
lang,
|
|
57522
58718
|
markdown,
|
|
58719
|
+
...args2.html === true ? { html: renderHtmlDocument(pagesIndexDoc(r, { standard, lang, sheetHref: (id) => `#page-${id}` })) } : {},
|
|
57523
58720
|
// `conformancePct` is null when this page decided nothing — a machine consumer must be able
|
|
57524
58721
|
// to tell "no criterion was assessed" from "every assessed criterion passed", which a bare
|
|
57525
58722
|
// 100 conflates. `decided`/`total` are the denominator that makes the number quotable.
|
|
@@ -57569,7 +58766,7 @@ function handleInit(args2, cwd) {
|
|
|
57569
58766
|
const enginePath = resolve11("scripts/ultra11y.mjs");
|
|
57570
58767
|
const written = [];
|
|
57571
58768
|
if (bool(args2.hook)) written.push(writeHook(cwd, enginePath, failOn));
|
|
57572
|
-
if (bool(args2.ci)) written.push(
|
|
58769
|
+
if (bool(args2.ci)) written.push(join45(cwd, ".github/workflows/a11y.yml"));
|
|
57573
58770
|
if (!written.length) {
|
|
57574
58771
|
throw new ToolError("nothing to install \u2014 pass hook:true and/or ci:true.");
|
|
57575
58772
|
}
|
|
@@ -57589,8 +58786,8 @@ function reportText(args2, tool) {
|
|
|
57589
58786
|
if (inline) return inline;
|
|
57590
58787
|
if (!file) throw new ToolError(`\`report_text\` is required \u2014 the report markdown for ultra11y_${tool} to work on.`);
|
|
57591
58788
|
if (!isAbsolute3(file)) throw new ToolError("`report_file` must be an absolute path.");
|
|
57592
|
-
if (!
|
|
57593
|
-
return
|
|
58789
|
+
if (!existsSync30(file)) throw new ToolError(`report file not found: ${file}`);
|
|
58790
|
+
return readFileSync27(file, "utf8");
|
|
57594
58791
|
}
|
|
57595
58792
|
function handleCriteria(args2) {
|
|
57596
58793
|
const lang = langOf(args2);
|
|
@@ -57605,7 +58802,7 @@ function handleCriteria(args2) {
|
|
|
57605
58802
|
function handleRead(args2, cwd) {
|
|
57606
58803
|
const raw = str2(args2.path);
|
|
57607
58804
|
if (!raw) throw new ToolError("`path` is required \u2014 relative to the project root, or an absolute path inside it.");
|
|
57608
|
-
const target = isAbsolute3(raw) ? raw :
|
|
58805
|
+
const target = isAbsolute3(raw) ? raw : join45(cwd, raw);
|
|
57609
58806
|
let real;
|
|
57610
58807
|
try {
|
|
57611
58808
|
real = realpathSync4(target);
|
|
@@ -57616,10 +58813,10 @@ function handleRead(args2, cwd) {
|
|
|
57616
58813
|
if (real !== root && !real.startsWith(root + sep5)) {
|
|
57617
58814
|
throw new ToolError(`path is outside the project: ${raw}. Use your own file tool for anything else.`);
|
|
57618
58815
|
}
|
|
57619
|
-
const st =
|
|
58816
|
+
const st = statSync12(real);
|
|
57620
58817
|
if (!st.isFile()) throw new ToolError(`not a file: ${raw}`);
|
|
57621
58818
|
if (st.size > MAX_READ_BYTES) throw new ToolError(`file is too large to read (${st.size} bytes): ${raw}`);
|
|
57622
|
-
const lines =
|
|
58819
|
+
const lines = readFileSync27(real, "utf8").split("\n");
|
|
57623
58820
|
const total = lines.length;
|
|
57624
58821
|
const start2 = Math.max(1, Math.floor(num2(args2.start_line) ?? 1));
|
|
57625
58822
|
if (start2 > total) throw new ToolError(`start_line ${start2} is past the end of the file (${total} lines).`);
|
|
@@ -57741,6 +58938,10 @@ var standardProp = {
|
|
|
57741
58938
|
description: "Which standard to report against. Default: wcag (WCAG 2.2 AA)."
|
|
57742
58939
|
};
|
|
57743
58940
|
var langProp = { type: "string", enum: ["en", "fr"], description: "Language for the rendered prose. Default: en." };
|
|
58941
|
+
var htmlProp = {
|
|
58942
|
+
type: "boolean",
|
|
58943
|
+
description: "Also return the report as a self-contained HTML page (no script, no external asset). Images are not embedded \u2014 this returns a string, not files."
|
|
58944
|
+
};
|
|
57744
58945
|
var COVERAGE_NOTE = "Static analysis alone decides only a few of the 55 WCAG 2.2 AA criteria \u2014 most need YOUR judgment (is this alt text actually relevant? is this link's purpose clear in context?) and some need a real browser (ultra11y_scan). A silent criterion is untested, never conformant.";
|
|
57745
58946
|
var TOOLS2 = [
|
|
57746
58947
|
{
|
|
@@ -57770,7 +58971,7 @@ var TOOLS2 = [
|
|
|
57770
58971
|
description: "Turn an audit into the auditor-facing conformance report: one section per success criterion, with the evidence and the verdict. Says explicitly which criteria were not tested, because a report silent about its own coverage reads as a clean bill of health.",
|
|
57771
58972
|
inputSchema: {
|
|
57772
58973
|
type: "object",
|
|
57773
|
-
properties: { cwd: cwdProp, globs: globsProp, standard: standardProp, lang: langProp },
|
|
58974
|
+
properties: { cwd: cwdProp, globs: globsProp, standard: standardProp, lang: langProp, html: htmlProp },
|
|
57774
58975
|
required: ["cwd"]
|
|
57775
58976
|
}
|
|
57776
58977
|
},
|
|
@@ -57902,7 +59103,8 @@ var TOOLS2 = [
|
|
|
57902
59103
|
type: "string",
|
|
57903
59104
|
enum: ["grid", "report"],
|
|
57904
59105
|
description: "`grid` (default) is the criterion \xD7 page matrix; `report` is the per-page dossiers."
|
|
57905
|
-
}
|
|
59106
|
+
},
|
|
59107
|
+
html: htmlProp
|
|
57906
59108
|
},
|
|
57907
59109
|
required: ["cwd"]
|
|
57908
59110
|
}
|
|
@@ -58160,25 +59362,25 @@ function str3(v) {
|
|
|
58160
59362
|
var DECLARED = new Set([...TOOLS2, ...WRITE_TOOLS].map((t2) => t2.name));
|
|
58161
59363
|
|
|
58162
59364
|
// src/mcp/resources.ts
|
|
58163
|
-
import { existsSync as
|
|
58164
|
-
import { basename as basename3, dirname as dirname13, join as
|
|
59365
|
+
import { existsSync as existsSync31, readdirSync as readdirSync6, readFileSync as readFileSync28, realpathSync as realpathSync5, statSync as statSync13 } from "fs";
|
|
59366
|
+
import { basename as basename3, dirname as dirname13, join as join46, resolve as resolve12, sep as sep6 } from "path";
|
|
58165
59367
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
58166
59368
|
var SKILL_NAME = "ultra11y";
|
|
58167
59369
|
var URI_SCHEME = "skill://";
|
|
58168
59370
|
function resolveSkillRoot(moduleDir) {
|
|
58169
59371
|
const here = moduleDir ?? dirname13(fileURLToPath4(import.meta.url));
|
|
58170
59372
|
const candidates2 = [resolve12(here, ".."), resolve12(here, "..", "skills", SKILL_NAME), resolve12(here, "..", "..", "skills", SKILL_NAME)];
|
|
58171
|
-
return candidates2.find((dir) =>
|
|
59373
|
+
return candidates2.find((dir) => existsSync31(join46(dir, "SKILL.md")));
|
|
58172
59374
|
}
|
|
58173
59375
|
function listResources(moduleDir) {
|
|
58174
59376
|
const root = resolveSkillRoot(moduleDir);
|
|
58175
59377
|
if (!root) return [];
|
|
58176
59378
|
const out2 = [describe(root, "SKILL.md", `${SKILL_NAME}: the skill`)];
|
|
58177
|
-
const refDir =
|
|
58178
|
-
if (!
|
|
59379
|
+
const refDir = join46(root, "references");
|
|
59380
|
+
if (!existsSync31(refDir)) return out2;
|
|
58179
59381
|
for (const file of readdirSync6(refDir).sort()) {
|
|
58180
59382
|
if (!file.endsWith(".md")) continue;
|
|
58181
|
-
out2.push(describe(root,
|
|
59383
|
+
out2.push(describe(root, join46("references", file), `${SKILL_NAME} reference: ${basename3(file, ".md")}`));
|
|
58182
59384
|
}
|
|
58183
59385
|
return out2;
|
|
58184
59386
|
}
|
|
@@ -58201,8 +59403,8 @@ function readResource(uri, moduleDir) {
|
|
|
58201
59403
|
if (targetReal !== rootReal && !targetReal.startsWith(rootReal + sep6)) {
|
|
58202
59404
|
throw new ResourceError(`resource path escapes the skill root: ${uri}`);
|
|
58203
59405
|
}
|
|
58204
|
-
if (!
|
|
58205
|
-
return { uri, mimeType: "text/markdown", text:
|
|
59406
|
+
if (!statSync13(targetReal).isFile()) throw new ResourceError(`not a file: ${uri}`);
|
|
59407
|
+
return { uri, mimeType: "text/markdown", text: readFileSync28(targetReal, "utf8") };
|
|
58206
59408
|
}
|
|
58207
59409
|
var ResourceError = class extends Error {
|
|
58208
59410
|
};
|
|
@@ -58213,14 +59415,14 @@ function describe(root, rel2, fallbackTitle) {
|
|
|
58213
59415
|
title: fallbackTitle,
|
|
58214
59416
|
mimeType: "text/markdown"
|
|
58215
59417
|
};
|
|
58216
|
-
const summary = firstProse(
|
|
59418
|
+
const summary = firstProse(join46(root, rel2));
|
|
58217
59419
|
if (summary) decl.description = summary;
|
|
58218
59420
|
return decl;
|
|
58219
59421
|
}
|
|
58220
59422
|
function firstProse(file) {
|
|
58221
59423
|
let text;
|
|
58222
59424
|
try {
|
|
58223
|
-
text =
|
|
59425
|
+
text = readFileSync28(file, "utf8");
|
|
58224
59426
|
} catch {
|
|
58225
59427
|
return void 0;
|
|
58226
59428
|
}
|
|
@@ -58385,7 +59587,7 @@ async function runStdioServer(opts = {}) {
|
|
|
58385
59587
|
let restore;
|
|
58386
59588
|
if (!opts.captureStdout && output === process.stdout) {
|
|
58387
59589
|
const original = process.stdout.write;
|
|
58388
|
-
process.stdout.write = ((
|
|
59590
|
+
process.stdout.write = ((chunk2, ...rest) => process.stderr.write(chunk2, ...rest));
|
|
58389
59591
|
restore = () => {
|
|
58390
59592
|
process.stdout.write = original;
|
|
58391
59593
|
};
|
|
@@ -58633,6 +59835,7 @@ Usage:
|
|
|
58633
59835
|
ultra11y audit [--format sarif|github] (CI: SARIF for code scanning, or inline annotations + job summary)
|
|
58634
59836
|
ultra11y audit --in <audit.json> [--fail-on blocking|major|minor] [--format sarif|github] (re-gate an audit already computed \u2014 e.g. one carrying adjudicated verdicts \u2014 without a second detection pass)
|
|
58635
59837
|
ultra11y report --in <audit.json> [--out <dir>] [--standard <pack>] [--format sarif|github] [--lang auto|en|fr]
|
|
59838
|
+
ultra11y report --in <audit.json> --html [--evidence] [--inline-budget <bytes>] [--out <dir>] (index.html + a printable single file; --evidence adds annotated crops)
|
|
58636
59839
|
ultra11y prd --in <audit.json> [--out <dir>] [--split criterion] [--format audit|doc|remediation] [--no-technical] [--standard <pack>] [--lang auto|en|fr]
|
|
58637
59840
|
ultra11y tickets --in <audit.json> [--provider auto|github|gitlab|jira] [--grain criterion|page|page-criterion|single|file] [--transport auto|cli|rest]
|
|
58638
59841
|
ultra11y tickets [--out <dir>] [--max-tickets <n>] [--dry-run] [--json] [--standard <pack>] [--format audit|remediation] [--lang auto|en|fr]
|
|
@@ -58657,6 +59860,7 @@ Usage:
|
|
|
58657
59860
|
ultra11y snapshot list [--root <dir>] [--json]
|
|
58658
59861
|
ultra11y pages --in <audit.json> [--standard <pack>] [--json] [--lang auto|en|fr] (the per-page criterion grid)
|
|
58659
59862
|
ultra11y pages --in <audit.json> --format report [--split page] [--out <dir>] (the per-page report, with screenshots)
|
|
59863
|
+
ultra11y pages --in <audit.json> --format report --out <dir> [--evidence] [--html] (annotated crops of each non-conformity, and the HTML site)
|
|
58660
59864
|
ultra11y pages discover --sitemap <url> | --crawl <url> | --from-snapshots [--depth <n>] [--max <n>] [--write] [--json] (build the page sample)
|
|
58661
59865
|
ultra11y pages --in <audit.json> --standard <pack> --diff <external.json> (hold the grid against an audit someone else performed)
|
|
58662
59866
|
ultra11y import --from file <report.json> | --from ara <id> [--source <adapter>] [--out <dir>] [--json]
|
|
@@ -59053,6 +60257,8 @@ var VALUE_FLAGS2 = /* @__PURE__ */ new Set([
|
|
|
59053
60257
|
"model",
|
|
59054
60258
|
"pack",
|
|
59055
60259
|
"format",
|
|
60260
|
+
// Bytes of image data the single-file HTML report may carry inline (src/html-emit.ts).
|
|
60261
|
+
"inline-budget",
|
|
59056
60262
|
"guidance",
|
|
59057
60263
|
"runtime",
|
|
59058
60264
|
"cwd",
|
|
@@ -59087,6 +60293,10 @@ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
59087
60293
|
"cross-file",
|
|
59088
60294
|
"json",
|
|
59089
60295
|
"quiet",
|
|
60296
|
+
// The visual tier. Both must be declared here or `parseArgs` swallows the value that
|
|
60297
|
+
// follows and the run prints "unknown flag (ignored)" for a flag that in fact works.
|
|
60298
|
+
"html",
|
|
60299
|
+
"evidence",
|
|
59090
60300
|
"no-default-excludes",
|
|
59091
60301
|
"no-captures",
|
|
59092
60302
|
"require-captures",
|
|
@@ -59236,7 +60446,16 @@ function emitCiFormat(result, format, standard, lang, failOn) {
|
|
|
59236
60446
|
console.error(md);
|
|
59237
60447
|
}
|
|
59238
60448
|
if (process.env.ULTRA11Y_PR_COMMENT === "1") {
|
|
59239
|
-
const
|
|
60449
|
+
const digest = prComment(result, {
|
|
60450
|
+
standard,
|
|
60451
|
+
lang,
|
|
60452
|
+
// The run is known before the artifact exists, so the link is always safe. The artifact
|
|
60453
|
+
// NAME is only set by the action when it actually uploads one — naming an artifact that
|
|
60454
|
+
// was never uploaded sends the reader to a page that does not exist.
|
|
60455
|
+
...process.env.ULTRA11Y_RUN_URL ? { runUrl: process.env.ULTRA11Y_RUN_URL } : {},
|
|
60456
|
+
...process.env.ULTRA11Y_ARTIFACT_NAME ? { artifactName: process.env.ULTRA11Y_ARTIFACT_NAME } : {}
|
|
60457
|
+
});
|
|
60458
|
+
const c2 = pushPrComment(digest, standard);
|
|
59240
60459
|
console.error(
|
|
59241
60460
|
c2.ok ? lang === "fr" ? `ultra11y : commentaire de PR ${c2.action === "updated" ? "mis \xE0 jour" : c2.action === "created" ? "cr\xE9\xE9" : "ignor\xE9"}${c2.reason ? ` (${c2.reason})` : ""}.` : `ultra11y: PR comment ${c2.action}${c2.reason ? ` (${c2.reason})` : ""}.` : lang === "fr" ? `ultra11y : commentaire de PR impossible${c2.reason ? ` \u2014 ${c2.reason}` : ""}.` : `ultra11y: PR comment failed${c2.reason ? ` \u2014 ${c2.reason}` : ""}.`
|
|
59242
60461
|
);
|
|
@@ -59322,9 +60541,9 @@ async function cmdAudit(p) {
|
|
|
59322
60541
|
const capturesFlag = typeof p.flags.captures === "string" && p.flags.captures ? p.flags.captures : void 0;
|
|
59323
60542
|
const capturesDir = capturesFlag ?? CAPTURES_DIR;
|
|
59324
60543
|
const scopedToDiff = p.flags.changed === true || p.flags.staged === true || since !== void 0;
|
|
59325
|
-
const capturesWanted = p.flags["no-captures"] !== true && !inputs.includes("-") && (capturesFlag !== void 0 ||
|
|
60544
|
+
const capturesWanted = p.flags["no-captures"] !== true && !inputs.includes("-") && (capturesFlag !== void 0 || existsSync32(capturesDir));
|
|
59326
60545
|
const useCaptures = capturesWanted && !scopedToDiff && !inputs.includes(capturesDir);
|
|
59327
|
-
const pagesWanted = p.flags["no-captures"] !== true && !inputs.includes("-") &&
|
|
60546
|
+
const pagesWanted = p.flags["no-captures"] !== true && !inputs.includes("-") && existsSync32(PAGES_DIR);
|
|
59328
60547
|
const usePages = pagesWanted && !scopedToDiff && !inputs.includes(PAGES_DIR);
|
|
59329
60548
|
const pagesInScope = pagesWanted && !scopedToDiff;
|
|
59330
60549
|
const auditInputs = [...inputs, ...useCaptures ? [capturesDir] : [], ...usePages ? [PAGES_DIR] : []];
|
|
@@ -59366,10 +60585,10 @@ async function cmdAudit(p) {
|
|
|
59366
60585
|
if (typeof p.flags.out === "string") {
|
|
59367
60586
|
const out2 = p.flags.out;
|
|
59368
60587
|
const asFile = out2.toLowerCase().endsWith(".json");
|
|
59369
|
-
const target = asFile ? out2 :
|
|
60588
|
+
const target = asFile ? out2 : join47(out2, "audit-latest.json");
|
|
59370
60589
|
try {
|
|
59371
|
-
|
|
59372
|
-
|
|
60590
|
+
mkdirSync17(asFile ? dirname14(out2) : out2, { recursive: true });
|
|
60591
|
+
writeFileSync19(target, JSON.stringify(result, null, 2) + "\n");
|
|
59373
60592
|
console.error(lang === "fr" ? `\u2192 audit \xE9crit dans ${target}` : `\u2192 audit written to ${target}`);
|
|
59374
60593
|
} catch {
|
|
59375
60594
|
}
|
|
@@ -59388,7 +60607,7 @@ async function cmdAudit(p) {
|
|
|
59388
60607
|
const baselineFlag = p.flags.baseline;
|
|
59389
60608
|
if (typeof baselineFlag === "string" && baselineFlag) {
|
|
59390
60609
|
let baseline = null;
|
|
59391
|
-
if (
|
|
60610
|
+
if (existsSync32(baselineFlag)) {
|
|
59392
60611
|
try {
|
|
59393
60612
|
const parsed = JSON.parse(readText(baselineFlag));
|
|
59394
60613
|
if (isCurrentAudit(parsed)) baseline = parsed;
|
|
@@ -59442,11 +60661,11 @@ async function cmdDev(p) {
|
|
|
59442
60661
|
return 2;
|
|
59443
60662
|
}
|
|
59444
60663
|
if (p.flags.next === true) {
|
|
59445
|
-
const dir =
|
|
60664
|
+
const dir = join47(root, ".ultra11y", "next");
|
|
59446
60665
|
const rel2 = ".ultra11y/next/overlay.jsx";
|
|
59447
60666
|
try {
|
|
59448
|
-
|
|
59449
|
-
|
|
60667
|
+
mkdirSync17(dir, { recursive: true });
|
|
60668
|
+
writeFileSync19(join47(dir, "overlay.jsx"), nextOverlayComponent(port));
|
|
59450
60669
|
} catch (e) {
|
|
59451
60670
|
console.error(`ultra11y dev: could not write ${rel2}: ${e instanceof Error ? e.message : String(e)}`);
|
|
59452
60671
|
return 1;
|
|
@@ -59495,7 +60714,7 @@ async function cmdPagesDiscover(p) {
|
|
|
59495
60714
|
const snaps = sampleFromSnapshots(readSnapshots(root));
|
|
59496
60715
|
if (!snaps.length) {
|
|
59497
60716
|
console.error(
|
|
59498
|
-
lang === "fr" ? `ultra11y pages discover : aucun instantan\xE9 sous ${
|
|
60717
|
+
lang === "fr" ? `ultra11y pages discover : aucun instantan\xE9 sous ${join47(root, PAGES_DIR)} \u2014 lancez d'abord vos tests E2E avec checkA11y, ou \`scan --sample\`.` : `ultra11y pages discover: no snapshot under ${join47(root, PAGES_DIR)} \u2014 run your E2E tests with checkA11y first, or \`scan --sample\`.`
|
|
59499
60718
|
);
|
|
59500
60719
|
return 1;
|
|
59501
60720
|
}
|
|
@@ -59575,9 +60794,9 @@ ${found} page(s) discovered, ${merged.added.length} new. Nothing was written \u2
|
|
|
59575
60794
|
}
|
|
59576
60795
|
return 0;
|
|
59577
60796
|
}
|
|
59578
|
-
const file =
|
|
60797
|
+
const file = join47(process.cwd(), ".ultra11yrc.json");
|
|
59579
60798
|
let doc = {};
|
|
59580
|
-
if (
|
|
60799
|
+
if (existsSync32(file)) {
|
|
59581
60800
|
try {
|
|
59582
60801
|
doc = JSON.parse(readText(file));
|
|
59583
60802
|
} catch {
|
|
@@ -59586,7 +60805,7 @@ ${found} page(s) discovered, ${merged.added.length} new. Nothing was written \u2
|
|
|
59586
60805
|
}
|
|
59587
60806
|
}
|
|
59588
60807
|
doc.sample = v.sample;
|
|
59589
|
-
|
|
60808
|
+
writeFileSync19(file, `${JSON.stringify(doc, null, 2)}
|
|
59590
60809
|
`);
|
|
59591
60810
|
if (!p.flags.json)
|
|
59592
60811
|
console.log(
|
|
@@ -59646,50 +60865,115 @@ async function cmdPages(p) {
|
|
|
59646
60865
|
const derived = derivePages(result, scope);
|
|
59647
60866
|
const split = p.flags.split === "page";
|
|
59648
60867
|
const outDir = typeof p.flags.out === "string" && p.flags.out ? p.flags.out : void 0;
|
|
59649
|
-
const shotOf = (id) =>
|
|
60868
|
+
const shotOf = (id) => join47(PAGES_DIR, id, "screen.png");
|
|
59650
60869
|
const shotsRelative = (fileDir) => {
|
|
59651
60870
|
const m = /* @__PURE__ */ new Map();
|
|
59652
60871
|
for (const pg of derived) {
|
|
59653
|
-
if (
|
|
60872
|
+
if (existsSync32(shotOf(pg.id))) m.set(pg.id, relative5(fileDir, shotOf(pg.id)).split("\\").join("/"));
|
|
59654
60873
|
}
|
|
59655
60874
|
return m;
|
|
59656
60875
|
};
|
|
60876
|
+
const shotPaths = (pages) => {
|
|
60877
|
+
const m = /* @__PURE__ */ new Map();
|
|
60878
|
+
for (const pg of pages) if (existsSync32(shotOf(pg.id))) m.set(pg.id, shotOf(pg.id));
|
|
60879
|
+
return m;
|
|
60880
|
+
};
|
|
59657
60881
|
const shotsCopiedInto = (dir) => {
|
|
59658
60882
|
const m = /* @__PURE__ */ new Map();
|
|
59659
|
-
const assets =
|
|
60883
|
+
const assets = join47(dir, "assets");
|
|
59660
60884
|
for (const pg of derived) {
|
|
59661
60885
|
const src = shotOf(pg.id);
|
|
59662
|
-
if (!
|
|
59663
|
-
|
|
59664
|
-
copyFileSync4(src,
|
|
60886
|
+
if (!existsSync32(src)) continue;
|
|
60887
|
+
mkdirSync17(assets, { recursive: true });
|
|
60888
|
+
copyFileSync4(src, join47(assets, `${pg.id}.png`));
|
|
59665
60889
|
m.set(pg.id, `./assets/${pg.id}.png`);
|
|
59666
60890
|
}
|
|
59667
60891
|
return m;
|
|
59668
60892
|
};
|
|
60893
|
+
const wantEvidence = p.flags.evidence === true;
|
|
60894
|
+
const wantHtml = p.flags.html === true;
|
|
59669
60895
|
if (!outDir) {
|
|
60896
|
+
if (wantEvidence || wantHtml) {
|
|
60897
|
+
console.error(
|
|
60898
|
+
lang === "fr" ? "ultra11y pages : --evidence et --html demandent --out <dir>. Le mode stdout n'a pas de r\xE9pertoire o\xF9 \xE9crire des images ni \xEAtre auto-suffisant." : "ultra11y pages: --evidence and --html require --out <dir>. Stdout mode has no directory to write images into, nor to be self-contained in."
|
|
60899
|
+
);
|
|
60900
|
+
return 2;
|
|
60901
|
+
}
|
|
59670
60902
|
console.log(renderPagesDocument(result, derived, { standard, lang, screenshots: shotsRelative(".") }));
|
|
59671
60903
|
return 0;
|
|
59672
60904
|
}
|
|
59673
|
-
|
|
60905
|
+
mkdirSync17(outDir, { recursive: true });
|
|
60906
|
+
const manifest = wantEvidence ? writeEvidence(result, { outDir }) : void 0;
|
|
60907
|
+
const cropFor = manifest ? (f) => {
|
|
60908
|
+
const c2 = manifest.crops.get(findingId(f));
|
|
60909
|
+
return c2 ? { href: c2.href, alt: c2.alt[lang] } : void 0;
|
|
60910
|
+
} : void 0;
|
|
60911
|
+
if (manifest) {
|
|
60912
|
+
const total = evidenceNotice(manifest, null, lang);
|
|
60913
|
+
console.error(
|
|
60914
|
+
lang === "fr" ? `ultra11y : ${manifest.totals.imaged} vignette(s) \xE9crite(s) dans ${join47(outDir, "assets")}.` : `ultra11y: ${manifest.totals.imaged} crop(s) written to ${join47(outDir, "assets")}.`
|
|
60915
|
+
);
|
|
60916
|
+
for (const line of total) console.error(line);
|
|
60917
|
+
}
|
|
60918
|
+
const evidenceOpts = {
|
|
60919
|
+
...cropFor ? { cropFor } : {}
|
|
60920
|
+
};
|
|
59674
60921
|
if (!split) {
|
|
59675
|
-
const file =
|
|
59676
|
-
|
|
60922
|
+
const file = join47(outDir, `pages-${result.date}.md`);
|
|
60923
|
+
writeFileSync19(file, `${renderPagesDocument(result, derived, { standard, lang, screenshots: shotsCopiedInto(outDir), ...evidenceOpts })}
|
|
59677
60924
|
`);
|
|
60925
|
+
const html2 = wantHtml ? emitHtml(result, {
|
|
60926
|
+
outDir,
|
|
60927
|
+
standard,
|
|
60928
|
+
lang,
|
|
60929
|
+
layout: "pages",
|
|
60930
|
+
pages: true,
|
|
60931
|
+
screenshots: shotPaths(derived),
|
|
60932
|
+
...manifest ? { evidence: manifest } : {},
|
|
60933
|
+
inlineBudget: budgetOf(p)
|
|
60934
|
+
}) : void 0;
|
|
59678
60935
|
console.log(file);
|
|
59679
|
-
return 0;
|
|
60936
|
+
return html2?.imagesDropped ? 1 : 0;
|
|
59680
60937
|
}
|
|
59681
60938
|
const shots = shotsCopiedInto(outDir);
|
|
59682
60939
|
const sheet = (id) => `page-${id}.md`;
|
|
59683
60940
|
const hrefs = new Map(derived.map((pg) => [pg.id, `./${sheet(pg.id)}`]));
|
|
59684
60941
|
for (const pg of derived) {
|
|
59685
|
-
|
|
59686
|
-
|
|
60942
|
+
const notice = manifest ? evidenceNotice(manifest, pg.id, lang) : [];
|
|
60943
|
+
writeFileSync19(
|
|
60944
|
+
join47(outDir, sheet(pg.id)),
|
|
60945
|
+
`${renderPageDocument(result, pg, { standard, lang, screenshots: shots, ...evidenceOpts, ...notice.length ? { evidenceNotice: notice } : {} })}
|
|
60946
|
+
`
|
|
60947
|
+
);
|
|
59687
60948
|
}
|
|
59688
|
-
const index =
|
|
59689
|
-
|
|
60949
|
+
const index = join47(outDir, "index.md");
|
|
60950
|
+
writeFileSync19(index, `${renderPagesIndex(result, derived, { standard, lang, hrefs })}
|
|
59690
60951
|
`);
|
|
60952
|
+
const html = wantHtml ? emitHtml(result, {
|
|
60953
|
+
outDir,
|
|
60954
|
+
standard,
|
|
60955
|
+
lang,
|
|
60956
|
+
layout: "pages",
|
|
60957
|
+
pages: true,
|
|
60958
|
+
screenshots: shotPaths(derived),
|
|
60959
|
+
...manifest ? { evidence: manifest } : {},
|
|
60960
|
+
inlineBudget: budgetOf(p)
|
|
60961
|
+
}) : void 0;
|
|
59691
60962
|
console.log(index);
|
|
59692
|
-
return 0;
|
|
60963
|
+
return html?.imagesDropped ? 1 : 0;
|
|
60964
|
+
}
|
|
60965
|
+
function budgetOf(p) {
|
|
60966
|
+
const raw = p.flags["inline-budget"];
|
|
60967
|
+
if (typeof raw !== "string" || !raw) return void 0;
|
|
60968
|
+
const n = Number.parseInt(raw, 10);
|
|
60969
|
+
return Number.isFinite(n) && n > 0 ? n : void 0;
|
|
60970
|
+
}
|
|
60971
|
+
function emitHtml(result, opts) {
|
|
60972
|
+
const res = writeHtml(result, opts);
|
|
60973
|
+
for (const n of res.notices) console.error(`ultra11y: ${n}`);
|
|
60974
|
+
console.error(`ultra11y: ${res.index}`);
|
|
60975
|
+
if (res.composite) console.error(`ultra11y: ${res.composite}`);
|
|
60976
|
+
return res;
|
|
59693
60977
|
}
|
|
59694
60978
|
async function cmdSnapshot(p) {
|
|
59695
60979
|
const sub = p.positionals[0];
|
|
@@ -59705,7 +60989,7 @@ async function cmdSnapshot(p) {
|
|
|
59705
60989
|
2
|
|
59706
60990
|
)
|
|
59707
60991
|
);
|
|
59708
|
-
else if (!snaps.length) console.log(lang === "fr" ? `Aucun instantan\xE9 dans ${
|
|
60992
|
+
else if (!snaps.length) console.log(lang === "fr" ? `Aucun instantan\xE9 dans ${join47(root, PAGES_DIR)}.` : `No snapshot in ${join47(root, PAGES_DIR)}.`);
|
|
59709
60993
|
else for (const s of snaps) console.log(`${s.meta.id} ${s.meta.name} ${s.meta.url}${s.meta.auth ? " [auth]" : ""}`);
|
|
59710
60994
|
return 0;
|
|
59711
60995
|
}
|
|
@@ -59752,7 +61036,7 @@ async function cmdSnapshot(p) {
|
|
|
59752
61036
|
console.error(`ultra11y snapshot write: could not write the snapshot: ${e instanceof Error ? e.message : String(e)}`);
|
|
59753
61037
|
return 1;
|
|
59754
61038
|
}
|
|
59755
|
-
const result = runAudit({ inputs: [
|
|
61039
|
+
const result = runAudit({ inputs: [join47(dir, "dom.html")], onWarn: (m) => console.error(m) });
|
|
59756
61040
|
const failOnRaw = p.flags["fail-on"];
|
|
59757
61041
|
const failOnParsed = parseFailOn(failOnRaw);
|
|
59758
61042
|
if (failOnRaw !== void 0 && failOnParsed === null) {
|
|
@@ -59784,9 +61068,9 @@ function cmdInit(p) {
|
|
|
59784
61068
|
if (want.baseline) {
|
|
59785
61069
|
const inputs = p.positionals.length ? p.positionals : ["."];
|
|
59786
61070
|
const result = runAudit({ inputs, onWarn: (m) => console.error(m) });
|
|
59787
|
-
|
|
59788
|
-
const bp =
|
|
59789
|
-
|
|
61071
|
+
mkdirSync17(join47(root, "audits"), { recursive: true });
|
|
61072
|
+
const bp = join47(root, "audits", "baseline.json");
|
|
61073
|
+
writeFileSync19(bp, JSON.stringify(result, null, 2) + "\n");
|
|
59790
61074
|
wrote.push(bp);
|
|
59791
61075
|
}
|
|
59792
61076
|
if (want.hook) wrote.push(writeHook(root, engineRel, failOn, legacy ? "baseline" : "staged"));
|
|
@@ -59911,11 +61195,28 @@ async function cmdReport(p) {
|
|
|
59911
61195
|
console.error(`ultra11y report: --format must be sarif|github (got "${String(p.flags.format)}").`);
|
|
59912
61196
|
return 2;
|
|
59913
61197
|
}
|
|
61198
|
+
if (ciFormat && p.flags.html === true) {
|
|
61199
|
+
console.error(
|
|
61200
|
+
lang === "fr" ? `ultra11y report : --html et --format ${ciFormat} sont incompatibles. --format nomme un canal CI, --html un document ; choisissez l'un ou l'autre.` : `ultra11y report: --html and --format ${ciFormat} are incompatible. --format names a CI channel, --html names a document; pick one.`
|
|
61201
|
+
);
|
|
61202
|
+
return 2;
|
|
61203
|
+
}
|
|
59914
61204
|
if (ciFormat) {
|
|
59915
61205
|
emitCiFormat(result, ciFormat, standard, lang);
|
|
59916
61206
|
return 0;
|
|
59917
61207
|
}
|
|
59918
61208
|
const path = writeReport(result, { out: out2, lang, standard });
|
|
61209
|
+
let html;
|
|
61210
|
+
if (p.flags.html === true) {
|
|
61211
|
+
const manifest = p.flags.evidence === true ? writeEvidence(result, { outDir: out2 }) : void 0;
|
|
61212
|
+
if (manifest) for (const line of evidenceNotice(manifest, null, lang)) console.error(line);
|
|
61213
|
+
html = emitHtml(result, { outDir: out2, standard, lang, ...manifest ? { evidence: manifest } : {}, inlineBudget: budgetOf(p) });
|
|
61214
|
+
} else if (p.flags.evidence === true) {
|
|
61215
|
+
console.error(
|
|
61216
|
+
lang === "fr" ? "ultra11y report : --evidence n'illustre que le rapport HTML. Ajoutez --html, ou utilisez `pages --format report --evidence` pour des fiches Markdown illustr\xE9es." : "ultra11y report: --evidence only illustrates the HTML report. Add --html, or use `pages --format report --evidence` for illustrated Markdown sheets."
|
|
61217
|
+
);
|
|
61218
|
+
return 2;
|
|
61219
|
+
}
|
|
59919
61220
|
const untested = isCore(standard) ? [] : untestedNeedsRendering(result);
|
|
59920
61221
|
const partial = untested.length > 0;
|
|
59921
61222
|
if (partial && !p.flags.json) console.error(`\u{1F6A8} ${partialAuditBanner(lang, untested)}`);
|
|
@@ -59927,6 +61228,7 @@ async function cmdReport(p) {
|
|
|
59927
61228
|
conformancePct: result.conformancePct,
|
|
59928
61229
|
date: result.date,
|
|
59929
61230
|
standard: typeof p.flags.standard === "string" ? p.flags.standard : "wcag",
|
|
61231
|
+
...html ? { htmlPath: html.index, htmlSinglePath: html.composite } : {},
|
|
59930
61232
|
...partial ? { partialAudit: true, untestedCriteria: untested } : {}
|
|
59931
61233
|
},
|
|
59932
61234
|
null,
|
|
@@ -59934,7 +61236,7 @@ async function cmdReport(p) {
|
|
|
59934
61236
|
)
|
|
59935
61237
|
);
|
|
59936
61238
|
else console.log(path);
|
|
59937
|
-
return 0;
|
|
61239
|
+
return html?.imagesDropped ? 1 : 0;
|
|
59938
61240
|
}
|
|
59939
61241
|
async function cmdPrd(p) {
|
|
59940
61242
|
const inFlag = p.flags.in;
|
|
@@ -60066,8 +61368,8 @@ async function cmdTickets(p) {
|
|
|
60066
61368
|
const ticketsOut = typeof p.flags.out === "string" && p.flags.out ? p.flags.out : void 0;
|
|
60067
61369
|
let setPath;
|
|
60068
61370
|
if (ticketsOut) {
|
|
60069
|
-
|
|
60070
|
-
setPath =
|
|
61371
|
+
mkdirSync17(ticketsOut, { recursive: true });
|
|
61372
|
+
setPath = join47(ticketsOut, `issues-${result.date}.json`);
|
|
60071
61373
|
const payload = {
|
|
60072
61374
|
tool: "ultra11y",
|
|
60073
61375
|
kind: "issues",
|
|
@@ -60078,7 +61380,7 @@ async function cmdTickets(p) {
|
|
|
60078
61380
|
count: plan.tickets.length,
|
|
60079
61381
|
issues: plan.tickets
|
|
60080
61382
|
};
|
|
60081
|
-
|
|
61383
|
+
writeFileSync19(setPath, `${JSON.stringify(payload, null, 2)}
|
|
60082
61384
|
`);
|
|
60083
61385
|
if (!json) console.log(setPath);
|
|
60084
61386
|
}
|
|
@@ -60168,8 +61470,8 @@ async function cmdTickets(p) {
|
|
|
60168
61470
|
return pushed.failed > 0 ? 1 : 0;
|
|
60169
61471
|
}
|
|
60170
61472
|
function depsAt(root) {
|
|
60171
|
-
const pkgPath =
|
|
60172
|
-
if (!
|
|
61473
|
+
const pkgPath = join47(root, "package.json");
|
|
61474
|
+
if (!existsSync32(pkgPath)) return {};
|
|
60173
61475
|
try {
|
|
60174
61476
|
const pkg = JSON.parse(readText(pkgPath));
|
|
60175
61477
|
return { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
@@ -60195,24 +61497,24 @@ function cmdRender(p) {
|
|
|
60195
61497
|
console.error(`ultra11y render: --runner must be playwright|cypress|auto (got "${forced}").`);
|
|
60196
61498
|
return 2;
|
|
60197
61499
|
}
|
|
60198
|
-
const detected = detectE2eRunner(depsAt(root), (f) =>
|
|
61500
|
+
const detected = detectE2eRunner(depsAt(root), (f) => existsSync32(join47(root, f)));
|
|
60199
61501
|
const runners = forced && forced !== "auto" ? [forced] : detected;
|
|
60200
61502
|
if (!runners.length) {
|
|
60201
61503
|
console.error(e2eSetupPlan([], {}, lang));
|
|
60202
61504
|
return 1;
|
|
60203
61505
|
}
|
|
60204
61506
|
const engineRef = engineRefFor(root);
|
|
60205
|
-
const dir =
|
|
61507
|
+
const dir = join47(root, ".ultra11y", "e2e");
|
|
60206
61508
|
const paths = {};
|
|
60207
61509
|
try {
|
|
60208
|
-
|
|
61510
|
+
mkdirSync17(dir, { recursive: true });
|
|
60209
61511
|
if (runners.includes("playwright")) {
|
|
60210
|
-
|
|
61512
|
+
writeFileSync19(join47(dir, "playwright.mjs"), playwrightFixture(engineRef));
|
|
60211
61513
|
paths.playwright = ".ultra11y/e2e/playwright.mjs";
|
|
60212
61514
|
}
|
|
60213
61515
|
if (runners.includes("cypress")) {
|
|
60214
|
-
|
|
60215
|
-
|
|
61516
|
+
writeFileSync19(join47(dir, "cypress-plugin.mjs"), cypressPlugin(engineRef));
|
|
61517
|
+
writeFileSync19(join47(dir, "cypress-commands.mjs"), cypressCommands());
|
|
60216
61518
|
paths.cypressPlugin = ".ultra11y/e2e/cypress-plugin.mjs";
|
|
60217
61519
|
paths.cypressCommands = ".ultra11y/e2e/cypress-commands.mjs";
|
|
60218
61520
|
}
|
|
@@ -60227,7 +61529,7 @@ function cmdRender(p) {
|
|
|
60227
61529
|
if (p.flags.scaffold === true) {
|
|
60228
61530
|
const out2 = typeof p.flags.out === "string" && p.flags.out ? p.flags.out : "ultra11y-render.tsx";
|
|
60229
61531
|
try {
|
|
60230
|
-
|
|
61532
|
+
writeFileSync19(out2, ssrHarness());
|
|
60231
61533
|
} catch (e) {
|
|
60232
61534
|
console.error(`ultra11y render: could not write ${out2}: ${e instanceof Error ? e.message : String(e)}`);
|
|
60233
61535
|
return 1;
|
|
@@ -60241,29 +61543,29 @@ Fill in COMPONENTS, run it (e.g. npx tsx ${out2}), then: node scripts/ultra11y.m
|
|
|
60241
61543
|
}
|
|
60242
61544
|
if (p.flags.setup === true) {
|
|
60243
61545
|
const rel2 = ".ultra11y/capture-setup.mjs";
|
|
60244
|
-
const out2 =
|
|
61546
|
+
const out2 = join47(root, rel2);
|
|
60245
61547
|
try {
|
|
60246
|
-
|
|
60247
|
-
|
|
61548
|
+
mkdirSync17(dirname14(out2), { recursive: true });
|
|
61549
|
+
writeFileSync19(out2, captureSetup());
|
|
60248
61550
|
} catch (e) {
|
|
60249
61551
|
console.error(`ultra11y render: could not write ${out2}: ${e instanceof Error ? e.message : String(e)}`);
|
|
60250
61552
|
return 1;
|
|
60251
61553
|
}
|
|
60252
61554
|
let setupDeps = {};
|
|
60253
|
-
const setupPkg =
|
|
60254
|
-
if (
|
|
61555
|
+
const setupPkg = join47(root, "package.json");
|
|
61556
|
+
if (existsSync32(setupPkg)) {
|
|
60255
61557
|
try {
|
|
60256
61558
|
const pkg = JSON.parse(readText(setupPkg));
|
|
60257
61559
|
setupDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
60258
61560
|
} catch {
|
|
60259
61561
|
}
|
|
60260
61562
|
}
|
|
60261
|
-
const tr = detectTestRunner(setupDeps, (f) =>
|
|
61563
|
+
const tr = detectTestRunner(setupDeps, (f) => existsSync32(join47(root, f)));
|
|
60262
61564
|
console.log(captureSetupPlan(tr, rel2, lang));
|
|
60263
61565
|
const gaLine = ".ultra11y/captures/*.html text eol=lf linguist-generated=true";
|
|
60264
|
-
const gaPath =
|
|
61566
|
+
const gaPath = join47(root, ".gitattributes");
|
|
60265
61567
|
try {
|
|
60266
|
-
const existing =
|
|
61568
|
+
const existing = existsSync32(gaPath) ? readFileSync29(gaPath, "utf8") : "";
|
|
60267
61569
|
if (!existing.includes(".ultra11y/captures/")) {
|
|
60268
61570
|
appendFileSync(gaPath, (existing && !existing.endsWith("\n") ? "\n" : "") + gaLine + "\n");
|
|
60269
61571
|
console.log(lang === "fr" ? `.gitattributes : ajout\xE9 \xAB ${gaLine} \xBB` : `.gitattributes: added "${gaLine}"`);
|
|
@@ -60271,8 +61573,8 @@ Fill in COMPONENTS, run it (e.g. npx tsx ${out2}), then: node scripts/ultra11y.m
|
|
|
60271
61573
|
} catch {
|
|
60272
61574
|
}
|
|
60273
61575
|
try {
|
|
60274
|
-
const giPath =
|
|
60275
|
-
if (
|
|
61576
|
+
const giPath = join47(root, ".gitignore");
|
|
61577
|
+
if (existsSync32(giPath) && /^\s*\/?\.ultra11y(\/\**)?\/?\s*$/m.test(readFileSync29(giPath, "utf8")))
|
|
60276
61578
|
console.error(
|
|
60277
61579
|
lang === "fr" ? "\u26A0\uFE0F .ultra11y semble ignor\xE9 par .gitignore \u2014 les captures doivent \xEAtre committ\xE9es pour le gate (ajoutez \xAB !.ultra11y/captures/ \xBB)." : '\u26A0\uFE0F .ultra11y appears gitignored \u2014 captures must be committed for the gate (add "!.ultra11y/captures/").'
|
|
60278
61580
|
);
|
|
@@ -60282,8 +61584,8 @@ Fill in COMPONENTS, run it (e.g. npx tsx ${out2}), then: node scripts/ultra11y.m
|
|
|
60282
61584
|
}
|
|
60283
61585
|
if (p.flags.storybook === true || typeof p.flags.storybook === "string") {
|
|
60284
61586
|
const sbDir = p.positionals[0] ?? "storybook-static";
|
|
60285
|
-
const indexPath =
|
|
60286
|
-
if (!
|
|
61587
|
+
const indexPath = existsSync32(join47(sbDir, "index.json")) ? join47(sbDir, "index.json") : join47(sbDir, "stories.json");
|
|
61588
|
+
if (!existsSync32(indexPath)) {
|
|
60287
61589
|
console.error(
|
|
60288
61590
|
lang === "fr" ? `ultra11y render : aucun index Storybook (index.json/stories.json) dans ${sbDir}.` : `ultra11y render: no Storybook index (index.json/stories.json) in ${sbDir}.`
|
|
60289
61591
|
);
|
|
@@ -60293,7 +61595,7 @@ Fill in COMPONENTS, run it (e.g. npx tsx ${out2}), then: node scripts/ultra11y.m
|
|
|
60293
61595
|
const provById = new Map(stories.map((s) => [s.id, storyProvenance(s)]));
|
|
60294
61596
|
const capturesFlag = typeof p.flags.captures === "string" && p.flags.captures ? p.flags.captures : void 0;
|
|
60295
61597
|
const htmlDir = capturesFlag ?? sbDir;
|
|
60296
|
-
const htmlFiles =
|
|
61598
|
+
const htmlFiles = existsSync32(htmlDir) ? discover([htmlDir]).files.filter((f) => /\.html?$/i.test(f)) : [];
|
|
60297
61599
|
const outDir = ".ultra11y/captures";
|
|
60298
61600
|
let attributed = 0;
|
|
60299
61601
|
let skipped = 0;
|
|
@@ -60315,8 +61617,8 @@ Fill in COMPONENTS, run it (e.g. npx tsx ${out2}), then: node scripts/ultra11y.m
|
|
|
60315
61617
|
continue;
|
|
60316
61618
|
}
|
|
60317
61619
|
try {
|
|
60318
|
-
|
|
60319
|
-
|
|
61620
|
+
mkdirSync17(outDir, { recursive: true });
|
|
61621
|
+
writeFileSync19(join47(outDir, `${hitId}.html`), `${formatCaptureComment(prov)}
|
|
60320
61622
|
${raw}${raw.endsWith("\n") ? "" : "\n"}`);
|
|
60321
61623
|
attributed++;
|
|
60322
61624
|
} catch {
|
|
@@ -60338,11 +61640,11 @@ ${raw}${raw.endsWith("\n") ? "" : "\n"}`);
|
|
|
60338
61640
|
}
|
|
60339
61641
|
if (p.flags.coverage === true) {
|
|
60340
61642
|
const capturesFlag = typeof p.flags.captures === "string" && p.flags.captures ? p.flags.captures : void 0;
|
|
60341
|
-
const capturesDir = capturesFlag ??
|
|
61643
|
+
const capturesDir = capturesFlag ?? join47(root, ".ultra11y/captures");
|
|
60342
61644
|
const graphExt = [...GRAPH_ONLY_EXT, ...asList(p.flags.ext) ?? []];
|
|
60343
61645
|
const sourceFiles = discover([root], { include: asList(p.flags.include), exclude: asList(p.flags.exclude), ext: graphExt }).files;
|
|
60344
61646
|
const graph = buildGraphStreaming(sourceFiles);
|
|
60345
|
-
const capFiles =
|
|
61647
|
+
const capFiles = existsSync32(capturesDir) ? discover([capturesDir]).files : [];
|
|
60346
61648
|
const entries = capFiles.map((f) => ({ file: toPosix(f), provenance: parseCaptureProvenance(readText(f)) }));
|
|
60347
61649
|
const cov = computeCaptureCoverage(graph, entries);
|
|
60348
61650
|
if (p.flags.json) console.log(JSON.stringify(cov, null, 2));
|
|
@@ -60350,15 +61652,15 @@ ${raw}${raw.endsWith("\n") ? "" : "\n"}`);
|
|
|
60350
61652
|
return 0;
|
|
60351
61653
|
}
|
|
60352
61654
|
let deps = {};
|
|
60353
|
-
const pkgPath =
|
|
60354
|
-
if (
|
|
61655
|
+
const pkgPath = join47(root, "package.json");
|
|
61656
|
+
if (existsSync32(pkgPath)) {
|
|
60355
61657
|
try {
|
|
60356
61658
|
const pkg = JSON.parse(readText(pkgPath));
|
|
60357
61659
|
deps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
60358
61660
|
} catch {
|
|
60359
61661
|
}
|
|
60360
61662
|
}
|
|
60361
|
-
const detection = detectFrameworks(deps, (f) =>
|
|
61663
|
+
const detection = detectFrameworks(deps, (f) => existsSync32(join47(root, f)));
|
|
60362
61664
|
if (p.flags.json) console.log(JSON.stringify(detection, null, 2));
|
|
60363
61665
|
else console.log(renderPlan(detection, lang));
|
|
60364
61666
|
return 0;
|
|
@@ -60557,9 +61859,9 @@ function applyAdjudicationFile(p, adj, lang) {
|
|
|
60557
61859
|
return 1;
|
|
60558
61860
|
}
|
|
60559
61861
|
const out2 = typeof p.flags.out === "string" ? p.flags.out : ".";
|
|
60560
|
-
|
|
60561
|
-
const auditPath =
|
|
60562
|
-
|
|
61862
|
+
mkdirSync17(out2, { recursive: true });
|
|
61863
|
+
const auditPath = join47(out2, "audit-latest.json");
|
|
61864
|
+
writeFileSync19(auditPath, JSON.stringify(r.audit, null, 2) + "\n");
|
|
60563
61865
|
if (p.flags.json)
|
|
60564
61866
|
console.log(
|
|
60565
61867
|
JSON.stringify(
|
|
@@ -60667,9 +61969,9 @@ async function cmdJudge(p) {
|
|
|
60667
61969
|
if (r.issues.length > 40) console.error(` \u2026 +${r.issues.length - 40}`);
|
|
60668
61970
|
return 1;
|
|
60669
61971
|
}
|
|
60670
|
-
|
|
60671
|
-
const auditPath =
|
|
60672
|
-
|
|
61972
|
+
mkdirSync17(out2, { recursive: true });
|
|
61973
|
+
const auditPath = join47(out2, "audit-latest.json");
|
|
61974
|
+
writeFileSync19(auditPath, `${JSON.stringify(r.audit, null, 2)}
|
|
60673
61975
|
`);
|
|
60674
61976
|
console.log(
|
|
60675
61977
|
lang === "fr" ? `\u2713 ${r.applied} crit\xE8re(s) adjug\xE9(s), ${r.stillManual} laiss\xE9(s) en r\xE9siduel \u2192 ${auditPath}` : `\u2713 ${r.applied} criterion(ia) adjudicated, ${r.stillManual} left residual \u2192 ${auditPath}`
|
|
@@ -60738,7 +62040,7 @@ async function cmdScan(p) {
|
|
|
60738
62040
|
return 0;
|
|
60739
62041
|
}
|
|
60740
62042
|
for (const target of p.positionals.filter((a) => a !== "-")) {
|
|
60741
|
-
if (/^https?:\/\//i.test(target) ||
|
|
62043
|
+
if (/^https?:\/\//i.test(target) || existsSync32(target)) continue;
|
|
60742
62044
|
console.error(
|
|
60743
62045
|
lang === "fr" ? `ultra11y scan : fichier introuvable (file not found) : ${target}. Passez une URL http(s):// ou un fichier HTML existant.` : `ultra11y scan: File not found: ${target}. Pass an http(s):// URL or an existing HTML file.`
|
|
60744
62046
|
);
|
|
@@ -60865,7 +62167,7 @@ async function cmdScan(p) {
|
|
|
60865
62167
|
}
|
|
60866
62168
|
let merged = mergeDynamic(audit2, dynamic, lang);
|
|
60867
62169
|
if (dynamic.snapshots?.length) {
|
|
60868
|
-
const doms = dynamic.snapshots.map((id) =>
|
|
62170
|
+
const doms = dynamic.snapshots.map((id) => join47(snapshotRoot ?? ".", PAGES_DIR, id, "dom.html")).filter((f) => existsSync32(f));
|
|
60869
62171
|
if (doms.length) {
|
|
60870
62172
|
const snapAudit = runAudit({ inputs: doms, onWarn: (m) => console.error(m) });
|
|
60871
62173
|
merged = mergeSnapshotAudit(merged, snapAudit);
|
|
@@ -60876,12 +62178,12 @@ async function cmdScan(p) {
|
|
|
60876
62178
|
attributePages(merged, scope);
|
|
60877
62179
|
}
|
|
60878
62180
|
}
|
|
60879
|
-
|
|
60880
|
-
|
|
62181
|
+
mkdirSync17(out2, { recursive: true });
|
|
62182
|
+
writeFileSync19(join47(out2, "audit-latest.json"), JSON.stringify(merged, null, 2) + "\n");
|
|
60881
62183
|
if (p.flags.json) console.log(JSON.stringify(merged, null, 2));
|
|
60882
62184
|
else {
|
|
60883
62185
|
console.log(
|
|
60884
|
-
lang === "fr" ? `Audit statique + dynamique fusionn\xE9 \u2192 ${
|
|
62186
|
+
lang === "fr" ? `Audit statique + dynamique fusionn\xE9 \u2192 ${join47(out2, "audit-latest.json")} (${merged.conformancePct}% r\xE9ussite, ${merged.findings.length} findings).` : `Static + dynamic audit merged \u2192 ${join47(out2, "audit-latest.json")} (${merged.conformancePct}% pass rate, ${merged.findings.length} findings).`
|
|
60885
62187
|
);
|
|
60886
62188
|
if (dynamic.snapshots?.length)
|
|
60887
62189
|
console.log(
|
|
@@ -61123,9 +62425,9 @@ async function cmdImport(p) {
|
|
|
61123
62425
|
return 1;
|
|
61124
62426
|
}
|
|
61125
62427
|
if (outDir) {
|
|
61126
|
-
|
|
61127
|
-
const rawFile =
|
|
61128
|
-
|
|
62428
|
+
mkdirSync17(outDir, { recursive: true });
|
|
62429
|
+
const rawFile = join47(outDir, `external-${adapterId}-${arg}.raw.json`);
|
|
62430
|
+
writeFileSync19(rawFile, rawText.endsWith("\n") ? rawText : `${rawText}
|
|
61129
62431
|
`);
|
|
61130
62432
|
if (!p.flags.json) console.log(rawFile);
|
|
61131
62433
|
}
|
|
@@ -61159,9 +62461,9 @@ async function cmdImport(p) {
|
|
|
61159
62461
|
return 0;
|
|
61160
62462
|
}
|
|
61161
62463
|
if (outDir) {
|
|
61162
|
-
|
|
61163
|
-
const file =
|
|
61164
|
-
|
|
62464
|
+
mkdirSync17(outDir, { recursive: true });
|
|
62465
|
+
const file = join47(outDir, "external-latest.json");
|
|
62466
|
+
writeFileSync19(file, `${JSON.stringify(audit2, null, 2)}
|
|
61165
62467
|
`);
|
|
61166
62468
|
console.log(file);
|
|
61167
62469
|
} else {
|
|
@@ -61266,7 +62568,7 @@ function cmdOrchestrate(p) {
|
|
|
61266
62568
|
}
|
|
61267
62569
|
const engineAbs = realpathSync6(fileURLToPath5(import.meta.url));
|
|
61268
62570
|
if (p.flags.list === true) {
|
|
61269
|
-
if (!
|
|
62571
|
+
if (!existsSync32(runFlag)) {
|
|
61270
62572
|
console.error(`ultra11y orchestrate: run dir not found: ${runFlag}.`);
|
|
61271
62573
|
return 2;
|
|
61272
62574
|
}
|
|
@@ -61293,7 +62595,7 @@ function cmdOrchestrate(p) {
|
|
|
61293
62595
|
);
|
|
61294
62596
|
} else {
|
|
61295
62597
|
console.log(
|
|
61296
|
-
lang === "fr" ? `Suivez ${
|
|
62598
|
+
lang === "fr" ? `Suivez ${join47(runFlag, "orchestration", "RUNBOOK.md")} s\xE9quentiellement (chemin \xE9co).` : `Follow ${join47(runFlag, "orchestration", "RUNBOOK.md")} sequentially (the eco path).`
|
|
61297
62599
|
);
|
|
61298
62600
|
}
|
|
61299
62601
|
if (p.flags.phase === void 0 && workflows.length === 0 && p.flags.eco !== true) {
|