ultra11y 4.0.1 → 4.2.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 +28 -0
- package/package.json +1 -1
- package/scripts/ultra11y.mjs +1685 -344
- package/skills/review-a11y/SKILL.md +1 -1
- package/skills/review-a11y/scripts/ultra11y.mjs +1685 -344
- package/skills/ultra11y/SKILL.md +1 -1
- package/skills/ultra11y/references/ci.md +109 -0
- package/skills/ultra11y/references/pages.md +76 -0
- package/skills/ultra11y/scripts/ultra11y.mjs +1685 -344
- package/src/annotate.ts +180 -15
- package/src/auditor.ts +115 -43
- package/src/cli.ts +187 -9
- package/src/dev.ts +23 -75
- package/src/evidence.ts +72 -37
- package/src/html-emit.ts +327 -0
- package/src/html-report.ts +482 -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 +40 -21
- package/src/pages.ts +28 -4
- package/src/report.ts +84 -31
- package/src/tickets/providers/github.ts +9 -4
- 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.0
|
|
9
|
+
var VERSION = "4.2.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));
|
|
@@ -49594,12 +49748,13 @@ function render(r, lang, opts) {
|
|
|
49594
49748
|
const group = ncUnits.filter((u) => u.severity === sev);
|
|
49595
49749
|
if (!group.length) continue;
|
|
49596
49750
|
out2.push(`### ${ICON3[sev]} ${s.sev[sev]} (${group.length})`, "");
|
|
49597
|
-
for (const u of group) out2.push(...renderAuditorUnit(u, opts.standard, lang, { heading: "####" }));
|
|
49751
|
+
for (const u of group) out2.push(...renderAuditorUnit(u, opts.standard, lang, { heading: "####", ...opts.cropFor ? { cropFor: opts.cropFor } : {} }));
|
|
49598
49752
|
}
|
|
49599
49753
|
}
|
|
49600
49754
|
if (advisoryUnits.length) {
|
|
49601
49755
|
out2.push(`## \u{1F4A1} ${s.recTitle}`, "", `> ${s.recNote}`, "");
|
|
49602
|
-
for (const u of advisoryUnits)
|
|
49756
|
+
for (const u of advisoryUnits)
|
|
49757
|
+
out2.push(...renderAuditorUnit(u, opts.standard, lang, { heading: "###", ...opts.cropFor ? { cropFor: opts.cropFor } : {} }));
|
|
49603
49758
|
}
|
|
49604
49759
|
const pageScope = pagesOf(r);
|
|
49605
49760
|
if (pageScope.length) attributePages(r, pageScope);
|
|
@@ -49667,8 +49822,7 @@ function render(r, lang, opts) {
|
|
|
49667
49822
|
}
|
|
49668
49823
|
return out2.join("\n");
|
|
49669
49824
|
}
|
|
49670
|
-
function
|
|
49671
|
-
const s = L4[lang];
|
|
49825
|
+
function reportGroups(r, lang = "en") {
|
|
49672
49826
|
const byGuideline = /* @__PURE__ */ new Map();
|
|
49673
49827
|
for (const c2 of r.criteria) {
|
|
49674
49828
|
const title2 = scTitle(c2.id, lang);
|
|
@@ -49682,12 +49836,14 @@ function renderReport(r, lang = "en", outDir) {
|
|
|
49682
49836
|
};
|
|
49683
49837
|
(byGuideline.get(c2.guideline) ?? byGuideline.set(c2.guideline, []).get(c2.guideline)).push(row);
|
|
49684
49838
|
}
|
|
49685
|
-
|
|
49686
|
-
|
|
49839
|
+
return r.guidelines.map((g) => ({ key: g.key, title: guidelineTitle(g.key, lang) ?? g.title, rows: byGuideline.get(g.key) ?? [] }));
|
|
49840
|
+
}
|
|
49841
|
+
function renderReport(r, lang = "en", outDir, cropFor) {
|
|
49842
|
+
const s = L4[lang];
|
|
49843
|
+
return render(r, lang, { std: s.wcagStd, groupHead: s.byGuideline, groups: reportGroups(r, lang), standard: CORE2, outDir, ...cropFor ? { cropFor } : {} });
|
|
49687
49844
|
}
|
|
49688
|
-
function
|
|
49845
|
+
function packReportGroups(r, pack, lang = "en") {
|
|
49689
49846
|
const derived = derivePackResults(r, pack.key);
|
|
49690
|
-
const std = `${pack.name} ${pack.baseVersion}`;
|
|
49691
49847
|
const s = L4[lang];
|
|
49692
49848
|
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
49849
|
const byTheme = /* @__PURE__ */ new Map();
|
|
@@ -49705,11 +49861,15 @@ function renderPackReport(r, pack, lang = "en", outDir) {
|
|
|
49705
49861
|
};
|
|
49706
49862
|
(byTheme.get(pr.theme) ?? byTheme.set(pr.theme, []).get(pr.theme)).push(row);
|
|
49707
49863
|
}
|
|
49708
|
-
|
|
49864
|
+
return pack.themes.map((t2) => ({ key: `${t2.number}.`, title: themeName(pack, t2.number, lang) ?? "", rows: byTheme.get(t2.number) ?? [] }));
|
|
49865
|
+
}
|
|
49866
|
+
function renderPackReport(r, pack, lang = "en", outDir, cropFor) {
|
|
49867
|
+
const derived = derivePackResults(r, pack.key);
|
|
49868
|
+
const std = `${pack.name} ${pack.baseVersion}`;
|
|
49709
49869
|
return render(r, lang, {
|
|
49710
49870
|
std,
|
|
49711
49871
|
groupHead: L4[lang].byTheme,
|
|
49712
|
-
groups,
|
|
49872
|
+
groups: packReportGroups(r, pack, lang),
|
|
49713
49873
|
derivedOf: std,
|
|
49714
49874
|
standard: pack.key,
|
|
49715
49875
|
partialAudit: untestedNeedsRendering(r),
|
|
@@ -49717,12 +49877,13 @@ function renderPackReport(r, pack, lang = "en", outDir) {
|
|
|
49717
49877
|
// Forwarded, unlike before: without it the per-page screenshots resolved against the
|
|
49718
49878
|
// CWD instead of the report's own directory, so a pack report written to `audits/`
|
|
49719
49879
|
// carried links that only worked when read from the repo root.
|
|
49720
|
-
outDir
|
|
49880
|
+
outDir,
|
|
49881
|
+
...cropFor ? { cropFor } : {}
|
|
49721
49882
|
});
|
|
49722
49883
|
}
|
|
49723
49884
|
function writeReport(r, opts) {
|
|
49724
49885
|
const core = isCore(opts.standard);
|
|
49725
|
-
const md = core ? renderReport(r, opts.lang, opts.out) : renderPackReport(r, loadPack(opts.standard), opts.lang, opts.out);
|
|
49886
|
+
const md = core ? renderReport(r, opts.lang, opts.out, opts.cropFor) : renderPackReport(r, loadPack(opts.standard), opts.lang, opts.out, opts.cropFor);
|
|
49726
49887
|
mkdirSync7(opts.out, { recursive: true });
|
|
49727
49888
|
const path = join29(opts.out, `${core ? "wcag" : opts.standard}-${r.date}.md`);
|
|
49728
49889
|
writeFileSync8(path, md);
|
|
@@ -50213,17 +50374,19 @@ function resolve4(opts) {
|
|
|
50213
50374
|
const mode = opts.transport ?? "auto";
|
|
50214
50375
|
const api = (env.GITHUB_API_URL || "https://api.github.com").replace(/\/+$/, "");
|
|
50215
50376
|
const token = env.GH_TOKEN || env.GITHUB_TOKEN;
|
|
50216
|
-
const
|
|
50377
|
+
const probe = opts.cliAvailable ?? ghAvailable;
|
|
50378
|
+
let cached;
|
|
50379
|
+
const cliOk = () => cached ??= probe();
|
|
50217
50380
|
if (mode === "cli") {
|
|
50218
|
-
return cliOk ? { transport: "cli", api } : { transport: "cli", api, reason: "`gh` is not installed or not authenticated (run `gh auth login`)" };
|
|
50381
|
+
return cliOk() ? { transport: "cli", api } : { transport: "cli", api, reason: "`gh` is not installed or not authenticated (run `gh auth login`)" };
|
|
50219
50382
|
}
|
|
50220
|
-
const repo = env.ULTRA11Y_GITHUB_REPO || env.GITHUB_REPOSITORY || (mode === "rest" || !cliOk ? gitRemoteSlug("github.com") : void 0);
|
|
50383
|
+
const repo = env.ULTRA11Y_GITHUB_REPO || env.GITHUB_REPOSITORY || (mode === "rest" || !cliOk() ? gitRemoteSlug("github.com") : void 0);
|
|
50221
50384
|
if (mode === "rest") {
|
|
50222
50385
|
if (!token) return { transport: "rest", api, reason: "no GitHub token \u2014 set GH_TOKEN or GITHUB_TOKEN" };
|
|
50223
50386
|
if (!repo) return { transport: "rest", api, token, reason: "no repository \u2014 set GITHUB_REPOSITORY (owner/name) or ULTRA11Y_GITHUB_REPO" };
|
|
50224
50387
|
return { transport: "rest", api, token, repo };
|
|
50225
50388
|
}
|
|
50226
|
-
if (cliOk) return { transport: "cli", api };
|
|
50389
|
+
if (cliOk()) return { transport: "cli", api };
|
|
50227
50390
|
if (token && repo) return { transport: "rest", api, token, repo };
|
|
50228
50391
|
const missing = [!token ? "a token (GH_TOKEN/GITHUB_TOKEN)" : "", !repo ? "a repository (GITHUB_REPOSITORY)" : ""].filter(Boolean);
|
|
50229
50392
|
return {
|
|
@@ -51236,7 +51399,6 @@ var L7 = {
|
|
|
51236
51399
|
tally: (c2, nc, na, m) => `${c2} conforme(s) \xB7 ${nc} non conforme(s) \xB7 ${na} non applicable(s) \xB7 ${m} \xE0 \xE9valuer`,
|
|
51237
51400
|
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
51401
|
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
51402
|
screenshotAlt: (n) => `Capture d'\xE9cran de la page ${n}`,
|
|
51241
51403
|
noScreenshot: "Aucune capture d'\xE9cran pour cette page (le producteur n'en a pas fourni) \u2014 le tier pixel est donc inactif ici.",
|
|
51242
51404
|
gridTitle: "Grille des crit\xE8res",
|
|
@@ -51277,7 +51439,6 @@ var L7 = {
|
|
|
51277
51439
|
tally: (c2, nc, na, m) => `${c2} conforming \xB7 ${nc} non-conforming \xB7 ${na} not applicable \xB7 ${m} to assess`,
|
|
51278
51440
|
coverage: (decided, total) => `Coverage: ${decided}/${total} criteria assessed \u2014 the rate above covers only those, and says nothing about the other ${total - decided}.`,
|
|
51279
51441
|
tests: "Tests",
|
|
51280
|
-
agentMark: "`C*`: conformity ruled by the AI agent from the evidence it cited (gated), not proven by the deterministic engine.",
|
|
51281
51442
|
screenshotAlt: (n) => `Screenshot of the ${n} page`,
|
|
51282
51443
|
noScreenshot: "No screenshot for this page (the producer supplied none) \u2014 the pixel tier is therefore inactive here.",
|
|
51283
51444
|
gridTitle: "Criteria grid",
|
|
@@ -51298,7 +51459,7 @@ var L7 = {
|
|
|
51298
51459
|
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
51460
|
}
|
|
51300
51461
|
};
|
|
51301
|
-
function
|
|
51462
|
+
function pageCriterionRows(result, page, standard, lang) {
|
|
51302
51463
|
if (isCore(standard)) {
|
|
51303
51464
|
return [...page.criteria].sort((a, b) => compareSC(a.id, b.id)).map((c2) => ({
|
|
51304
51465
|
id: c2.id,
|
|
@@ -51320,7 +51481,7 @@ function rowsFor(result, page, standard, lang) {
|
|
|
51320
51481
|
decidedBy: byId2.get(pc.id)?.decidedBy
|
|
51321
51482
|
}));
|
|
51322
51483
|
}
|
|
51323
|
-
function
|
|
51484
|
+
function pageTally(rows) {
|
|
51324
51485
|
return {
|
|
51325
51486
|
c: rows.filter((r) => r.status === "C").length,
|
|
51326
51487
|
nc: rows.filter((r) => r.status === "NC").length,
|
|
@@ -51328,12 +51489,12 @@ function tally(rows) {
|
|
|
51328
51489
|
manual: rows.filter((r) => r.status === "manual").length
|
|
51329
51490
|
};
|
|
51330
51491
|
}
|
|
51331
|
-
function
|
|
51332
|
-
const t2 =
|
|
51492
|
+
function pageCoverage(rows) {
|
|
51493
|
+
const t2 = pageTally(rows);
|
|
51333
51494
|
return { decided: t2.c + t2.nc, total: rows.length };
|
|
51334
51495
|
}
|
|
51335
|
-
function
|
|
51336
|
-
const { c: c2, nc } =
|
|
51496
|
+
function pageRatePct(rows) {
|
|
51497
|
+
const { c: c2, nc } = pageTally(rows);
|
|
51337
51498
|
return c2 + nc === 0 ? null : Math.round(c2 / (c2 + nc) * 100);
|
|
51338
51499
|
}
|
|
51339
51500
|
function renderPageReport(result, page, opts = {}) {
|
|
@@ -51348,10 +51509,10 @@ function renderPageReport(result, page, opts = {}) {
|
|
|
51348
51509
|
meta2.push(`- **${s.basis}** : ${basisLabel(page.basis, lang)}`);
|
|
51349
51510
|
if (page.auth) meta2.push(`- **${s.auth}** : \u2705`);
|
|
51350
51511
|
out2.push(...meta2);
|
|
51351
|
-
const rows =
|
|
51352
|
-
const t2 =
|
|
51353
|
-
const cov =
|
|
51354
|
-
const rate =
|
|
51512
|
+
const rows = pageCriterionRows(result, page, standard, lang);
|
|
51513
|
+
const t2 = pageTally(rows);
|
|
51514
|
+
const cov = pageCoverage(rows);
|
|
51515
|
+
const rate = pageRatePct(rows);
|
|
51355
51516
|
out2.push(`- **${s.rate}** : **${rate === null ? "\u2014" : `${rate} %`}** _(${s.rateNote})_`);
|
|
51356
51517
|
out2.push(`- ${s.tally(t2.c, t2.nc, t2.na, t2.manual)}`);
|
|
51357
51518
|
out2.push(`- ${s.coverage(cov.decided, cov.total)}`, "");
|
|
@@ -51359,6 +51520,8 @@ function renderPageReport(result, page, opts = {}) {
|
|
|
51359
51520
|
const shot = opts.screenshots?.get(page.id);
|
|
51360
51521
|
if (shot) out2.push(``, "");
|
|
51361
51522
|
else out2.push(`_${s.noScreenshot}_`, "");
|
|
51523
|
+
const refused = opts.evidenceNotice?.(page.id) ?? [];
|
|
51524
|
+
if (refused.length) out2.push(...refused, "");
|
|
51362
51525
|
const withTests = rows.some((r) => r.tests.length);
|
|
51363
51526
|
out2.push(`${h}# ${s.gridTitle}`, "", `> ${s.gridNote}`, "");
|
|
51364
51527
|
out2.push(withTests ? `| ${s.criterion} | ${s.tests} | ${s.status} |` : `| ${s.criterion} | ${s.status} |`);
|
|
@@ -51374,16 +51537,17 @@ function renderPageReport(result, page, opts = {}) {
|
|
|
51374
51537
|
else out2.push(`| ${row.label} | ${mark} |`);
|
|
51375
51538
|
}
|
|
51376
51539
|
out2.push("", `> ${s.manualWarn}`, "");
|
|
51377
|
-
if (rows.some((r) => r.decidedBy === "agent" && r.status === "C")) out2.push(`> ${
|
|
51540
|
+
if (rows.some((r) => r.decidedBy === "agent" && r.status === "C")) out2.push(`> ${agentMarkNote(lang)}`, "");
|
|
51378
51541
|
const units = prdUnits(pageView(result, page), standard, lang);
|
|
51379
51542
|
const ncUnits = units.filter((u) => !u.advisory);
|
|
51380
51543
|
const advUnits = units.filter((u) => u.advisory);
|
|
51544
|
+
const unit = { heading: `${h}##`, collapse: true, ...opts.cropFor ? { cropFor: opts.cropFor } : {} };
|
|
51381
51545
|
out2.push(`${h}# ${s.ncTitle}`, "");
|
|
51382
51546
|
if (!ncUnits.length) out2.push(s.noNc, "");
|
|
51383
|
-
else for (const u of ncUnits) out2.push(...renderAuditorUnit(u, standard, lang,
|
|
51547
|
+
else for (const u of ncUnits) out2.push(...renderAuditorUnit(u, standard, lang, unit));
|
|
51384
51548
|
if (advUnits.length) {
|
|
51385
51549
|
out2.push(`${h}# \u{1F4A1} ${s.recTitle}`, "", `> ${s.recNote}`, "");
|
|
51386
|
-
for (const u of advUnits) out2.push(...renderAuditorUnit(u, standard, lang,
|
|
51550
|
+
for (const u of advUnits) out2.push(...renderAuditorUnit(u, standard, lang, unit));
|
|
51387
51551
|
}
|
|
51388
51552
|
return out2.join("\n");
|
|
51389
51553
|
}
|
|
@@ -51409,12 +51573,12 @@ function renderPagesIndex(result, pages, opts = {}) {
|
|
|
51409
51573
|
out2.push(`| ${s.page} | ${s.url} | ${s.basis} | ${s.rateShort} | ${s.blocking} | ${s.major} | ${s.minor} | ${s.sheet} |`);
|
|
51410
51574
|
out2.push("| --- | --- | --- | --- | --- | --- | --- | --- |");
|
|
51411
51575
|
for (const p of pages) {
|
|
51412
|
-
const rows =
|
|
51413
|
-
const cov =
|
|
51576
|
+
const rows = pageCriterionRows(result, p, standard, lang);
|
|
51577
|
+
const cov = pageCoverage(rows);
|
|
51414
51578
|
const nc = [...p.findings, ...(result.packFindings ?? []).filter((f) => f.page === p.id)].filter((f) => !f.advisory);
|
|
51415
51579
|
const href = opts.hrefs?.get(p.id);
|
|
51416
51580
|
out2.push(
|
|
51417
|
-
`| ${p.name}${p.auth ? " \u{1F512}" : ""} | \`${p.url}\` | ${basisLabel(p.basis, lang)} | ${formatRate(
|
|
51581
|
+
`| ${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
51582
|
);
|
|
51419
51583
|
}
|
|
51420
51584
|
out2.push("");
|
|
@@ -56097,6 +56261,7 @@ var S = {
|
|
|
56097
56261
|
where: "Emplacement",
|
|
56098
56262
|
what: "Constat",
|
|
56099
56263
|
more: (n) => `\u2026 et ${n} autre(s).`,
|
|
56264
|
+
moreGroups: (n) => `\u2026 et ${n} autre(s) groupe(s) \u2014 voir le r\xE9sum\xE9 de job.`,
|
|
56100
56265
|
perPage: "Bilan page par page",
|
|
56101
56266
|
page: "Page",
|
|
56102
56267
|
count: "Constats",
|
|
@@ -56104,6 +56269,16 @@ var S = {
|
|
|
56104
56269
|
pageRate: "Taux",
|
|
56105
56270
|
snapshot: "instantan\xE9",
|
|
56106
56271
|
source: "source",
|
|
56272
|
+
occurrences: "Occ.",
|
|
56273
|
+
pagesCol: "Pages",
|
|
56274
|
+
grouped: (groups, occ) => `${groups} d\xE9faut(s) distinct(s) \xB7 ${occ} occurrence(s)`,
|
|
56275
|
+
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.",
|
|
56276
|
+
verdictFail: (n) => `\u{1F534} ${n} non-conformit\xE9(s) bloquante(s) \u2014 la porte est rouge.`,
|
|
56277
|
+
verdictWarn: "\u{1F7E0} Aucune non-conformit\xE9 bloquante ; des constats majeurs ou mineurs restent \xE0 traiter.",
|
|
56278
|
+
verdictPass: "\u2705 Aucune non-conformit\xE9 relev\xE9e par le moteur statique.",
|
|
56279
|
+
artifact: (name2) => `Rapport complet (HTML, captures annot\xE9es) : artefact **${name2}** du run.`,
|
|
56280
|
+
runLink: (url) => `[Voir le run et son r\xE9sum\xE9 de job](${url})`,
|
|
56281
|
+
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
56282
|
unanchored: (n) => `${n} constat(s) rattach\xE9(s) \xE0 une URL, sans ligne de code \xE0 annoter \u2014 voir le rapport.`,
|
|
56108
56283
|
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
56284
|
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 +56294,7 @@ var S = {
|
|
|
56119
56294
|
where: "Location",
|
|
56120
56295
|
what: "Finding",
|
|
56121
56296
|
more: (n) => `\u2026 and ${n} more.`,
|
|
56297
|
+
moreGroups: (n) => `\u2026 and ${n} more group(s) \u2014 see the job summary.`,
|
|
56122
56298
|
perPage: "Page-by-page scoreboard",
|
|
56123
56299
|
page: "Page",
|
|
56124
56300
|
count: "Findings",
|
|
@@ -56126,20 +56302,75 @@ var S = {
|
|
|
56126
56302
|
pageRate: "Rate",
|
|
56127
56303
|
snapshot: "snapshot",
|
|
56128
56304
|
source: "source",
|
|
56305
|
+
occurrences: "Occ.",
|
|
56306
|
+
pagesCol: "Pages",
|
|
56307
|
+
grouped: (groups, occ) => `${groups} distinct defect(s) \xB7 ${occ} occurrence(s)`,
|
|
56308
|
+
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.",
|
|
56309
|
+
verdictFail: (n) => `\u{1F534} ${n} blocking non-conformity(ies) \u2014 the gate is red.`,
|
|
56310
|
+
verdictWarn: "\u{1F7E0} No blocking non-conformity; major or minor findings remain.",
|
|
56311
|
+
verdictPass: "\u2705 No non-conformity found by the static engine.",
|
|
56312
|
+
artifact: (name2) => `Full report (HTML, annotated crops): artifact **${name2}** of this run.`,
|
|
56313
|
+
runLink: (url) => `[See the run and its job summary](${url})`,
|
|
56314
|
+
clamped: (n) => `_${n} group(s) dropped from this comment to fit GitHub's limit \u2014 the job summary carries them all._`,
|
|
56129
56315
|
unanchored: (n) => `${n} finding(s) keyed to a URL, with no code line to annotate \u2014 see the report.`,
|
|
56130
56316
|
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
56317
|
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
56318
|
}
|
|
56133
56319
|
};
|
|
56134
56320
|
var MAX_ROWS = 50;
|
|
56321
|
+
var COMMENT_ROWS = 10;
|
|
56322
|
+
var COMMENT_LIMIT = 65536;
|
|
56323
|
+
function groupFindings(findings, standard, lang, baseDir) {
|
|
56324
|
+
const groups = /* @__PURE__ */ new Map();
|
|
56325
|
+
for (const f of findings) {
|
|
56326
|
+
const criterion = criterionLabel2(f, standard);
|
|
56327
|
+
const key = `${criterion}\0${f.ruleId}\0${f.selectorHint}`;
|
|
56328
|
+
const g = groups.get(key);
|
|
56329
|
+
if (g) {
|
|
56330
|
+
g.occurrences++;
|
|
56331
|
+
if (f.page) g.pageSet.add(f.page);
|
|
56332
|
+
continue;
|
|
56333
|
+
}
|
|
56334
|
+
groups.set(key, {
|
|
56335
|
+
criterion,
|
|
56336
|
+
ruleId: f.ruleId,
|
|
56337
|
+
selectorHint: f.selectorHint,
|
|
56338
|
+
severity: f.severity,
|
|
56339
|
+
message: resolveMessage(f, lang),
|
|
56340
|
+
where: isUrl2(f.file) ? f.file : `${repoRelative(f.file, baseDir)}:${Math.max(1, f.line)}`,
|
|
56341
|
+
occurrences: 1,
|
|
56342
|
+
pages: 0,
|
|
56343
|
+
pageSet: new Set(f.page ? [f.page] : [])
|
|
56344
|
+
});
|
|
56345
|
+
}
|
|
56346
|
+
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));
|
|
56347
|
+
}
|
|
56348
|
+
function runRate(result, standard, lang) {
|
|
56349
|
+
const groups = isCore(standard) ? reportGroups(result, lang) : packReportGroups(result, loadPack(standard), lang);
|
|
56350
|
+
const { decided, total } = reportCoverage(groups);
|
|
56351
|
+
const agentRuled = groups.some((g) => g.rows.some((r) => r.decidedBy === "agent" && r.status === "C"));
|
|
56352
|
+
return { text: `${formatRate(decided === 0 ? null : result.conformancePct, decided, total)}${agentRuled ? "*" : ""}`, agentRuled };
|
|
56353
|
+
}
|
|
56354
|
+
function groupTableHead(s) {
|
|
56355
|
+
return [`| ${s.severity} | ${s.criterion} | ${s.where} | ${s.what} | ${s.occurrences} | ${s.pagesCol} |`, "| --- | --- | --- | --- | ---: | ---: |"];
|
|
56356
|
+
}
|
|
56357
|
+
function groupRow(g) {
|
|
56358
|
+
const cell = (v) => v.replace(/\|/g, "\\|");
|
|
56359
|
+
return `| ${ICON5[g.severity]} ${g.severity} | ${cell(g.criterion)} | \`${cell(g.where)}\` (\`${cell(g.selectorHint)}\`) | ${cell(g.message)} | ${g.occurrences} | ${g.pages || "\u2014"} |`;
|
|
56360
|
+
}
|
|
56361
|
+
function groupTable(rows, s) {
|
|
56362
|
+
return [...groupTableHead(s), ...rows.map(groupRow)];
|
|
56363
|
+
}
|
|
56135
56364
|
function stepSummary(result, opts = {}) {
|
|
56136
56365
|
const standard = opts.standard ?? CORE2;
|
|
56137
56366
|
const lang = opts.lang ?? "en";
|
|
56138
56367
|
const s = S[lang];
|
|
56139
56368
|
const stdLabel = isCore(standard) ? "WCAG 2.2 AA" : loadPack(standard).name;
|
|
56369
|
+
const rate = runRate(result, standard, lang);
|
|
56140
56370
|
const out2 = [];
|
|
56141
56371
|
out2.push(`## ${s.title} \u2014 ${stdLabel}`, "");
|
|
56142
|
-
out2.push(`\`${result.date}\` \xB7 ${result.scope.files} ${s.files} \xB7 **${
|
|
56372
|
+
out2.push(`\`${result.date}\` \xB7 ${result.scope.files} ${s.files} \xB7 **${rate.text}** ${s.rate}`, "");
|
|
56373
|
+
if (rate.agentRuled) out2.push(`> ${agentMarkNote(lang)}`, "");
|
|
56143
56374
|
const baseDir = opts.baseDir ?? process.cwd();
|
|
56144
56375
|
const all = findingsForStandard(result, standard);
|
|
56145
56376
|
if (!all.length) {
|
|
@@ -56147,21 +56378,52 @@ function stepSummary(result, opts = {}) {
|
|
|
56147
56378
|
out2.push(perPageTable(result, standard, lang));
|
|
56148
56379
|
return out2.join("\n");
|
|
56149
56380
|
}
|
|
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));
|
|
56381
|
+
const grouped = groupFindings(all, standard, lang, baseDir);
|
|
56382
|
+
out2.push(`### ${s.findings} \u2014 ${s.grouped(grouped.length, all.length)}`, "");
|
|
56383
|
+
out2.push(`> ${s.groupNote}`, "");
|
|
56384
|
+
out2.push(...groupTable(grouped.slice(0, MAX_ROWS), s));
|
|
56385
|
+
if (grouped.length > MAX_ROWS) out2.push("", s.more(grouped.length - MAX_ROWS));
|
|
56159
56386
|
out2.push("");
|
|
56160
56387
|
const unanchored = all.filter((f) => isUrl2(f.file)).length;
|
|
56161
56388
|
if (unanchored) out2.push(`> ${s.unanchored(unanchored)}`, "");
|
|
56162
56389
|
out2.push(perPageTable(result, standard, lang));
|
|
56163
56390
|
return out2.join("\n");
|
|
56164
56391
|
}
|
|
56392
|
+
function prComment(result, opts = {}) {
|
|
56393
|
+
const standard = opts.standard ?? CORE2;
|
|
56394
|
+
const lang = opts.lang ?? "en";
|
|
56395
|
+
const s = S[lang];
|
|
56396
|
+
const baseDir = opts.baseDir ?? process.cwd();
|
|
56397
|
+
const stdLabel = isCore(standard) ? "WCAG 2.2 AA" : loadPack(standard).name;
|
|
56398
|
+
const all = findingsForStandard(result, standard);
|
|
56399
|
+
const normative = all.filter((f) => !f.advisory);
|
|
56400
|
+
const blocking = normative.filter((f) => f.severity === "bloquant").length;
|
|
56401
|
+
const rate = runRate(result, standard, lang);
|
|
56402
|
+
const grouped = groupFindings(all, standard, lang, baseDir);
|
|
56403
|
+
const orphans = unattributedFindings(result).filter((f) => !f.advisory).length;
|
|
56404
|
+
const head = [];
|
|
56405
|
+
head.push(`### ${s.title} \u2014 ${stdLabel}`, "");
|
|
56406
|
+
head.push(blocking ? s.verdictFail(blocking) : normative.length ? s.verdictWarn : s.verdictPass, "");
|
|
56407
|
+
head.push(`\`${result.date}\` \xB7 ${result.scope.files} ${s.files} \xB7 **${rate.text}** ${s.rate}`, "");
|
|
56408
|
+
if (rate.agentRuled) head.push(`> ${agentMarkNote(lang)}`, "");
|
|
56409
|
+
if (orphans) head.push(`> ${s.unattributed(orphans)}`, "");
|
|
56410
|
+
const tail = [];
|
|
56411
|
+
if (opts.artifactName) tail.push(s.artifact(opts.artifactName), "");
|
|
56412
|
+
if (opts.runUrl) tail.push(s.runLink(opts.runUrl), "");
|
|
56413
|
+
const assemble = (rows2) => {
|
|
56414
|
+
const body2 = [];
|
|
56415
|
+
if (grouped.length) {
|
|
56416
|
+
body2.push(s.grouped(grouped.length, all.length), "");
|
|
56417
|
+
body2.push(...groupTable(grouped.slice(0, rows2), s), "");
|
|
56418
|
+
const omitted = grouped.length - rows2;
|
|
56419
|
+
if (omitted > 0) body2.push(rows2 < COMMENT_ROWS ? s.clamped(omitted) : s.moreGroups(omitted), "");
|
|
56420
|
+
}
|
|
56421
|
+
return [...head, ...body2, ...tail].join("\n").trimEnd();
|
|
56422
|
+
};
|
|
56423
|
+
let rows = Math.min(COMMENT_ROWS, grouped.length);
|
|
56424
|
+
while (rows > 0 && assemble(rows).length > COMMENT_LIMIT) rows--;
|
|
56425
|
+
return assemble(rows);
|
|
56426
|
+
}
|
|
56165
56427
|
function perPageTable(result, standard = CORE2, lang = "en") {
|
|
56166
56428
|
const s = S[lang];
|
|
56167
56429
|
const scope = pagesOf(result);
|
|
@@ -56186,10 +56448,1018 @@ function perPageTable(result, standard = CORE2, lang = "en") {
|
|
|
56186
56448
|
return out2.join("\n");
|
|
56187
56449
|
}
|
|
56188
56450
|
|
|
56451
|
+
// src/evidence.ts
|
|
56452
|
+
import { createHash as createHash6 } from "crypto";
|
|
56453
|
+
import { existsSync as existsSync28, mkdirSync as mkdirSync13, readFileSync as readFileSync23, writeFileSync as writeFileSync15 } from "fs";
|
|
56454
|
+
import { join as join40 } from "path";
|
|
56455
|
+
var DEFAULT_GEOMETRY = { pad: 24, minWidth: 320, minHeight: 120, maxWidth: 960, maxHeight: 540 };
|
|
56456
|
+
var DEFAULT_CAPS = { perRule: 6, perPage: 12, total: 200 };
|
|
56457
|
+
var HALO = { r: 255, g: 255, b: 255, a: 0.92 };
|
|
56458
|
+
var MARK3 = { r: 179, g: 38, b: 30, a: 1 };
|
|
56459
|
+
var RING = 3;
|
|
56460
|
+
function pageIdOfSnapshot(file) {
|
|
56461
|
+
if (!isSnapshotDom(file)) return null;
|
|
56462
|
+
const posix3 = file.split("\\").join("/");
|
|
56463
|
+
const at = posix3.lastIndexOf(`${PAGES_DIR}/`);
|
|
56464
|
+
if (at < 0) return null;
|
|
56465
|
+
const rest = posix3.slice(at + PAGES_DIR.length + 1);
|
|
56466
|
+
const id = rest.slice(0, rest.indexOf("/"));
|
|
56467
|
+
return id || null;
|
|
56468
|
+
}
|
|
56469
|
+
function readJson3(file) {
|
|
56470
|
+
try {
|
|
56471
|
+
return JSON.parse(readFileSync23(file, "utf8"));
|
|
56472
|
+
} catch {
|
|
56473
|
+
return void 0;
|
|
56474
|
+
}
|
|
56475
|
+
}
|
|
56476
|
+
function resolveScale(img, viewport, boxes) {
|
|
56477
|
+
const candidates2 = [viewport?.width, boxes.get(0)?.w];
|
|
56478
|
+
for (const w of candidates2) {
|
|
56479
|
+
if (!w || w <= 0) continue;
|
|
56480
|
+
const scale = img.width / w;
|
|
56481
|
+
if (scale >= 0.5 && scale <= 4) return scale;
|
|
56482
|
+
}
|
|
56483
|
+
return null;
|
|
56484
|
+
}
|
|
56485
|
+
function loadPageEvidence(root, pageId) {
|
|
56486
|
+
const dir = snapshotDir(root, pageId);
|
|
56487
|
+
const domFile = join40(dir, "dom.html");
|
|
56488
|
+
const shotFile = join40(dir, "screen.png");
|
|
56489
|
+
if (!existsSync28(domFile)) return { skip: "no-snapshot" };
|
|
56490
|
+
if (!existsSync28(shotFile)) return { skip: "no-screenshot" };
|
|
56491
|
+
let dom;
|
|
56492
|
+
let shot;
|
|
56493
|
+
try {
|
|
56494
|
+
dom = readFileSync23(domFile, "utf8");
|
|
56495
|
+
shot = readFileSync23(shotFile);
|
|
56496
|
+
} catch {
|
|
56497
|
+
return { skip: "no-snapshot" };
|
|
56498
|
+
}
|
|
56499
|
+
const img = decodePng(shot);
|
|
56500
|
+
if (!img) return { skip: "unreadable-image" };
|
|
56501
|
+
const digest = readJson3(join40(dir, "boxes.json"));
|
|
56502
|
+
if (!digest) return { skip: "no-boxes" };
|
|
56503
|
+
const doc = parseHtml(dom, domFile);
|
|
56504
|
+
const boxes = alignedBoxes(dom, digest, doc);
|
|
56505
|
+
if (!boxes) return { skip: "no-boxes" };
|
|
56506
|
+
const scale = resolveScale(img, metaOf(dir)?.viewport, boxes);
|
|
56507
|
+
if (scale === null) return { skip: "unknown-scale" };
|
|
56508
|
+
const ordinalOf = /* @__PURE__ */ new Map();
|
|
56509
|
+
for (let i2 = 0; i2 < doc.elements.length; i2++) {
|
|
56510
|
+
const start2 = doc.elements[i2]?.start;
|
|
56511
|
+
if (start2 !== void 0 && !ordinalOf.has(start2)) ordinalOf.set(start2, i2);
|
|
56512
|
+
}
|
|
56513
|
+
const viewport = metaOf(dir)?.viewport ?? { width: Math.round(img.width / scale), height: Math.round(img.height / scale) };
|
|
56514
|
+
return { pageId, img, scale, viewport, ordinalOf, boxes, truncated: Boolean(digest.truncated) };
|
|
56515
|
+
}
|
|
56516
|
+
function metaOf(dir) {
|
|
56517
|
+
const raw = readJson3(join40(dir, "meta.json"));
|
|
56518
|
+
if (raw === void 0) return void 0;
|
|
56519
|
+
const v = validateSnapshotMeta(raw);
|
|
56520
|
+
return v.ok && v.meta ? v.meta : void 0;
|
|
56521
|
+
}
|
|
56522
|
+
function cropRect(box, g, scale, img) {
|
|
56523
|
+
const bx = box.x * scale;
|
|
56524
|
+
const by = box.y * scale;
|
|
56525
|
+
const bw = box.w * scale;
|
|
56526
|
+
const bh = box.h * scale;
|
|
56527
|
+
const w = Math.min(Math.max(bw + g.pad * 2 * scale, g.minWidth * scale), g.maxWidth * scale, img.width);
|
|
56528
|
+
const h = Math.min(Math.max(bh + g.pad * 2 * scale, g.minHeight * scale), g.maxHeight * scale, img.height);
|
|
56529
|
+
let x = bx + bw / 2 - w / 2;
|
|
56530
|
+
let y = by + bh / 2 - h / 2;
|
|
56531
|
+
x = Math.max(0, Math.min(x, img.width - w));
|
|
56532
|
+
y = Math.max(0, Math.min(y, img.height - h));
|
|
56533
|
+
return { x, y, w, h };
|
|
56534
|
+
}
|
|
56535
|
+
function annotate(raster, target) {
|
|
56536
|
+
strokeRect(raster, { x: target.x - RING, y: target.y - RING, w: target.w + RING * 2, h: target.h + RING * 2 }, HALO, RING);
|
|
56537
|
+
strokeRect(raster, target, MARK3, RING);
|
|
56538
|
+
strokeRect(raster, { x: target.x + RING, y: target.y + RING, w: target.w - RING * 2, h: target.h - RING * 2 }, HALO, 1, 6);
|
|
56539
|
+
const arm = Math.max(6, Math.min(18, Math.round(Math.min(target.w, target.h) / 3)));
|
|
56540
|
+
const t2 = RING;
|
|
56541
|
+
const x0 = target.x - RING;
|
|
56542
|
+
const y0 = target.y - RING;
|
|
56543
|
+
const x1 = target.x + target.w + RING;
|
|
56544
|
+
const y1 = target.y + target.h + RING;
|
|
56545
|
+
for (const [cx, cy, dx, dy] of [
|
|
56546
|
+
[x0, y0, 1, 1],
|
|
56547
|
+
[x1, y0, -1, 1],
|
|
56548
|
+
[x0, y1, 1, -1],
|
|
56549
|
+
[x1, y1, -1, -1]
|
|
56550
|
+
]) {
|
|
56551
|
+
fillRect(raster, { x: dx > 0 ? cx : cx - arm, y: dy > 0 ? cy - t2 : cy, w: arm, h: t2 }, MARK3);
|
|
56552
|
+
fillRect(raster, { x: dx > 0 ? cx - t2 : cx, y: dy > 0 ? cy : cy - arm, w: t2, h: arm }, MARK3);
|
|
56553
|
+
}
|
|
56554
|
+
}
|
|
56555
|
+
function cropFinding(ctx, f, geometry) {
|
|
56556
|
+
const g = { ...DEFAULT_GEOMETRY, ...geometry };
|
|
56557
|
+
if (f.sourceStart === void 0) return { skip: "no-offsets" };
|
|
56558
|
+
const ordinal = ctx.ordinalOf.get(f.sourceStart);
|
|
56559
|
+
if (ordinal === void 0) return { skip: "unjoinable" };
|
|
56560
|
+
if (ordinal === 0) return { skip: "page-scope" };
|
|
56561
|
+
const box = ctx.boxes.get(ordinal);
|
|
56562
|
+
if (!box) return { skip: ctx.truncated ? "truncated" : "no-boxes" };
|
|
56563
|
+
if (box.w <= 0 || box.h <= 0) return { skip: "zero-area" };
|
|
56564
|
+
const vpArea = ctx.viewport.width * ctx.viewport.height;
|
|
56565
|
+
if (vpArea > 0 && box.w * box.h / vpArea >= 0.7) return { skip: "page-scope" };
|
|
56566
|
+
const visibleTop = Math.max(0, box.y * ctx.scale);
|
|
56567
|
+
const visibleBottom = Math.min(ctx.img.height, (box.y + box.h) * ctx.scale);
|
|
56568
|
+
const visibleFraction = box.h > 0 ? (visibleBottom - visibleTop) / (box.h * ctx.scale) : 0;
|
|
56569
|
+
if (visibleFraction < 0.5) return { skip: "below-the-fold" };
|
|
56570
|
+
const rect = cropRect(box, g, ctx.scale, ctx.img);
|
|
56571
|
+
const crop = cropImage(ctx.img, rect);
|
|
56572
|
+
if (!crop) return { skip: "below-the-fold" };
|
|
56573
|
+
annotate(crop.raster, {
|
|
56574
|
+
x: box.x * ctx.scale - crop.rect.x,
|
|
56575
|
+
y: box.y * ctx.scale - crop.rect.y,
|
|
56576
|
+
w: box.w * ctx.scale,
|
|
56577
|
+
h: box.h * ctx.scale
|
|
56578
|
+
});
|
|
56579
|
+
return {
|
|
56580
|
+
png: encodePng(crop.raster),
|
|
56581
|
+
width: crop.raster.width,
|
|
56582
|
+
height: crop.raster.height,
|
|
56583
|
+
box,
|
|
56584
|
+
scale: ctx.scale,
|
|
56585
|
+
clipped: visibleFraction < 1
|
|
56586
|
+
};
|
|
56587
|
+
}
|
|
56588
|
+
var S2 = {
|
|
56589
|
+
fr: {
|
|
56590
|
+
// No `N.N —` sequence: src/check.ts scans the WHOLE document for criterion mentions.
|
|
56591
|
+
alt: (sel, page) => `Capture recadr\xE9e de l'\xE9l\xE9ment ${sel} sur la page ${page}, entour\xE9 d'un cadre`,
|
|
56592
|
+
notImaged: (n) => `${n} occurrence(s) ne sont pas illustr\xE9es :`,
|
|
56593
|
+
reasons: {
|
|
56594
|
+
"no-snapshot": "la capture de page que ce constat d\xE9signe n'est pas sur ce disque \u2014 sa r\xE9f\xE9rence fichier:ligne reste valable",
|
|
56595
|
+
"no-screenshot": "le producteur n'a pas fourni de capture pour cette page, donc le niveau pixel est inactif ici",
|
|
56596
|
+
"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",
|
|
56597
|
+
"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",
|
|
56598
|
+
truncated: "la page d\xE9passe la limite de collecte : aucune position n'a \xE9t\xE9 enregistr\xE9e pour cet \xE9l\xE9ment",
|
|
56599
|
+
"no-offsets": "le constat ne porte pas d'ancrage dans la source, il ne peut pas \xEAtre joint \xE0 une position",
|
|
56600
|
+
unjoinable: "l'ancrage du constat ne correspond \xE0 aucun \xE9l\xE9ment du DOM s\xE9rialis\xE9",
|
|
56601
|
+
"page-scope": "le constat porte sur la page enti\xE8re \u2014 la capture de page ci-dessus est l'illustration",
|
|
56602
|
+
"zero-area": "l'\xE9l\xE9ment n'occupe aucune surface peinte",
|
|
56603
|
+
"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",
|
|
56604
|
+
"unknown-scale": "le rapport pixels/CSS est ind\xE9termin\xE9 pour cette page ; un rapport devin\xE9 encadrerait le mauvais endroit",
|
|
56605
|
+
deduplicated: "m\xEAme d\xE9faut sur le m\xEAme \xE9l\xE9ment : il est montr\xE9 par l'illustration d'une autre occurrence, rien n'a \xE9t\xE9 perdu",
|
|
56606
|
+
capped: "d\xE9faut distinct laiss\xE9 sans image : le plafond de vignettes de ce lot est atteint \u2014 relevez `--evidence-max` pour les obtenir"
|
|
56607
|
+
}
|
|
56608
|
+
},
|
|
56609
|
+
en: {
|
|
56610
|
+
alt: (sel, page) => `Cropped screenshot of the ${sel} element on the ${page} page, outlined`,
|
|
56611
|
+
notImaged: (n) => `${n} occurrence(s) are not illustrated:`,
|
|
56612
|
+
reasons: {
|
|
56613
|
+
"no-snapshot": "the page snapshot this finding names is not on this disk \u2014 its file:line reference still holds",
|
|
56614
|
+
"no-screenshot": "the producer supplied no screenshot for this page, so the pixel tier is inactive here",
|
|
56615
|
+
"unreadable-image": "the screenshot does not decode (the engine reads 8-bit truecolour only) \u2014 no image beats a wrong one",
|
|
56616
|
+
"no-boxes": "the element positions do not verify against the serialized DOM, so they were refused wholesale rather than outlining the wrong element",
|
|
56617
|
+
truncated: "the page exceeded the collection cap: no position was recorded for this element",
|
|
56618
|
+
"no-offsets": "the finding carries no source anchor, so it cannot be joined to a position",
|
|
56619
|
+
unjoinable: "the finding's anchor matches no element in the serialized DOM",
|
|
56620
|
+
"page-scope": "the finding is about the whole page \u2014 the page screenshot above is the illustration",
|
|
56621
|
+
"zero-area": "the element paints no box",
|
|
56622
|
+
"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",
|
|
56623
|
+
"unknown-scale": "the image-pixel to CSS-pixel ratio is indeterminate for this page; a guessed ratio would outline the wrong place",
|
|
56624
|
+
deduplicated: "the same defect on the same element: it is shown by the illustration of another occurrence, nothing was lost",
|
|
56625
|
+
capped: "a distinct defect left undrawn: this run's crop limit was reached \u2014 raise `--evidence-max` to get them"
|
|
56626
|
+
}
|
|
56627
|
+
}
|
|
56628
|
+
};
|
|
56629
|
+
function evidenceRefusals(m, pageId, lang) {
|
|
56630
|
+
const t2 = pageId === null ? m.totals : m.perPage.get(pageId);
|
|
56631
|
+
if (!t2) return void 0;
|
|
56632
|
+
const missing = t2.located - t2.imaged;
|
|
56633
|
+
if (missing <= 0) return void 0;
|
|
56634
|
+
const entries = Object.entries(t2.skipped).filter(([, n]) => n);
|
|
56635
|
+
const s = S2[lang];
|
|
56636
|
+
return {
|
|
56637
|
+
headline: s.notImaged(missing),
|
|
56638
|
+
reasons: entries.sort((a, b) => b[1] - a[1]).map(([reason, n]) => `${n} \u2014 ${s.reasons[reason]}`)
|
|
56639
|
+
};
|
|
56640
|
+
}
|
|
56641
|
+
function evidenceNotice(m, pageId, lang) {
|
|
56642
|
+
const r = evidenceRefusals(m, pageId, lang);
|
|
56643
|
+
return r ? [r.headline, ...r.reasons.map((x) => `- ${x}`)] : [];
|
|
56644
|
+
}
|
|
56645
|
+
function bump(t2, reason) {
|
|
56646
|
+
t2.skipped[reason] = (t2.skipped[reason] ?? 0) + 1;
|
|
56647
|
+
}
|
|
56648
|
+
function cropName(f) {
|
|
56649
|
+
return `${createHash6("sha1").update(findingId(f)).digest("hex").slice(0, 12)}.png`;
|
|
56650
|
+
}
|
|
56651
|
+
function writeEvidence(result, opts) {
|
|
56652
|
+
const root = opts.root ?? process.cwd();
|
|
56653
|
+
const caps = { ...DEFAULT_CAPS, ...opts.caps };
|
|
56654
|
+
const manifest = {
|
|
56655
|
+
crops: /* @__PURE__ */ new Map(),
|
|
56656
|
+
skipped: /* @__PURE__ */ new Map(),
|
|
56657
|
+
perPage: /* @__PURE__ */ new Map(),
|
|
56658
|
+
totals: { located: 0, imaged: 0, skipped: {} }
|
|
56659
|
+
};
|
|
56660
|
+
const byPage = /* @__PURE__ */ new Map();
|
|
56661
|
+
for (const f of [...result.findings, ...result.packFindings ?? []]) {
|
|
56662
|
+
if (f.advisory) continue;
|
|
56663
|
+
const page = pageIdOfSnapshot(f.file);
|
|
56664
|
+
if (!page) continue;
|
|
56665
|
+
const list = byPage.get(page);
|
|
56666
|
+
if (list) list.push(f);
|
|
56667
|
+
else byPage.set(page, [f]);
|
|
56668
|
+
}
|
|
56669
|
+
for (const [pageId, findings] of [...byPage].sort((a, b) => a[0].localeCompare(b[0]))) {
|
|
56670
|
+
const tally = { located: findings.length, imaged: 0, skipped: {} };
|
|
56671
|
+
manifest.perPage.set(pageId, tally);
|
|
56672
|
+
manifest.totals.located += findings.length;
|
|
56673
|
+
const ctx = loadPageEvidence(root, pageId);
|
|
56674
|
+
if ("skip" in ctx) {
|
|
56675
|
+
for (const f of findings) {
|
|
56676
|
+
manifest.skipped.set(findingId(f), ctx.skip);
|
|
56677
|
+
bump(tally, ctx.skip);
|
|
56678
|
+
bump(manifest.totals, ctx.skip);
|
|
56679
|
+
}
|
|
56680
|
+
continue;
|
|
56681
|
+
}
|
|
56682
|
+
const dir = join40(opts.outDir, "assets", pageId);
|
|
56683
|
+
let wrote = false;
|
|
56684
|
+
const perRule = /* @__PURE__ */ new Map();
|
|
56685
|
+
const seen = /* @__PURE__ */ new Set();
|
|
56686
|
+
let onPage = 0;
|
|
56687
|
+
for (const f of findings) {
|
|
56688
|
+
const id = findingId(f);
|
|
56689
|
+
const key = `${f.ruleId}\0${f.selectorHint}`;
|
|
56690
|
+
const ruleCount = perRule.get(f.ruleId) ?? 0;
|
|
56691
|
+
const overLimit = ruleCount >= caps.perRule || onPage >= caps.perPage || manifest.crops.size >= caps.total;
|
|
56692
|
+
const refusal = seen.has(key) ? "deduplicated" : overLimit ? "capped" : null;
|
|
56693
|
+
if (refusal) {
|
|
56694
|
+
manifest.skipped.set(id, refusal);
|
|
56695
|
+
bump(tally, refusal);
|
|
56696
|
+
bump(manifest.totals, refusal);
|
|
56697
|
+
continue;
|
|
56698
|
+
}
|
|
56699
|
+
const crop = cropFinding(ctx, f, opts.geometry);
|
|
56700
|
+
if ("skip" in crop) {
|
|
56701
|
+
manifest.skipped.set(id, crop.skip);
|
|
56702
|
+
bump(tally, crop.skip);
|
|
56703
|
+
bump(manifest.totals, crop.skip);
|
|
56704
|
+
continue;
|
|
56705
|
+
}
|
|
56706
|
+
const name2 = cropName(f);
|
|
56707
|
+
const path = join40(dir, name2);
|
|
56708
|
+
try {
|
|
56709
|
+
if (!wrote) {
|
|
56710
|
+
mkdirSync13(dir, { recursive: true });
|
|
56711
|
+
wrote = true;
|
|
56712
|
+
}
|
|
56713
|
+
writeFileSync15(path, crop.png);
|
|
56714
|
+
} catch {
|
|
56715
|
+
manifest.skipped.set(id, "no-screenshot");
|
|
56716
|
+
bump(tally, "no-screenshot");
|
|
56717
|
+
bump(manifest.totals, "no-screenshot");
|
|
56718
|
+
continue;
|
|
56719
|
+
}
|
|
56720
|
+
seen.add(key);
|
|
56721
|
+
perRule.set(f.ruleId, ruleCount + 1);
|
|
56722
|
+
onPage++;
|
|
56723
|
+
tally.imaged++;
|
|
56724
|
+
manifest.totals.imaged++;
|
|
56725
|
+
manifest.crops.set(id, {
|
|
56726
|
+
findingId: id,
|
|
56727
|
+
page: pageId,
|
|
56728
|
+
ruleId: f.ruleId,
|
|
56729
|
+
criteriaId: f.criteriaId,
|
|
56730
|
+
href: `./assets/${pageId}/${name2}`,
|
|
56731
|
+
path,
|
|
56732
|
+
width: crop.width,
|
|
56733
|
+
height: crop.height,
|
|
56734
|
+
box: crop.box,
|
|
56735
|
+
scale: crop.scale,
|
|
56736
|
+
clipped: crop.clipped,
|
|
56737
|
+
alt: { fr: S2.fr.alt(f.selectorHint, pageId), en: S2.en.alt(f.selectorHint, pageId) }
|
|
56738
|
+
});
|
|
56739
|
+
}
|
|
56740
|
+
}
|
|
56741
|
+
return manifest;
|
|
56742
|
+
}
|
|
56743
|
+
|
|
56744
|
+
// src/html-emit.ts
|
|
56745
|
+
import { mkdirSync as mkdirSync14, readFileSync as readFileSync24, statSync as statSync11, writeFileSync as writeFileSync16 } from "fs";
|
|
56746
|
+
import { join as join41 } from "path";
|
|
56747
|
+
|
|
56748
|
+
// src/html.ts
|
|
56749
|
+
var MARK4 = { C: "C", NC: "NC", NA: "\u2014", manual: "?" };
|
|
56750
|
+
var CLASS = { C: "c", NC: "nc", NA: "na", manual: "m" };
|
|
56751
|
+
var T3 = {
|
|
56752
|
+
fr: {
|
|
56753
|
+
skip: "Aller au contenu",
|
|
56754
|
+
nav: "Documents du rapport",
|
|
56755
|
+
printHint: "Pour un PDF : imprimez cette page (Ctrl/Cmd + P) et choisissez \xAB Enregistrer au format PDF \xBB.",
|
|
56756
|
+
generated: "G\xE9n\xE9r\xE9 par ultra11y"
|
|
56757
|
+
},
|
|
56758
|
+
en: {
|
|
56759
|
+
skip: "Skip to content",
|
|
56760
|
+
nav: "Report documents",
|
|
56761
|
+
printHint: "For a PDF: print this page (Ctrl/Cmd + P) and choose \u201CSave as PDF\u201D.",
|
|
56762
|
+
generated: "Generated by ultra11y"
|
|
56763
|
+
}
|
|
56764
|
+
};
|
|
56765
|
+
function esc2(s) {
|
|
56766
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
56767
|
+
}
|
|
56768
|
+
var STYLE = `
|
|
56769
|
+
:root { color-scheme: light dark; --bg:#fff; --fg:#111; --line:#e3e3e6; --mut:#666; --soft:#f6f6f8;
|
|
56770
|
+
--c:#0a7d33; --nc:#b3261e; --m:#b06000; --accent:#0b5fa5; }
|
|
56771
|
+
@media (prefers-color-scheme: dark) { :root { --bg:#141416; --fg:#f2f2f7; --line:#2c2c2e; --mut:#a1a1a6; --soft:#1c1c1f;
|
|
56772
|
+
--c:#3fbf6f; --nc:#ff6b5e; --m:#d99a2b; --accent:#6cb6ff; } }
|
|
56773
|
+
* { box-sizing:border-box; }
|
|
56774
|
+
body { margin:0; background:var(--bg); color:var(--fg); font:15px/1.6 ui-sans-serif,system-ui,-apple-system,sans-serif; }
|
|
56775
|
+
.skip { position:absolute; left:-9999px; top:0; padding:8px 14px; background:var(--bg); color:var(--fg); border:2px solid var(--accent); z-index:9; }
|
|
56776
|
+
.skip:focus { left:8px; top:8px; }
|
|
56777
|
+
.wrap { max-width:78ch; margin:0 auto; padding:32px 20px 64px; }
|
|
56778
|
+
header.top { border-bottom:1px solid var(--line); }
|
|
56779
|
+
h1 { font-size:26px; line-height:1.25; margin:0 0 6px; }
|
|
56780
|
+
h2 { font-size:20px; margin:36px 0 10px; padding-top:8px; border-top:1px solid var(--line); }
|
|
56781
|
+
h3 { font-size:17px; margin:26px 0 8px; }
|
|
56782
|
+
h4 { font-size:15px; margin:20px 0 6px; }
|
|
56783
|
+
p { margin:0 0 12px; } p.sub { color:var(--mut); margin:0 0 4px; }
|
|
56784
|
+
a { color:var(--accent); }
|
|
56785
|
+
a:focus-visible, summary:focus-visible { outline:3px solid var(--accent); outline-offset:2px; }
|
|
56786
|
+
nav.docs ul { list-style:none; display:flex; flex-wrap:wrap; gap:6px 16px; margin:12px 0 0; padding:0; }
|
|
56787
|
+
nav.docs [aria-current="page"] { font-weight:700; }
|
|
56788
|
+
code, .mono { font:13px/1.5 ui-monospace,SFMono-Regular,Menlo,monospace; overflow-wrap:anywhere; }
|
|
56789
|
+
ul.body { margin:0 0 12px; padding-left:22px; }
|
|
56790
|
+
.note { margin:0 0 14px; padding:10px 14px; border-left:4px solid var(--mut); background:var(--soft); }
|
|
56791
|
+
.note.warn { border-left-color:var(--m); }
|
|
56792
|
+
.tw { overflow-x:auto; margin:0 0 18px; }
|
|
56793
|
+
table { border-collapse:collapse; width:100%; font-size:14px; }
|
|
56794
|
+
caption { text-align:left; font-weight:600; padding:0 0 8px; }
|
|
56795
|
+
th, td { padding:6px 10px; border-bottom:1px solid var(--line); text-align:left; vertical-align:top; }
|
|
56796
|
+
thead th { border-bottom:2px solid var(--line); background:var(--soft); }
|
|
56797
|
+
td.end, th.end { text-align:right; }
|
|
56798
|
+
tr.group th { background:var(--soft); font-weight:700; }
|
|
56799
|
+
.st { font-weight:700; }
|
|
56800
|
+
.st-c{color:var(--c)} .st-nc{color:var(--nc)} .st-na{color:var(--mut)} .st-m{color:var(--m)}
|
|
56801
|
+
figure { margin:0 0 18px; }
|
|
56802
|
+
figure img { max-width:100%; height:auto; border:1px solid var(--line); }
|
|
56803
|
+
figcaption { color:var(--mut); font-size:13px; margin-top:6px; }
|
|
56804
|
+
details { margin:0 0 14px; border:1px solid var(--line); padding:8px 12px; }
|
|
56805
|
+
summary { cursor:pointer; font-weight:600; }
|
|
56806
|
+
footer.bot { margin-top:48px; padding-top:12px; border-top:1px solid var(--line); color:var(--mut); font-size:13px; }
|
|
56807
|
+
@media print {
|
|
56808
|
+
:root { --bg:#fff; --fg:#000; --line:#bbb; --mut:#444; --soft:#f2f2f2; --c:#0a7d33; --nc:#b3261e; --m:#8a4b00; --accent:#00449e; }
|
|
56809
|
+
.skip, nav.docs { display:none; }
|
|
56810
|
+
.wrap { max-width:none; padding:0; }
|
|
56811
|
+
body { font-size:11pt; }
|
|
56812
|
+
/* One criterion per block, never split across a page break \u2014 the whole point of a
|
|
56813
|
+
printable deliverable is that a finding and its evidence stay on the same sheet. */
|
|
56814
|
+
h2, h3 { break-after:avoid-page; page-break-after:avoid; }
|
|
56815
|
+
figure, table, .note, details { break-inside:avoid-page; page-break-inside:avoid; }
|
|
56816
|
+
details { border:0; padding:0; } details > summary { list-style:none; }
|
|
56817
|
+
/* A collapsed section would print as a title with nothing under it. */
|
|
56818
|
+
details[open], details:not([open]) > *:not(summary) { display:revert; }
|
|
56819
|
+
figure img { max-width:16cm; }
|
|
56820
|
+
a { color:inherit; text-decoration:underline; }
|
|
56821
|
+
}`;
|
|
56822
|
+
function runHtml(r) {
|
|
56823
|
+
let inner = esc2(r.text);
|
|
56824
|
+
if (r.mono) inner = `<code>${inner}</code>`;
|
|
56825
|
+
if (r.strong) inner = `<strong>${inner}</strong>`;
|
|
56826
|
+
if (r.em) inner = `<em>${inner}</em>`;
|
|
56827
|
+
return r.href ? `<a href="${esc2(r.href)}">${inner}</a>` : inner;
|
|
56828
|
+
}
|
|
56829
|
+
var runsHtml = (runs) => runs.map(runHtml).join("");
|
|
56830
|
+
function cellHtml(c2, tag) {
|
|
56831
|
+
const attrs = [];
|
|
56832
|
+
if (tag === "th") attrs.push('scope="row"');
|
|
56833
|
+
if (c2.align === "end") attrs.push('class="end"');
|
|
56834
|
+
if (c2.colspan && c2.colspan > 1) attrs.push(`colspan="${c2.colspan}"`);
|
|
56835
|
+
let inner;
|
|
56836
|
+
if (c2.status) {
|
|
56837
|
+
inner = `<span class="st st-${CLASS[c2.status]}">${esc2(MARK4[c2.status])}</span>`;
|
|
56838
|
+
} else {
|
|
56839
|
+
inner = c2.text === "" ? "\u2014" : runHtml({ text: c2.text, mono: c2.mono, strong: c2.strong, ...c2.href ? { href: c2.href } : {} });
|
|
56840
|
+
}
|
|
56841
|
+
return `<${tag}${attrs.length ? ` ${attrs.join(" ")}` : ""}>${inner}</${tag}>`;
|
|
56842
|
+
}
|
|
56843
|
+
function blockHtml(b) {
|
|
56844
|
+
switch (b.kind) {
|
|
56845
|
+
case "heading":
|
|
56846
|
+
return [`<h${b.level}${b.id ? ` id="${esc2(b.id)}"` : ""}>${esc2(b.text)}</h${b.level}>`];
|
|
56847
|
+
case "para":
|
|
56848
|
+
return [`<p>${runsHtml(b.runs)}</p>`];
|
|
56849
|
+
case "note":
|
|
56850
|
+
return [`<div class="note${b.tone === "warn" ? " warn" : ""}">${runsHtml(b.runs)}</div>`];
|
|
56851
|
+
case "list":
|
|
56852
|
+
return [`<ul class="body">${b.items.map((i2) => `<li>${runsHtml(i2)}</li>`).join("")}</ul>`];
|
|
56853
|
+
case "figure":
|
|
56854
|
+
return [`<figure><img src="${esc2(b.src)}" alt="${esc2(b.alt)}">${b.caption ? `<figcaption>${esc2(b.caption)}</figcaption>` : ""}</figure>`];
|
|
56855
|
+
case "details":
|
|
56856
|
+
return [`<details open><summary>${esc2(b.summary)}</summary>`, ...b.blocks.flatMap(blockHtml), "</details>"];
|
|
56857
|
+
case "table": {
|
|
56858
|
+
const out2 = ['<div class="tw">', "<table>", `<caption>${esc2(b.caption)}</caption>`, "<thead><tr>"];
|
|
56859
|
+
for (const col of b.columns) out2.push(`<th scope="col"${col.align === "end" ? ' class="end"' : ""}>${esc2(col.text)}</th>`);
|
|
56860
|
+
out2.push("</tr></thead><tbody>");
|
|
56861
|
+
for (const row of b.rows) {
|
|
56862
|
+
const group = row.length === 1 && (row[0]?.colspan ?? 0) > 1;
|
|
56863
|
+
if (group) {
|
|
56864
|
+
const c2 = row[0];
|
|
56865
|
+
out2.push(`<tr class="group"><th scope="colgroup" colspan="${c2.colspan}">${esc2(c2.text)}</th></tr>`);
|
|
56866
|
+
continue;
|
|
56867
|
+
}
|
|
56868
|
+
out2.push(`<tr>${row.map((c2, i2) => cellHtml(c2, i2 === 0 || c2.header ? "th" : "td")).join("")}</tr>`);
|
|
56869
|
+
}
|
|
56870
|
+
out2.push("</tbody></table>", "</div>");
|
|
56871
|
+
return out2;
|
|
56872
|
+
}
|
|
56873
|
+
}
|
|
56874
|
+
}
|
|
56875
|
+
function renderHtmlDocument(doc) {
|
|
56876
|
+
const t2 = T3[doc.lang];
|
|
56877
|
+
const out2 = [];
|
|
56878
|
+
out2.push(`<!doctype html>`, `<html lang="${doc.lang}">`, "<head>");
|
|
56879
|
+
out2.push('<meta charset="utf-8">', '<meta name="viewport" content="width=device-width, initial-scale=1">');
|
|
56880
|
+
out2.push(`<title>${esc2(doc.title)}</title>`);
|
|
56881
|
+
out2.push(`<style>${STYLE}</style>`);
|
|
56882
|
+
out2.push("</head>", "<body>");
|
|
56883
|
+
out2.push(`<a class="skip" href="#content">${esc2(t2.skip)}</a>`);
|
|
56884
|
+
out2.push('<header class="top"><div class="wrap">');
|
|
56885
|
+
out2.push(`<h1>${esc2(doc.title)}</h1>`);
|
|
56886
|
+
if (doc.subtitle?.length) out2.push(`<p class="sub">${runsHtml(doc.subtitle)}</p>`);
|
|
56887
|
+
if (doc.nav?.length) {
|
|
56888
|
+
out2.push(`<nav class="docs" aria-label="${esc2(t2.nav)}"><ul>`);
|
|
56889
|
+
for (const l of doc.nav) out2.push(`<li><a href="${esc2(l.href)}"${l.current ? ' aria-current="page"' : ""}>${esc2(l.text)}</a></li>`);
|
|
56890
|
+
out2.push("</ul></nav>");
|
|
56891
|
+
}
|
|
56892
|
+
out2.push("</div></header>");
|
|
56893
|
+
out2.push('<main id="content" class="wrap">');
|
|
56894
|
+
for (const b of doc.blocks) out2.push(...blockHtml(b));
|
|
56895
|
+
out2.push("</main>");
|
|
56896
|
+
out2.push(`<footer class="bot"><div class="wrap"><p>${esc2(t2.generated)} \xB7 ${esc2(t2.printHint)}</p></div></footer>`);
|
|
56897
|
+
out2.push("</body>", "</html>");
|
|
56898
|
+
return `${out2.join("\n")}
|
|
56899
|
+
`;
|
|
56900
|
+
}
|
|
56901
|
+
|
|
56902
|
+
// src/html-report.ts
|
|
56903
|
+
function refusalBlocks(r) {
|
|
56904
|
+
if (!r) return [];
|
|
56905
|
+
return [
|
|
56906
|
+
{ kind: "note", tone: "warn", runs: [{ text: r.headline }] },
|
|
56907
|
+
{ kind: "list", items: r.reasons.map((text) => [{ text }]) }
|
|
56908
|
+
];
|
|
56909
|
+
}
|
|
56910
|
+
var T4 = {
|
|
56911
|
+
fr: {
|
|
56912
|
+
reportTitle: "Rapport de conformit\xE9",
|
|
56913
|
+
indexTitle: "Rapport d'accessibilit\xE9",
|
|
56914
|
+
pagesTitle: "Rapport page par page",
|
|
56915
|
+
date: "Date",
|
|
56916
|
+
files: "fichiers",
|
|
56917
|
+
rate: "r\xE9ussite automatique",
|
|
56918
|
+
synthesis: "Synth\xE8se",
|
|
56919
|
+
synthCaption: (h) => `Nombre de crit\xE8res par statut, regroup\xE9s par ${h}.`,
|
|
56920
|
+
byGuideline: "r\xE8gle WCAG",
|
|
56921
|
+
byTheme: "th\xE9matique",
|
|
56922
|
+
total: "Total",
|
|
56923
|
+
conforming: "C",
|
|
56924
|
+
nonConforming: "NC",
|
|
56925
|
+
notApplicable: "NA",
|
|
56926
|
+
toAssess: "\xC0 \xE9valuer",
|
|
56927
|
+
group: "Groupe",
|
|
56928
|
+
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.`,
|
|
56929
|
+
ncTitle: "Non-conformit\xE9s",
|
|
56930
|
+
ncCaption: "Une entr\xE9e par crit\xE8re non conforme.",
|
|
56931
|
+
noNc: "Aucune non-conformit\xE9 relev\xE9e par le moteur statique. Les crit\xE8res \xAB \xE0 \xE9valuer \xBB restent \xE0 trancher.",
|
|
56932
|
+
recTitle: "Recommandations (non normatives)",
|
|
56933
|
+
recNote: "Bonnes pratiques sans test normatif en \xE9chec : elles ne rendent aucun crit\xE8re non conforme et n'entrent pas dans le taux.",
|
|
56934
|
+
occurrences: "Occurrences",
|
|
56935
|
+
occCaption: (id) => `Occurrences du crit\xE8re ${id}.`,
|
|
56936
|
+
where: "Emplacement",
|
|
56937
|
+
selector: "S\xE9lecteur",
|
|
56938
|
+
what: "Constat",
|
|
56939
|
+
evidence: "Preuve",
|
|
56940
|
+
perPage: "Bilan page par page",
|
|
56941
|
+
perPageCaption: "Taux et constats par page, avec la base sur laquelle chaque page a \xE9t\xE9 jug\xE9e.",
|
|
56942
|
+
page: "Page",
|
|
56943
|
+
url: "URL",
|
|
56944
|
+
basis: "Base",
|
|
56945
|
+
pageRate: "Taux",
|
|
56946
|
+
sheet: "Fiche",
|
|
56947
|
+
grid: "Grille des crit\xE8res",
|
|
56948
|
+
gridCaption: (n) => `Statut de chaque crit\xE8re pour la page ${n}.`,
|
|
56949
|
+
crossGridCaption: "Statut de chaque crit\xE8re, page par page.",
|
|
56950
|
+
criterion: "Crit\xE8re",
|
|
56951
|
+
status: "Statut",
|
|
56952
|
+
legend: "`C` conforme \xB7 `NC` non conforme \xB7 `\u2014` non applicable \xB7 `?` \xE0 \xE9valuer.",
|
|
56953
|
+
tally: (c2, nc, na, m) => `${c2} conforme(s) \xB7 ${nc} non conforme(s) \xB7 ${na} non applicable(s) \xB7 ${m} \xE0 \xE9valuer`,
|
|
56954
|
+
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`.",
|
|
56955
|
+
documents: "Documents",
|
|
56956
|
+
openComposite: "Rapport complet, en un seul fichier (imprimable en PDF)",
|
|
56957
|
+
openPages: "Rapport page par page",
|
|
56958
|
+
noScreenshot: "Aucune capture d'\xE9cran pour cette page.",
|
|
56959
|
+
screenshotAlt: (n) => `Capture d'\xE9cran de la page ${n}`
|
|
56960
|
+
},
|
|
56961
|
+
en: {
|
|
56962
|
+
reportTitle: "Conformance report",
|
|
56963
|
+
indexTitle: "Accessibility report",
|
|
56964
|
+
pagesTitle: "Page-by-page report",
|
|
56965
|
+
date: "Date",
|
|
56966
|
+
files: "files",
|
|
56967
|
+
rate: "automatic pass rate",
|
|
56968
|
+
synthesis: "Synthesis",
|
|
56969
|
+
synthCaption: (h) => `Criteria count per status, grouped by ${h}.`,
|
|
56970
|
+
byGuideline: "WCAG guideline",
|
|
56971
|
+
byTheme: "theme",
|
|
56972
|
+
total: "Total",
|
|
56973
|
+
conforming: "C",
|
|
56974
|
+
nonConforming: "NC",
|
|
56975
|
+
notApplicable: "NA",
|
|
56976
|
+
toAssess: "To assess",
|
|
56977
|
+
group: "Group",
|
|
56978
|
+
coverage: (d, t2) => `Coverage: ${d}/${t2} criteria decided. The rate covers only those and says nothing about the other ${t2 - d}.`,
|
|
56979
|
+
ncTitle: "Non-conformities",
|
|
56980
|
+
ncCaption: "One entry per non-conforming criterion.",
|
|
56981
|
+
noNc: "No non-conformity found by the static engine. The criteria left to assess are still open.",
|
|
56982
|
+
recTitle: "Recommendations (non-normative)",
|
|
56983
|
+
recNote: "Good practices with no failing normative test: they never make a criterion non-conforming and do not enter the rate.",
|
|
56984
|
+
occurrences: "Occurrences",
|
|
56985
|
+
occCaption: (id) => `Occurrences of criterion ${id}.`,
|
|
56986
|
+
where: "Location",
|
|
56987
|
+
selector: "Selector",
|
|
56988
|
+
what: "Finding",
|
|
56989
|
+
evidence: "Evidence",
|
|
56990
|
+
perPage: "Page-by-page scoreboard",
|
|
56991
|
+
perPageCaption: "Rate and findings per page, with the basis each page was judged on.",
|
|
56992
|
+
page: "Page",
|
|
56993
|
+
url: "URL",
|
|
56994
|
+
basis: "Basis",
|
|
56995
|
+
pageRate: "Rate",
|
|
56996
|
+
sheet: "Sheet",
|
|
56997
|
+
grid: "Criteria grid",
|
|
56998
|
+
gridCaption: (n) => `Each criterion's status for the ${n} page.`,
|
|
56999
|
+
crossGridCaption: "Each criterion's status, page by page.",
|
|
57000
|
+
criterion: "Criterion",
|
|
57001
|
+
status: "Status",
|
|
57002
|
+
legend: "`C` conforming \xB7 `NC` non-conforming \xB7 `\u2014` not applicable \xB7 `?` to assess.",
|
|
57003
|
+
tally: (c2, nc, na, m) => `${c2} conforming \xB7 ${nc} non-conforming \xB7 ${na} not applicable \xB7 ${m} to assess`,
|
|
57004
|
+
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`.',
|
|
57005
|
+
documents: "Documents",
|
|
57006
|
+
openComposite: "Full report, in a single file (printable to PDF)",
|
|
57007
|
+
openPages: "Page-by-page report",
|
|
57008
|
+
noScreenshot: "No screenshot for this page.",
|
|
57009
|
+
screenshotAlt: (n) => `Screenshot of the ${n} page`
|
|
57010
|
+
}
|
|
57011
|
+
};
|
|
57012
|
+
function ticks(text) {
|
|
57013
|
+
return text.split(/`([^`]+)`/).flatMap((part, i2) => part === "" ? [] : i2 % 2 === 1 ? [{ text: part, mono: true }] : [{ text: part }]);
|
|
57014
|
+
}
|
|
57015
|
+
var stdName = (standard) => isCore(standard) ? "WCAG 2.2 AA" : loadPack(standard).name;
|
|
57016
|
+
function headline(result, standard, lang) {
|
|
57017
|
+
const t2 = T4[lang];
|
|
57018
|
+
const groups = isCore(standard) ? reportGroups(result, lang) : packReportGroups(result, loadPack(standard), lang);
|
|
57019
|
+
const { decided, total } = reportCoverage(groups);
|
|
57020
|
+
const agentRuled = groups.some((g) => g.rows.some((r) => r.decidedBy === "agent" && r.status === "C"));
|
|
57021
|
+
return {
|
|
57022
|
+
runs: [
|
|
57023
|
+
{ text: result.date, mono: true },
|
|
57024
|
+
{ text: ` \xB7 ${result.scope.files} ${t2.files} \xB7 ` },
|
|
57025
|
+
{ text: `${formatRate(decided === 0 ? null : result.conformancePct, decided, total)}${agentRuled ? "*" : ""}`, strong: true },
|
|
57026
|
+
{ text: ` ${t2.rate}` }
|
|
57027
|
+
],
|
|
57028
|
+
agentRuled,
|
|
57029
|
+
decided,
|
|
57030
|
+
total
|
|
57031
|
+
};
|
|
57032
|
+
}
|
|
57033
|
+
function synthesisBlocks(result, standard, lang) {
|
|
57034
|
+
const t2 = T4[lang];
|
|
57035
|
+
const core = isCore(standard);
|
|
57036
|
+
const groups = core ? reportGroups(result, lang) : packReportGroups(result, loadPack(standard), lang);
|
|
57037
|
+
const tot = reportTotals(groups);
|
|
57038
|
+
const { decided, total } = reportCoverage(groups);
|
|
57039
|
+
const rows = groups.map((g) => {
|
|
57040
|
+
const x = tallyRows(g.rows);
|
|
57041
|
+
return [
|
|
57042
|
+
{ text: `${g.key} ${g.title}`.trim() },
|
|
57043
|
+
{ text: String(x.c), align: "end" },
|
|
57044
|
+
{ text: String(x.nc), align: "end" },
|
|
57045
|
+
{ text: String(x.na), align: "end" },
|
|
57046
|
+
{ text: String(x.manual), align: "end" }
|
|
57047
|
+
];
|
|
57048
|
+
});
|
|
57049
|
+
rows.push([
|
|
57050
|
+
{ text: t2.total, strong: true },
|
|
57051
|
+
{ text: String(tot.c), align: "end", strong: true },
|
|
57052
|
+
{ text: String(tot.nc), align: "end", strong: true },
|
|
57053
|
+
{ text: String(tot.na), align: "end", strong: true },
|
|
57054
|
+
{ text: String(tot.manual), align: "end", strong: true }
|
|
57055
|
+
]);
|
|
57056
|
+
return [
|
|
57057
|
+
{ kind: "heading", level: 2, text: t2.synthesis, id: "synthesis" },
|
|
57058
|
+
{
|
|
57059
|
+
kind: "table",
|
|
57060
|
+
caption: t2.synthCaption(core ? t2.byGuideline : t2.byTheme),
|
|
57061
|
+
columns: [
|
|
57062
|
+
{ text: t2.group },
|
|
57063
|
+
{ text: t2.conforming, align: "end" },
|
|
57064
|
+
{ text: t2.nonConforming, align: "end" },
|
|
57065
|
+
{ text: t2.notApplicable, align: "end" },
|
|
57066
|
+
{ text: t2.toAssess, align: "end" }
|
|
57067
|
+
],
|
|
57068
|
+
rows
|
|
57069
|
+
},
|
|
57070
|
+
{ kind: "note", tone: "warn", runs: ticks(t2.coverage(decided, total)) },
|
|
57071
|
+
{ kind: "note", tone: "warn", runs: ticks(t2.manualWarn) }
|
|
57072
|
+
];
|
|
57073
|
+
}
|
|
57074
|
+
function criterionBlocks(unit, standard, lang, level, crops) {
|
|
57075
|
+
const t2 = T4[lang];
|
|
57076
|
+
const m = auditorUnitModel(unit, standard, lang, { collapse: true });
|
|
57077
|
+
const out2 = [{ kind: "heading", level, text: `${m.icon} ${m.label}`, id: `c-${unit.criteriaId}` }];
|
|
57078
|
+
out2.push({ kind: "note", tone: "info", runs: ticks(m.normativeNote) });
|
|
57079
|
+
out2.push({ kind: "list", items: m.fields.map((f) => [{ text: `${f.label} : `, strong: true }, ...ticks(f.value)]) });
|
|
57080
|
+
out2.push({
|
|
57081
|
+
kind: "para",
|
|
57082
|
+
runs: [{ text: `${t2.what} (${m.conformanceTerms.nonConformant}) : `, strong: true }, { text: `${m.occurrences} \u2014 ${m.messages.join(" ; ")}` }]
|
|
57083
|
+
});
|
|
57084
|
+
if (m.fixes.length) out2.push({ kind: "para", runs: [{ text: `${m.conformanceTerms.conformant} : `, strong: true }, { text: m.fixes.join(" ; ") }] });
|
|
57085
|
+
const hasEvidence = crops ? m.normative.some((f) => crops(f)) : false;
|
|
57086
|
+
const columns = [{ text: t2.where }, { text: t2.selector }, { text: t2.what }, ...hasEvidence ? [{ text: t2.evidence }] : []];
|
|
57087
|
+
const rows = m.normative.map((f) => {
|
|
57088
|
+
const cells = [{ text: `${f.file}:${f.line}`, mono: true }, { text: f.selectorHint, mono: true }, { text: resolveOccurrence(f, lang) }];
|
|
57089
|
+
if (hasEvidence) cells.push({ text: crops?.(f) ? "\u25A3" : "" });
|
|
57090
|
+
return cells;
|
|
57091
|
+
});
|
|
57092
|
+
if (rows.length) out2.push({ kind: "table", caption: t2.occCaption(unit.criteriaId), columns, rows });
|
|
57093
|
+
if (crops) {
|
|
57094
|
+
for (const f of m.normative) {
|
|
57095
|
+
const c2 = crops(f);
|
|
57096
|
+
if (c2) out2.push({ kind: "figure", src: c2.href, alt: c2.alt, caption: `${f.file}:${f.line} \u2014 ${f.selectorHint}` });
|
|
57097
|
+
}
|
|
57098
|
+
}
|
|
57099
|
+
return out2;
|
|
57100
|
+
}
|
|
57101
|
+
function resolveOccurrence(f, lang) {
|
|
57102
|
+
return f.msg ? auditorUnitModel({ criteriaId: f.criteriaId, title: "", label: "", refs: [], severity: f.severity, findings: [f] }, CORE2, lang).messages[0] ?? f.message : f.message;
|
|
57103
|
+
}
|
|
57104
|
+
function findingsBlocks(result, standard, lang, level, crops, refusals) {
|
|
57105
|
+
const t2 = T4[lang];
|
|
57106
|
+
const { nc, advisory } = partitionUnits(prdUnits(result, standard, lang));
|
|
57107
|
+
const out2 = [{ kind: "heading", level: 2, text: t2.ncTitle, id: "nc" }];
|
|
57108
|
+
out2.push(...refusalBlocks(refusals?.(null)));
|
|
57109
|
+
if (!nc.length) out2.push({ kind: "para", runs: [{ text: t2.noNc }] });
|
|
57110
|
+
for (const u of nc) out2.push(...criterionBlocks(u, standard, lang, level === 2 ? 3 : 4, crops));
|
|
57111
|
+
if (advisory.length) {
|
|
57112
|
+
out2.push({ kind: "heading", level: 2, text: t2.recTitle, id: "rec" });
|
|
57113
|
+
out2.push({ kind: "note", tone: "info", runs: ticks(t2.recNote) });
|
|
57114
|
+
for (const u of advisory) out2.push(...criterionBlocks(u, standard, lang, level === 2 ? 3 : 4, crops));
|
|
57115
|
+
}
|
|
57116
|
+
return out2;
|
|
57117
|
+
}
|
|
57118
|
+
function scoreboardBlocks(result, standard, lang, sheetHref) {
|
|
57119
|
+
const t2 = T4[lang];
|
|
57120
|
+
const scope = pagesOf(result);
|
|
57121
|
+
if (!scope.length) return [];
|
|
57122
|
+
const derived = derivePages(result, scope);
|
|
57123
|
+
const rows = derived.map((p) => [
|
|
57124
|
+
{ text: `${p.name}${p.auth ? " \u{1F512}" : ""}`, ...sheetHref ? { href: sheetHref(p.id) } : {} },
|
|
57125
|
+
{ text: p.url, mono: true },
|
|
57126
|
+
{ text: basisLabel(p.basis, lang) },
|
|
57127
|
+
{ text: formatRate(p.conformancePct, p.decided, p.total), align: "end" }
|
|
57128
|
+
]);
|
|
57129
|
+
const out2 = [
|
|
57130
|
+
{ kind: "heading", level: 2, text: t2.perPage, id: "pages" },
|
|
57131
|
+
{
|
|
57132
|
+
kind: "table",
|
|
57133
|
+
caption: t2.perPageCaption,
|
|
57134
|
+
columns: [{ text: t2.page }, { text: t2.url }, { text: t2.basis }, { text: t2.pageRate, align: "end" }],
|
|
57135
|
+
rows
|
|
57136
|
+
}
|
|
57137
|
+
];
|
|
57138
|
+
const orphans = unattributedFindings(result).filter((f) => !f.advisory).length;
|
|
57139
|
+
if (orphans) out2.push({ kind: "note", tone: "warn", runs: ticks(unattributedNote(orphans, lang)) });
|
|
57140
|
+
for (const basis of ["attributed", "not-audited"]) {
|
|
57141
|
+
const note = derived.some((p) => p.basis === basis) ? pageBasisWarning(basis, lang) : void 0;
|
|
57142
|
+
if (note) out2.push({ kind: "note", tone: "warn", runs: ticks(note) });
|
|
57143
|
+
}
|
|
57144
|
+
return out2;
|
|
57145
|
+
}
|
|
57146
|
+
function crossGridBlocks(result, derived, standard, lang) {
|
|
57147
|
+
const t2 = T4[lang];
|
|
57148
|
+
if (!derived.length) return [];
|
|
57149
|
+
const { rows, status } = pageGridModel(result, derived, standard, lang);
|
|
57150
|
+
if (!rows.length) return [];
|
|
57151
|
+
const table = {
|
|
57152
|
+
kind: "table",
|
|
57153
|
+
caption: t2.crossGridCaption,
|
|
57154
|
+
columns: [{ text: t2.criterion }, ...derived.map((p) => ({ text: `${p.name}${p.auth ? " \u{1F512}" : ""}` }))],
|
|
57155
|
+
rows: []
|
|
57156
|
+
};
|
|
57157
|
+
let group = "";
|
|
57158
|
+
for (const row of rows) {
|
|
57159
|
+
if (row.group !== group) {
|
|
57160
|
+
group = row.group;
|
|
57161
|
+
table.rows.push([{ text: group, colspan: derived.length + 1 }]);
|
|
57162
|
+
}
|
|
57163
|
+
table.rows.push([{ text: row.label }, ...derived.map((p) => ({ status: status.get(row.id)?.get(p.id) ?? "manual", text: "" }))]);
|
|
57164
|
+
}
|
|
57165
|
+
return [{ kind: "heading", level: 2, text: t2.grid, id: "grid" }, { kind: "note", tone: "info", runs: ticks(t2.legend) }, table];
|
|
57166
|
+
}
|
|
57167
|
+
function pageGridBlocks(result, page, standard, lang) {
|
|
57168
|
+
const t2 = T4[lang];
|
|
57169
|
+
const rows = pageCriterionRows(result, page, standard, lang);
|
|
57170
|
+
if (!rows.length) return [];
|
|
57171
|
+
const cov = pageCoverage(rows);
|
|
57172
|
+
const tally = pageTally(rows);
|
|
57173
|
+
const table = { kind: "table", caption: t2.gridCaption(page.name), columns: [{ text: t2.criterion }, { text: t2.status }], rows: [] };
|
|
57174
|
+
let group = "";
|
|
57175
|
+
for (const row of rows) {
|
|
57176
|
+
if (row.group !== group) {
|
|
57177
|
+
group = row.group;
|
|
57178
|
+
table.rows.push([{ text: group, colspan: 2 }]);
|
|
57179
|
+
}
|
|
57180
|
+
table.rows.push([{ text: row.label }, { status: row.status, text: row.decidedBy === "agent" && row.status === "C" ? "*" : "" }]);
|
|
57181
|
+
}
|
|
57182
|
+
const out2 = [
|
|
57183
|
+
{ kind: "heading", level: 2, text: t2.grid },
|
|
57184
|
+
{
|
|
57185
|
+
kind: "para",
|
|
57186
|
+
runs: [
|
|
57187
|
+
{ text: `${t2.pageRate} : `, strong: true },
|
|
57188
|
+
{ text: formatRate(pageRatePct(rows), cov.decided, cov.total), strong: true }
|
|
57189
|
+
]
|
|
57190
|
+
},
|
|
57191
|
+
{ kind: "para", runs: [{ text: t2.tally(tally.c, tally.nc, tally.na, tally.manual) }] },
|
|
57192
|
+
{ kind: "note", tone: "info", runs: ticks(t2.legend) },
|
|
57193
|
+
table
|
|
57194
|
+
];
|
|
57195
|
+
if (rows.some((r) => r.decidedBy === "agent" && r.status === "C")) out2.push({ kind: "note", tone: "warn", runs: ticks(agentMarkNote(lang)) });
|
|
57196
|
+
return out2;
|
|
57197
|
+
}
|
|
57198
|
+
function indexDoc(result, opts = {}) {
|
|
57199
|
+
const standard = opts.standard ?? CORE2;
|
|
57200
|
+
const lang = opts.lang ?? "en";
|
|
57201
|
+
const t2 = T4[lang];
|
|
57202
|
+
const h = headline(result, standard, lang);
|
|
57203
|
+
const blocks = [];
|
|
57204
|
+
if (opts.links?.length) {
|
|
57205
|
+
blocks.push({ kind: "heading", level: 2, text: t2.documents, id: "documents" });
|
|
57206
|
+
blocks.push({ kind: "list", items: opts.links.map((l) => [{ text: l.text, href: l.href }]) });
|
|
57207
|
+
}
|
|
57208
|
+
blocks.push(...synthesisBlocks(result, standard, lang));
|
|
57209
|
+
if (h.agentRuled) blocks.push({ kind: "note", tone: "warn", runs: ticks(agentMarkNote(lang)) });
|
|
57210
|
+
blocks.push(...scoreboardBlocks(result, standard, lang));
|
|
57211
|
+
return { lang, title: `${t2.indexTitle} \u2014 ${stdName(standard)}`, subtitle: h.runs, ...opts.nav ? { nav: opts.nav } : {}, blocks };
|
|
57212
|
+
}
|
|
57213
|
+
function compositeDoc(result, opts = {}) {
|
|
57214
|
+
const standard = opts.standard ?? CORE2;
|
|
57215
|
+
const lang = opts.lang ?? "en";
|
|
57216
|
+
const t2 = T4[lang];
|
|
57217
|
+
const h = headline(result, standard, lang);
|
|
57218
|
+
const blocks = [...synthesisBlocks(result, standard, lang)];
|
|
57219
|
+
if (h.agentRuled) blocks.push({ kind: "note", tone: "warn", runs: ticks(agentMarkNote(lang)) });
|
|
57220
|
+
blocks.push(...findingsBlocks(result, standard, lang, 2, opts.crops, opts.refusals));
|
|
57221
|
+
blocks.push(...scoreboardBlocks(result, standard, lang));
|
|
57222
|
+
const scope = pagesOf(result);
|
|
57223
|
+
if (scope.length) blocks.push(...crossGridBlocks(result, derivePages(result, scope), standard, lang));
|
|
57224
|
+
return { lang, title: `${t2.reportTitle} ${standardLabel(standard)} \u2014 ${result.date}`, subtitle: h.runs, ...opts.nav ? { nav: opts.nav } : {}, blocks };
|
|
57225
|
+
}
|
|
57226
|
+
function pagesIndexDoc(result, opts = { sheetHref: (id) => `./page-${id}.html` }) {
|
|
57227
|
+
const standard = opts.standard ?? CORE2;
|
|
57228
|
+
const lang = opts.lang ?? "en";
|
|
57229
|
+
const t2 = T4[lang];
|
|
57230
|
+
const h = headline(result, standard, lang);
|
|
57231
|
+
const scope = pagesOf(result);
|
|
57232
|
+
const blocks = [...scoreboardBlocks(result, standard, lang, opts.sheetHref)];
|
|
57233
|
+
if (scope.length) blocks.push(...crossGridBlocks(result, derivePages(result, scope), standard, lang));
|
|
57234
|
+
return { lang, title: `${t2.pagesTitle} \u2014 ${stdName(standard)}`, subtitle: h.runs, ...opts.nav ? { nav: opts.nav } : {}, blocks };
|
|
57235
|
+
}
|
|
57236
|
+
function pageDoc(result, page, opts = {}) {
|
|
57237
|
+
const standard = opts.standard ?? CORE2;
|
|
57238
|
+
const lang = opts.lang ?? "en";
|
|
57239
|
+
const t2 = T4[lang];
|
|
57240
|
+
const blocks = [
|
|
57241
|
+
{
|
|
57242
|
+
kind: "list",
|
|
57243
|
+
items: [
|
|
57244
|
+
[
|
|
57245
|
+
{ text: `${t2.url} : `, strong: true },
|
|
57246
|
+
{ text: page.url, mono: true }
|
|
57247
|
+
],
|
|
57248
|
+
[{ text: `${t2.basis} : `, strong: true }, { text: basisLabel(page.basis, lang) }]
|
|
57249
|
+
]
|
|
57250
|
+
}
|
|
57251
|
+
];
|
|
57252
|
+
const warn = pageBasisWarning(page.basis, lang);
|
|
57253
|
+
if (warn) blocks.push({ kind: "note", tone: "warn", runs: ticks(warn) });
|
|
57254
|
+
blocks.push(
|
|
57255
|
+
opts.screenshot ? { kind: "figure", src: opts.screenshot, alt: t2.screenshotAlt(page.name) } : { kind: "para", runs: [{ text: t2.noScreenshot, em: true }] }
|
|
57256
|
+
);
|
|
57257
|
+
blocks.push(...refusalBlocks(opts.refusals?.(page.id)));
|
|
57258
|
+
blocks.push(...pageGridBlocks(result, page, standard, lang));
|
|
57259
|
+
const view = { ...result, criteria: page.criteria, findings: page.findings };
|
|
57260
|
+
const { nc, advisory } = partitionUnits(prdUnits(view, standard, lang));
|
|
57261
|
+
blocks.push({ kind: "heading", level: 2, text: t2.ncTitle });
|
|
57262
|
+
if (!nc.length) blocks.push({ kind: "para", runs: [{ text: t2.noNc }] });
|
|
57263
|
+
for (const u of nc) blocks.push(...criterionBlocks(u, standard, lang, 3, opts.crops));
|
|
57264
|
+
if (advisory.length) {
|
|
57265
|
+
blocks.push({ kind: "heading", level: 2, text: t2.recTitle });
|
|
57266
|
+
blocks.push({ kind: "note", tone: "info", runs: ticks(t2.recNote) });
|
|
57267
|
+
for (const u of advisory) blocks.push(...criterionBlocks(u, standard, lang, 3, opts.crops));
|
|
57268
|
+
}
|
|
57269
|
+
return {
|
|
57270
|
+
lang,
|
|
57271
|
+
title: `${page.name} \u2014 ${stdName(standard)}`,
|
|
57272
|
+
subtitle: [{ text: result.date, mono: true }, { text: ` \xB7 ${findingsForStandard(view, standard).length} ${t2.what.toLowerCase()}` }],
|
|
57273
|
+
...opts.nav ? { nav: opts.nav } : {},
|
|
57274
|
+
blocks
|
|
57275
|
+
};
|
|
57276
|
+
}
|
|
57277
|
+
|
|
57278
|
+
// src/html-emit.ts
|
|
57279
|
+
var DEFAULT_INLINE_BUDGET = 12 * 1024 * 1024;
|
|
57280
|
+
var T5 = {
|
|
57281
|
+
fr: {
|
|
57282
|
+
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.`,
|
|
57283
|
+
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.`,
|
|
57284
|
+
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.`,
|
|
57285
|
+
docTitle: "Rapport complet, en un seul fichier (imprimable en PDF)",
|
|
57286
|
+
pagesTitle: "Rapport page par page",
|
|
57287
|
+
indexTitle: "Tableau de bord"
|
|
57288
|
+
},
|
|
57289
|
+
en: {
|
|
57290
|
+
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.`,
|
|
57291
|
+
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.`,
|
|
57292
|
+
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.`,
|
|
57293
|
+
docTitle: "Full report, in a single file (printable to PDF)",
|
|
57294
|
+
pagesTitle: "Page-by-page report",
|
|
57295
|
+
indexTitle: "Dashboard"
|
|
57296
|
+
}
|
|
57297
|
+
};
|
|
57298
|
+
var mb = (n) => (n / (1024 * 1024)).toFixed(1);
|
|
57299
|
+
function inlineSize(path) {
|
|
57300
|
+
try {
|
|
57301
|
+
return Math.ceil(statSync11(path).size / 3) * 4;
|
|
57302
|
+
} catch {
|
|
57303
|
+
return 0;
|
|
57304
|
+
}
|
|
57305
|
+
}
|
|
57306
|
+
function dataUri(path) {
|
|
57307
|
+
try {
|
|
57308
|
+
return `data:image/png;base64,${readFileSync24(path).toString("base64")}`;
|
|
57309
|
+
} catch {
|
|
57310
|
+
return void 0;
|
|
57311
|
+
}
|
|
57312
|
+
}
|
|
57313
|
+
function cropLookup(m, lang, hrefOf) {
|
|
57314
|
+
if (!m) return () => void 0;
|
|
57315
|
+
return (f) => {
|
|
57316
|
+
const c2 = m.crops.get(findingId(f));
|
|
57317
|
+
if (!c2) return void 0;
|
|
57318
|
+
const href = hrefOf(c2.path, c2.href);
|
|
57319
|
+
return href ? { href, alt: c2.alt[lang] } : void 0;
|
|
57320
|
+
};
|
|
57321
|
+
}
|
|
57322
|
+
function refusalLookup(m, lang) {
|
|
57323
|
+
if (!m) return void 0;
|
|
57324
|
+
return (pageId) => evidenceRefusals(m, pageId, lang);
|
|
57325
|
+
}
|
|
57326
|
+
function pickRung(cropBytes, shotBytes, budget) {
|
|
57327
|
+
const sum = (xs) => xs.reduce((a, b) => a + b, 0);
|
|
57328
|
+
if (sum(cropBytes) + sum(shotBytes) <= budget) return { steps: [], cropsPerCriterion: Number.POSITIVE_INFINITY, shots: true };
|
|
57329
|
+
if (sum(cropBytes) <= budget) return { steps: ["screenshots"], cropsPerCriterion: Number.POSITIVE_INFINITY, shots: false };
|
|
57330
|
+
const smallest = [...cropBytes].sort((a, b) => a - b);
|
|
57331
|
+
let kept = 0;
|
|
57332
|
+
let total = 0;
|
|
57333
|
+
for (const b of smallest) {
|
|
57334
|
+
if (total + b > budget) break;
|
|
57335
|
+
total += b;
|
|
57336
|
+
kept++;
|
|
57337
|
+
}
|
|
57338
|
+
if (!kept) return { steps: ["screenshots", "crops", "none"], cropsPerCriterion: 0, shots: false };
|
|
57339
|
+
return { steps: ["screenshots", "crops"], cropsPerCriterion: 1, shots: false };
|
|
57340
|
+
}
|
|
57341
|
+
function writeHtml(result, opts) {
|
|
57342
|
+
const standard = opts.standard ?? CORE2;
|
|
57343
|
+
const lang = opts.lang ?? "en";
|
|
57344
|
+
const t2 = T5[lang];
|
|
57345
|
+
const budget = opts.inlineBudget ?? DEFAULT_INLINE_BUDGET;
|
|
57346
|
+
const stdKey = isCore(standard) ? "wcag" : loadPack(standard).key;
|
|
57347
|
+
mkdirSync14(opts.outDir, { recursive: true });
|
|
57348
|
+
const crops = [...opts.evidence?.crops.values() ?? []];
|
|
57349
|
+
const shots = [...opts.screenshots?.values() ?? []];
|
|
57350
|
+
const rung = pickRung(
|
|
57351
|
+
crops.map((c2) => inlineSize(c2.path)),
|
|
57352
|
+
shots.map(inlineSize),
|
|
57353
|
+
budget
|
|
57354
|
+
);
|
|
57355
|
+
const notices = [];
|
|
57356
|
+
if (rung.steps.includes("none")) notices.push(t2.overNone(mb(budget)));
|
|
57357
|
+
else if (rung.steps.includes("crops")) notices.push(t2.overCrops(mb(crops.reduce((a, c2) => a + inlineSize(c2.path), 0)), mb(budget)));
|
|
57358
|
+
else if (rung.steps.includes("screenshots"))
|
|
57359
|
+
notices.push(t2.overComposite(mb(crops.reduce((a, c2) => a + inlineSize(c2.path), 0) + shots.reduce((a, s) => a + inlineSize(s), 0)), mb(budget)));
|
|
57360
|
+
let inlinedBytes = 0;
|
|
57361
|
+
const perCriterion = /* @__PURE__ */ new Map();
|
|
57362
|
+
const inlineCrops = cropLookup(opts.evidence, lang, (path) => {
|
|
57363
|
+
if (rung.cropsPerCriterion === 0) return void 0;
|
|
57364
|
+
const uri = dataUri(path);
|
|
57365
|
+
if (!uri) return void 0;
|
|
57366
|
+
inlinedBytes += uri.length;
|
|
57367
|
+
return uri;
|
|
57368
|
+
});
|
|
57369
|
+
const budgetedCrops = (f) => {
|
|
57370
|
+
const c2 = opts.evidence?.crops.get(findingId(f));
|
|
57371
|
+
if (!c2) return void 0;
|
|
57372
|
+
const n = perCriterion.get(c2.criteriaId) ?? 0;
|
|
57373
|
+
if (n >= rung.cropsPerCriterion) return void 0;
|
|
57374
|
+
const drawn = inlineCrops(f);
|
|
57375
|
+
if (drawn) perCriterion.set(c2.criteriaId, n + 1);
|
|
57376
|
+
return drawn;
|
|
57377
|
+
};
|
|
57378
|
+
const flat = opts.layout === "pages";
|
|
57379
|
+
const sheetDir = flat ? opts.outDir : join41(opts.outDir, "pages");
|
|
57380
|
+
const up = flat ? "./" : "../";
|
|
57381
|
+
const compositeName = `ultra11y-${stdKey}-${result.date}.html`;
|
|
57382
|
+
const nav = flat ? [{ href: "./index.html", text: t2.pagesTitle }] : [
|
|
57383
|
+
{ href: "./index.html", text: t2.indexTitle },
|
|
57384
|
+
{ href: `./${compositeName}`, text: t2.docTitle },
|
|
57385
|
+
...opts.pages ? [{ href: "./pages/index.html", text: t2.pagesTitle }] : []
|
|
57386
|
+
];
|
|
57387
|
+
const refusals = refusalLookup(opts.evidence, lang);
|
|
57388
|
+
let compositePath;
|
|
57389
|
+
if (!flat) {
|
|
57390
|
+
const composite = compositeDoc(result, {
|
|
57391
|
+
standard,
|
|
57392
|
+
lang,
|
|
57393
|
+
crops: budgetedCrops,
|
|
57394
|
+
...refusals ? { refusals } : {},
|
|
57395
|
+
nav: nav.map((n) => ({ ...n, current: n.href === `./${compositeName}` }))
|
|
57396
|
+
});
|
|
57397
|
+
if (notices.length) composite.blocks.unshift({ kind: "note", tone: "warn", runs: noticeRuns(notices) });
|
|
57398
|
+
compositePath = join41(opts.outDir, compositeName);
|
|
57399
|
+
writeFileSync16(compositePath, renderHtmlDocument(composite));
|
|
57400
|
+
}
|
|
57401
|
+
const fileCrops = cropLookup(opts.evidence, lang, (_p, href) => href.replace(/^\.\//, up));
|
|
57402
|
+
const indexNav = nav.map((n) => ({ ...n, current: n.href === "./index.html" }));
|
|
57403
|
+
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") });
|
|
57404
|
+
const indexPath = join41(opts.outDir, "index.html");
|
|
57405
|
+
writeFileSync16(indexPath, renderHtmlDocument(index));
|
|
57406
|
+
const sheets = [];
|
|
57407
|
+
if (opts.pages) {
|
|
57408
|
+
const derived = derivePages(result, pagesOf(result));
|
|
57409
|
+
mkdirSync14(sheetDir, { recursive: true });
|
|
57410
|
+
const sheetNav = flat ? [{ href: "./index.html", text: t2.pagesTitle }] : [
|
|
57411
|
+
{ href: `${up}index.html`, text: t2.indexTitle },
|
|
57412
|
+
{ href: `${up}${compositeName}`, text: t2.docTitle },
|
|
57413
|
+
{ href: "./index.html", text: t2.pagesTitle }
|
|
57414
|
+
];
|
|
57415
|
+
if (!flat) {
|
|
57416
|
+
writeFileSync16(
|
|
57417
|
+
join41(sheetDir, "index.html"),
|
|
57418
|
+
renderHtmlDocument(
|
|
57419
|
+
pagesIndexDoc(result, {
|
|
57420
|
+
standard,
|
|
57421
|
+
lang,
|
|
57422
|
+
nav: sheetNav.map((n) => ({ ...n, current: n.href === "./index.html" })),
|
|
57423
|
+
sheetHref: (id) => `./page-${id}.html`
|
|
57424
|
+
})
|
|
57425
|
+
)
|
|
57426
|
+
);
|
|
57427
|
+
sheets.push(join41(sheetDir, "index.html"));
|
|
57428
|
+
}
|
|
57429
|
+
for (const p of derived) {
|
|
57430
|
+
const doc = pageDoc(result, p, {
|
|
57431
|
+
standard,
|
|
57432
|
+
lang,
|
|
57433
|
+
crops: fileCrops,
|
|
57434
|
+
...refusals ? { refusals } : {},
|
|
57435
|
+
nav: sheetNav,
|
|
57436
|
+
// Where `cmdPages`' screenshot copier already put the capture: `assets/<id>.png`,
|
|
57437
|
+
// beside the entry point, seen from wherever this sheet sits.
|
|
57438
|
+
...opts.screenshots?.has(p.id) ? { screenshot: `${up}assets/${p.id}.png` } : {}
|
|
57439
|
+
});
|
|
57440
|
+
const path = join41(sheetDir, `page-${p.id}.html`);
|
|
57441
|
+
writeFileSync16(path, renderHtmlDocument(doc));
|
|
57442
|
+
sheets.push(path);
|
|
57443
|
+
}
|
|
57444
|
+
}
|
|
57445
|
+
return {
|
|
57446
|
+
index: indexPath,
|
|
57447
|
+
...compositePath ? { composite: compositePath } : {},
|
|
57448
|
+
sheets,
|
|
57449
|
+
inlinedBytes,
|
|
57450
|
+
degraded: rung.steps,
|
|
57451
|
+
imagesDropped: rung.steps.includes("none"),
|
|
57452
|
+
notices
|
|
57453
|
+
};
|
|
57454
|
+
}
|
|
57455
|
+
function noticeRuns(notices) {
|
|
57456
|
+
return notices.flatMap((n, i2) => i2 ? [{ text: " " }, { text: n }] : [{ text: n }]);
|
|
57457
|
+
}
|
|
57458
|
+
|
|
56189
57459
|
// src/dev.ts
|
|
56190
57460
|
import { createServer } from "http";
|
|
56191
|
-
import { mkdirSync as
|
|
56192
|
-
import { dirname as dirname12, join as
|
|
57461
|
+
import { mkdirSync as mkdirSync15, readFileSync as readFileSync25, writeFileSync as writeFileSync17 } from "fs";
|
|
57462
|
+
import { dirname as dirname12, join as join42 } from "path";
|
|
56193
57463
|
var DEV_DEFAULT_PORT = 4111;
|
|
56194
57464
|
function criterionLabel3(f, standard) {
|
|
56195
57465
|
if (isCore(standard)) return `WCAG ${f.criteriaId}`;
|
|
@@ -56367,71 +57637,19 @@ export function Ultra11yOverlay({ endpoint = "http://127.0.0.1:${port}" }) {
|
|
|
56367
57637
|
export default Ultra11yOverlay;
|
|
56368
57638
|
`;
|
|
56369
57639
|
}
|
|
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
57640
|
function dashboardHtml(result, pages, standard, lang) {
|
|
56376
57641
|
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>`;
|
|
57642
|
+
const title2 = `ultra11y \u2014 ${isCore(standard) ? "WCAG 2.2 AA" : loadPack(standard).name}`;
|
|
56395
57643
|
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>`);
|
|
57644
|
+
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.";
|
|
57645
|
+
return renderHtmlDocument({ lang, title: title2, blocks: [{ kind: "para", runs: [{ text: empty }] }] });
|
|
56432
57646
|
}
|
|
56433
|
-
|
|
56434
|
-
|
|
57647
|
+
return renderHtmlDocument({
|
|
57648
|
+
lang,
|
|
57649
|
+
title: title2,
|
|
57650
|
+
subtitle: [{ text: `${pages.length} ${fr ? "page(s) captur\xE9e(s)" : "page(s) captured"} \xB7 ` }, { text: result.date, mono: true }],
|
|
57651
|
+
blocks: [...scoreboardBlocks(result, standard, lang), ...crossGridBlocks(result, pages, standard, lang)]
|
|
57652
|
+
});
|
|
56435
57653
|
}
|
|
56436
57654
|
function cors(res) {
|
|
56437
57655
|
res.setHeader("access-control-allow-origin", "*");
|
|
@@ -56463,13 +57681,13 @@ function auditCollected(root, payload) {
|
|
|
56463
57681
|
...payload.css ? { css: payload.css } : {},
|
|
56464
57682
|
...shot ? { screenshotBase64: shot } : {}
|
|
56465
57683
|
});
|
|
56466
|
-
return { ok: true, result: runAudit({ inputs: [
|
|
57684
|
+
return { ok: true, result: runAudit({ inputs: [join42(dir, "dom.html")] }) };
|
|
56467
57685
|
}
|
|
56468
57686
|
function projectPages(root) {
|
|
56469
57687
|
const snaps = readSnapshots(root);
|
|
56470
57688
|
if (!snaps.length) return { result: null, pages: [] };
|
|
56471
57689
|
const scope = pageScopesFrom(snaps);
|
|
56472
|
-
const result = runAudit({ inputs: [
|
|
57690
|
+
const result = runAudit({ inputs: [join42(root, ".ultra11y/pages")] });
|
|
56473
57691
|
result.scope.pages = scope;
|
|
56474
57692
|
attributePages(result, scope);
|
|
56475
57693
|
foldRecordedAdjudication(root, result);
|
|
@@ -56483,7 +57701,7 @@ function judgePages(result) {
|
|
|
56483
57701
|
function foldRecordedAdjudication(root, fresh) {
|
|
56484
57702
|
let prior;
|
|
56485
57703
|
try {
|
|
56486
|
-
prior = JSON.parse(
|
|
57704
|
+
prior = JSON.parse(readFileSync25(join42(root, "audits", "audit-latest.json"), "utf8"));
|
|
56487
57705
|
} catch {
|
|
56488
57706
|
return;
|
|
56489
57707
|
}
|
|
@@ -56598,9 +57816,9 @@ function startDevServer(opts) {
|
|
|
56598
57816
|
});
|
|
56599
57817
|
let auditPath;
|
|
56600
57818
|
if (applied.ok) {
|
|
56601
|
-
auditPath =
|
|
56602
|
-
|
|
56603
|
-
|
|
57819
|
+
auditPath = join42(opts.root, "audits", "audit-latest.json");
|
|
57820
|
+
mkdirSync15(dirname12(auditPath), { recursive: true });
|
|
57821
|
+
writeFileSync17(auditPath, `${JSON.stringify(applied.audit, null, 2)}
|
|
56604
57822
|
`);
|
|
56605
57823
|
}
|
|
56606
57824
|
res.writeHead(200, { "content-type": "application/json" }).end(
|
|
@@ -56914,11 +58132,11 @@ function e2eSetupPlan(runners, paths, lang = "en") {
|
|
|
56914
58132
|
}
|
|
56915
58133
|
|
|
56916
58134
|
// src/orchestrate.ts
|
|
56917
|
-
import { existsSync as
|
|
56918
|
-
import { join as
|
|
58135
|
+
import { existsSync as existsSync29, mkdirSync as mkdirSync16, readFileSync as readFileSync26, rmSync as rmSync7, writeFileSync as writeFileSync18 } from "fs";
|
|
58136
|
+
import { join as join44, resolve as resolve10 } from "path";
|
|
56919
58137
|
|
|
56920
58138
|
// src/orchestrate-templates.ts
|
|
56921
|
-
import { join as
|
|
58139
|
+
import { join as join43 } from "path";
|
|
56922
58140
|
var ONE_WRITER_FOOTER = `
|
|
56923
58141
|
## Return, don't write
|
|
56924
58142
|
|
|
@@ -56990,7 +58208,7 @@ var PHASE_SPECS = {
|
|
|
56990
58208
|
title: "Adjudicate",
|
|
56991
58209
|
schema: ADJUDICATE_SCHEMA,
|
|
56992
58210
|
description: (n) => `Adjudicate the ${n} residual judgment criterion(ia) of an ultra11y audit (fan-out, fail-closed fold)`,
|
|
56993
|
-
applyHint: (engine, worklist, run2) => `node ${engine} verify --apply ${worklist} --in ${
|
|
58211
|
+
applyHint: (engine, worklist, run2) => `node ${engine} verify --apply ${worklist} --in ${join43(run2, "audit-latest.json")} --out ${run2}`
|
|
56994
58212
|
},
|
|
56995
58213
|
"verify-report": {
|
|
56996
58214
|
role: "refuter",
|
|
@@ -57012,7 +58230,7 @@ function toBatches(ids, batchSize) {
|
|
|
57012
58230
|
}
|
|
57013
58231
|
function phaseWorkflowScript(ph, runAbs, engineAbs, batchSize) {
|
|
57014
58232
|
const spec = phaseSpec(ph.name);
|
|
57015
|
-
const scriptPath =
|
|
58233
|
+
const scriptPath = join43(runAbs, "orchestration", `${ph.name}.workflow.mjs`);
|
|
57016
58234
|
const meta2 = { name: `ultra11y-${ph.name}`, description: spec.description(ph.items), phases: [{ title: spec.title }] };
|
|
57017
58235
|
return [
|
|
57018
58236
|
`export const meta = ${JSON.stringify(meta2)}`,
|
|
@@ -57056,7 +58274,7 @@ function agentContracts(runAbs, engineAbs) {
|
|
|
57056
58274
|
|
|
57057
58275
|
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.
|
|
57058
58276
|
|
|
57059
|
-
Worklist: \`${
|
|
58277
|
+
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>\`).
|
|
57060
58278
|
|
|
57061
58279
|
For EACH of your criteria:
|
|
57062
58280
|
|
|
@@ -57074,7 +58292,7 @@ ${footer}`,
|
|
|
57074
58292
|
|
|
57075
58293
|
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.
|
|
57076
58294
|
|
|
57077
|
-
Worklist: \`${
|
|
58295
|
+
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>\`).
|
|
57078
58296
|
|
|
57079
58297
|
For EACH of your entries:
|
|
57080
58298
|
|
|
@@ -57110,14 +58328,14 @@ ${status}
|
|
|
57110
58328
|
|
|
57111
58329
|
## The loop (play every role yourself, one item at a time)
|
|
57112
58330
|
|
|
57113
|
-
1. **Audit** (if not done): \`${engine} audit "<globs>" --graph --out ${runAbs}\` \u2192 \`${
|
|
57114
|
-
2. **Adjudicate the residual criteria** \u2014 \`${engine} verify --manual --in ${
|
|
57115
|
-
3. **Report**: \`${engine} report --in ${
|
|
57116
|
-
4. **Verify the report's claims** \u2014 \`${engine} verify --report <the report .md> --out ${runAbs}\` writes \`${
|
|
58331
|
+
1. **Audit** (if not done): \`${engine} audit "<globs>" --graph --out ${runAbs}\` \u2192 \`${join43(runAbs, "audit-latest.json")}\`.
|
|
58332
|
+
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}\`.
|
|
58333
|
+
3. **Report**: \`${engine} report --in ${join43(runAbs, "audit-latest.json")} --out ${runAbs}\`.
|
|
58334
|
+
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>\`.
|
|
57117
58335
|
5. **Gate**: \`${engine} check --report <the report .md> --semantic\` must exit 0 before presenting anything.
|
|
57118
58336
|
6. **Fix & re-audit**: \`${engine} fix <globs> --write --iterate\`, hand-apply the judgment fixes, then loop from step 1 until the gate stays green.
|
|
57119
58337
|
|
|
57120
|
-
With subagents available, prefer the emitted workflows instead: \`orchestrate --run ${runAbs} --phase <p>\` then \`Workflow({ scriptPath: "${
|
|
58338
|
+
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.
|
|
57121
58339
|
`;
|
|
57122
58340
|
}
|
|
57123
58341
|
|
|
@@ -57127,12 +58345,12 @@ var SMALL_WORKLIST = 3;
|
|
|
57127
58345
|
var BATCH_SIZE2 = 8;
|
|
57128
58346
|
function listPhases(runDir, engineAbs) {
|
|
57129
58347
|
const run2 = resolve10(runDir);
|
|
57130
|
-
const adjPath =
|
|
58348
|
+
const adjPath = join44(run2, "ADJUDICATE.todo.json");
|
|
57131
58349
|
let adjIds = [];
|
|
57132
58350
|
let adjReady = false;
|
|
57133
|
-
if (
|
|
58351
|
+
if (existsSync29(adjPath)) {
|
|
57134
58352
|
try {
|
|
57135
|
-
const f = JSON.parse(
|
|
58353
|
+
const f = JSON.parse(readFileSync26(adjPath, "utf8"));
|
|
57136
58354
|
if (f && f.kind === "adjudication" && Array.isArray(f.items)) {
|
|
57137
58355
|
adjReady = true;
|
|
57138
58356
|
adjIds = f.items.map((i2) => i2.criteriaId);
|
|
@@ -57140,12 +58358,12 @@ function listPhases(runDir, engineAbs) {
|
|
|
57140
58358
|
} catch {
|
|
57141
58359
|
}
|
|
57142
58360
|
}
|
|
57143
|
-
const verPath =
|
|
58361
|
+
const verPath = join44(run2, "VERIFY.todo.json");
|
|
57144
58362
|
let verIds = [];
|
|
57145
58363
|
let verReady = false;
|
|
57146
|
-
if (
|
|
58364
|
+
if (existsSync29(verPath)) {
|
|
57147
58365
|
try {
|
|
57148
|
-
const items = JSON.parse(
|
|
58366
|
+
const items = JSON.parse(readFileSync26(verPath, "utf8"));
|
|
57149
58367
|
if (Array.isArray(items)) {
|
|
57150
58368
|
verReady = true;
|
|
57151
58369
|
verIds = items.map((i2) => String(i2.n));
|
|
@@ -57160,7 +58378,7 @@ function listPhases(runDir, engineAbs) {
|
|
|
57160
58378
|
worklist: adjPath,
|
|
57161
58379
|
items: adjIds.length,
|
|
57162
58380
|
ids: adjIds,
|
|
57163
|
-
prerequisite: `node ${engineAbs} verify --manual --in ${
|
|
58381
|
+
prerequisite: `node ${engineAbs} verify --manual --in ${join44(run2, "audit-latest.json")} --out ${run2}`
|
|
57164
58382
|
},
|
|
57165
58383
|
{
|
|
57166
58384
|
name: "verify-report",
|
|
@@ -57174,7 +58392,7 @@ function listPhases(runDir, engineAbs) {
|
|
|
57174
58392
|
}
|
|
57175
58393
|
function orchestrateRun(runDir, engineAbs, opts = {}) {
|
|
57176
58394
|
const run2 = resolve10(runDir);
|
|
57177
|
-
if (!
|
|
58395
|
+
if (!existsSync29(run2)) {
|
|
57178
58396
|
return { exitCode: 2, written: [], notices: [], errors: [`run dir not found: ${run2}`], phases: [] };
|
|
57179
58397
|
}
|
|
57180
58398
|
const phases = listPhases(run2, engineAbs);
|
|
@@ -57201,22 +58419,22 @@ function orchestrateRun(runDir, engineAbs, opts = {}) {
|
|
|
57201
58419
|
}
|
|
57202
58420
|
selected = [ph];
|
|
57203
58421
|
}
|
|
57204
|
-
const orchDir =
|
|
57205
|
-
const agentsDir =
|
|
57206
|
-
|
|
57207
|
-
|
|
58422
|
+
const orchDir = join44(run2, "orchestration");
|
|
58423
|
+
const agentsDir = join44(orchDir, "agents");
|
|
58424
|
+
mkdirSync16(join44(orchDir, "out"), { recursive: true });
|
|
58425
|
+
mkdirSync16(agentsDir, { recursive: true });
|
|
57208
58426
|
const written = [];
|
|
57209
58427
|
const notices = [];
|
|
57210
58428
|
for (const [name2, content] of Object.entries(agentContracts(run2, engineAbs))) {
|
|
57211
|
-
const p =
|
|
57212
|
-
|
|
58429
|
+
const p = join44(agentsDir, `${name2}.md`);
|
|
58430
|
+
writeFileSync18(p, content);
|
|
57213
58431
|
written.push(p);
|
|
57214
58432
|
}
|
|
57215
58433
|
const emitted = new Set(opts.eco ? [] : selected.filter((p) => p.items > 0).map((p) => p.name));
|
|
57216
58434
|
for (const ph of phases) {
|
|
57217
58435
|
if (emitted.has(ph.name)) continue;
|
|
57218
|
-
const stale =
|
|
57219
|
-
if (
|
|
58436
|
+
const stale = join44(orchDir, `${ph.name}.workflow.mjs`);
|
|
58437
|
+
if (existsSync29(stale)) {
|
|
57220
58438
|
rmSync7(stale, { force: true });
|
|
57221
58439
|
notices.push(`phase "${ph.name}": stale workflow removed \u2014 its worklist ${ph.ready ? "is now empty" : "no longer exists"}.`);
|
|
57222
58440
|
}
|
|
@@ -57230,13 +58448,13 @@ function orchestrateRun(runDir, engineAbs, opts = {}) {
|
|
|
57230
58448
|
if (ph.items <= SMALL_WORKLIST) {
|
|
57231
58449
|
notices.push(`phase "${ph.name}": only ${ph.items} item(s) \u2014 the sequential --eco path is equivalent and cheaper.`);
|
|
57232
58450
|
}
|
|
57233
|
-
const p =
|
|
57234
|
-
|
|
58451
|
+
const p = join44(orchDir, `${ph.name}.workflow.mjs`);
|
|
58452
|
+
writeFileSync18(p, phaseWorkflowScript(ph, run2, engineAbs, BATCH_SIZE2));
|
|
57235
58453
|
written.push(p);
|
|
57236
58454
|
}
|
|
57237
58455
|
}
|
|
57238
|
-
const rb =
|
|
57239
|
-
|
|
58456
|
+
const rb = join44(orchDir, "RUNBOOK.md");
|
|
58457
|
+
writeFileSync18(rb, runbookMd(phases, run2, engineAbs));
|
|
57240
58458
|
written.push(rb);
|
|
57241
58459
|
return { exitCode: 0, written, notices, errors: [], phases };
|
|
57242
58460
|
}
|
|
@@ -57245,8 +58463,8 @@ function orchestrateRun(runDir, engineAbs, opts = {}) {
|
|
|
57245
58463
|
import { createInterface as createInterface2 } from "readline";
|
|
57246
58464
|
|
|
57247
58465
|
// src/mcp/handlers.ts
|
|
57248
|
-
import { existsSync as
|
|
57249
|
-
import { isAbsolute as isAbsolute3, join as
|
|
58466
|
+
import { existsSync as existsSync30, readFileSync as readFileSync27, realpathSync as realpathSync4, statSync as statSync12 } from "fs";
|
|
58467
|
+
import { isAbsolute as isAbsolute3, join as join45, resolve as resolve11, sep as sep5 } from "path";
|
|
57250
58468
|
|
|
57251
58469
|
// src/project-lock.ts
|
|
57252
58470
|
var chains = /* @__PURE__ */ new Map();
|
|
@@ -57294,8 +58512,8 @@ function requiredCwd(args2, defaults) {
|
|
|
57294
58512
|
const cwd = str2(args2.cwd) ?? defaults.defaultCwd;
|
|
57295
58513
|
if (!cwd) throw new ToolError("`cwd` is required: an absolute path to the project root.");
|
|
57296
58514
|
const abs = resolve11(cwd);
|
|
57297
|
-
if (!
|
|
57298
|
-
if (!
|
|
58515
|
+
if (!existsSync30(abs)) throw new ToolError(`project root not found: ${abs}`);
|
|
58516
|
+
if (!statSync12(abs).isDirectory()) throw new ToolError(`\`cwd\` is not a directory: ${abs}`);
|
|
57299
58517
|
return abs;
|
|
57300
58518
|
}
|
|
57301
58519
|
function standardOf(args2) {
|
|
@@ -57360,7 +58578,7 @@ function audit(args2, cwd) {
|
|
|
57360
58578
|
process.chdir(cwd);
|
|
57361
58579
|
const inputs = strArray2(args2.globs) ?? DEFAULT_GLOBS;
|
|
57362
58580
|
const scopedToDiff = bool(args2.changed) || bool(args2.staged) || str2(args2.since) !== void 0;
|
|
57363
|
-
const extra = [CAPTURES_DIR, PAGES_DIR].filter((d) => !scopedToDiff &&
|
|
58581
|
+
const extra = [CAPTURES_DIR, PAGES_DIR].filter((d) => !scopedToDiff && existsSync30(d) && !inputs.includes(d));
|
|
57364
58582
|
const result = runAudit({
|
|
57365
58583
|
inputs: [...inputs, ...extra],
|
|
57366
58584
|
include: strArray2(args2.include),
|
|
@@ -57398,7 +58616,14 @@ function handleReport(args2, cwd) {
|
|
|
57398
58616
|
const lang = langOf(args2);
|
|
57399
58617
|
const r = audit(args2, cwd);
|
|
57400
58618
|
const md = standard === "wcag" ? renderReport(r, lang) : renderPackReport(r, loadPack(standard), lang);
|
|
57401
|
-
return {
|
|
58619
|
+
return {
|
|
58620
|
+
cwd,
|
|
58621
|
+
standard,
|
|
58622
|
+
lang,
|
|
58623
|
+
report: md,
|
|
58624
|
+
...args2.html === true ? { html: renderHtmlDocument(compositeDoc(r, { standard, lang })) } : {},
|
|
58625
|
+
next: "Validate it with ultra11y_check before presenting it."
|
|
58626
|
+
};
|
|
57402
58627
|
}
|
|
57403
58628
|
function handlePrd(args2, cwd) {
|
|
57404
58629
|
const standard = standardOf(args2);
|
|
@@ -57472,13 +58697,13 @@ function handlePackCheck(args2, cwd) {
|
|
|
57472
58697
|
return { cwd, ...res };
|
|
57473
58698
|
}
|
|
57474
58699
|
function handleSampleCheck(cwd, standard) {
|
|
57475
|
-
const file =
|
|
57476
|
-
if (!
|
|
58700
|
+
const file = join45(cwd, ".ultra11yrc.json");
|
|
58701
|
+
if (!existsSync30(file)) {
|
|
57477
58702
|
throw new ToolError(`no .ultra11yrc.json at ${cwd} \u2014 a page sample must be declared before it can be linted.`);
|
|
57478
58703
|
}
|
|
57479
58704
|
let raw;
|
|
57480
58705
|
try {
|
|
57481
|
-
raw = JSON.parse(
|
|
58706
|
+
raw = JSON.parse(readFileSync27(file, "utf8"));
|
|
57482
58707
|
} catch (e) {
|
|
57483
58708
|
throw new ToolError(`.ultra11yrc.json is not valid JSON: ${e.message}`);
|
|
57484
58709
|
}
|
|
@@ -57522,6 +58747,7 @@ function handlePages(args2, cwd) {
|
|
|
57522
58747
|
standard,
|
|
57523
58748
|
lang,
|
|
57524
58749
|
markdown,
|
|
58750
|
+
...args2.html === true ? { html: renderHtmlDocument(pagesIndexDoc(r, { standard, lang, sheetHref: (id) => `#page-${id}` })) } : {},
|
|
57525
58751
|
// `conformancePct` is null when this page decided nothing — a machine consumer must be able
|
|
57526
58752
|
// to tell "no criterion was assessed" from "every assessed criterion passed", which a bare
|
|
57527
58753
|
// 100 conflates. `decided`/`total` are the denominator that makes the number quotable.
|
|
@@ -57571,7 +58797,7 @@ function handleInit(args2, cwd) {
|
|
|
57571
58797
|
const enginePath = resolve11("scripts/ultra11y.mjs");
|
|
57572
58798
|
const written = [];
|
|
57573
58799
|
if (bool(args2.hook)) written.push(writeHook(cwd, enginePath, failOn));
|
|
57574
|
-
if (bool(args2.ci)) written.push(
|
|
58800
|
+
if (bool(args2.ci)) written.push(join45(cwd, ".github/workflows/a11y.yml"));
|
|
57575
58801
|
if (!written.length) {
|
|
57576
58802
|
throw new ToolError("nothing to install \u2014 pass hook:true and/or ci:true.");
|
|
57577
58803
|
}
|
|
@@ -57591,8 +58817,8 @@ function reportText(args2, tool) {
|
|
|
57591
58817
|
if (inline) return inline;
|
|
57592
58818
|
if (!file) throw new ToolError(`\`report_text\` is required \u2014 the report markdown for ultra11y_${tool} to work on.`);
|
|
57593
58819
|
if (!isAbsolute3(file)) throw new ToolError("`report_file` must be an absolute path.");
|
|
57594
|
-
if (!
|
|
57595
|
-
return
|
|
58820
|
+
if (!existsSync30(file)) throw new ToolError(`report file not found: ${file}`);
|
|
58821
|
+
return readFileSync27(file, "utf8");
|
|
57596
58822
|
}
|
|
57597
58823
|
function handleCriteria(args2) {
|
|
57598
58824
|
const lang = langOf(args2);
|
|
@@ -57607,7 +58833,7 @@ function handleCriteria(args2) {
|
|
|
57607
58833
|
function handleRead(args2, cwd) {
|
|
57608
58834
|
const raw = str2(args2.path);
|
|
57609
58835
|
if (!raw) throw new ToolError("`path` is required \u2014 relative to the project root, or an absolute path inside it.");
|
|
57610
|
-
const target = isAbsolute3(raw) ? raw :
|
|
58836
|
+
const target = isAbsolute3(raw) ? raw : join45(cwd, raw);
|
|
57611
58837
|
let real;
|
|
57612
58838
|
try {
|
|
57613
58839
|
real = realpathSync4(target);
|
|
@@ -57618,10 +58844,10 @@ function handleRead(args2, cwd) {
|
|
|
57618
58844
|
if (real !== root && !real.startsWith(root + sep5)) {
|
|
57619
58845
|
throw new ToolError(`path is outside the project: ${raw}. Use your own file tool for anything else.`);
|
|
57620
58846
|
}
|
|
57621
|
-
const st =
|
|
58847
|
+
const st = statSync12(real);
|
|
57622
58848
|
if (!st.isFile()) throw new ToolError(`not a file: ${raw}`);
|
|
57623
58849
|
if (st.size > MAX_READ_BYTES) throw new ToolError(`file is too large to read (${st.size} bytes): ${raw}`);
|
|
57624
|
-
const lines =
|
|
58850
|
+
const lines = readFileSync27(real, "utf8").split("\n");
|
|
57625
58851
|
const total = lines.length;
|
|
57626
58852
|
const start2 = Math.max(1, Math.floor(num2(args2.start_line) ?? 1));
|
|
57627
58853
|
if (start2 > total) throw new ToolError(`start_line ${start2} is past the end of the file (${total} lines).`);
|
|
@@ -57743,6 +58969,10 @@ var standardProp = {
|
|
|
57743
58969
|
description: "Which standard to report against. Default: wcag (WCAG 2.2 AA)."
|
|
57744
58970
|
};
|
|
57745
58971
|
var langProp = { type: "string", enum: ["en", "fr"], description: "Language for the rendered prose. Default: en." };
|
|
58972
|
+
var htmlProp = {
|
|
58973
|
+
type: "boolean",
|
|
58974
|
+
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."
|
|
58975
|
+
};
|
|
57746
58976
|
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.";
|
|
57747
58977
|
var TOOLS2 = [
|
|
57748
58978
|
{
|
|
@@ -57772,7 +59002,7 @@ var TOOLS2 = [
|
|
|
57772
59002
|
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.",
|
|
57773
59003
|
inputSchema: {
|
|
57774
59004
|
type: "object",
|
|
57775
|
-
properties: { cwd: cwdProp, globs: globsProp, standard: standardProp, lang: langProp },
|
|
59005
|
+
properties: { cwd: cwdProp, globs: globsProp, standard: standardProp, lang: langProp, html: htmlProp },
|
|
57776
59006
|
required: ["cwd"]
|
|
57777
59007
|
}
|
|
57778
59008
|
},
|
|
@@ -57904,7 +59134,8 @@ var TOOLS2 = [
|
|
|
57904
59134
|
type: "string",
|
|
57905
59135
|
enum: ["grid", "report"],
|
|
57906
59136
|
description: "`grid` (default) is the criterion \xD7 page matrix; `report` is the per-page dossiers."
|
|
57907
|
-
}
|
|
59137
|
+
},
|
|
59138
|
+
html: htmlProp
|
|
57908
59139
|
},
|
|
57909
59140
|
required: ["cwd"]
|
|
57910
59141
|
}
|
|
@@ -58162,25 +59393,25 @@ function str3(v) {
|
|
|
58162
59393
|
var DECLARED = new Set([...TOOLS2, ...WRITE_TOOLS].map((t2) => t2.name));
|
|
58163
59394
|
|
|
58164
59395
|
// src/mcp/resources.ts
|
|
58165
|
-
import { existsSync as
|
|
58166
|
-
import { basename as basename3, dirname as dirname13, join as
|
|
59396
|
+
import { existsSync as existsSync31, readdirSync as readdirSync6, readFileSync as readFileSync28, realpathSync as realpathSync5, statSync as statSync13 } from "fs";
|
|
59397
|
+
import { basename as basename3, dirname as dirname13, join as join46, resolve as resolve12, sep as sep6 } from "path";
|
|
58167
59398
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
58168
59399
|
var SKILL_NAME = "ultra11y";
|
|
58169
59400
|
var URI_SCHEME = "skill://";
|
|
58170
59401
|
function resolveSkillRoot(moduleDir) {
|
|
58171
59402
|
const here = moduleDir ?? dirname13(fileURLToPath4(import.meta.url));
|
|
58172
59403
|
const candidates2 = [resolve12(here, ".."), resolve12(here, "..", "skills", SKILL_NAME), resolve12(here, "..", "..", "skills", SKILL_NAME)];
|
|
58173
|
-
return candidates2.find((dir) =>
|
|
59404
|
+
return candidates2.find((dir) => existsSync31(join46(dir, "SKILL.md")));
|
|
58174
59405
|
}
|
|
58175
59406
|
function listResources(moduleDir) {
|
|
58176
59407
|
const root = resolveSkillRoot(moduleDir);
|
|
58177
59408
|
if (!root) return [];
|
|
58178
59409
|
const out2 = [describe(root, "SKILL.md", `${SKILL_NAME}: the skill`)];
|
|
58179
|
-
const refDir =
|
|
58180
|
-
if (!
|
|
59410
|
+
const refDir = join46(root, "references");
|
|
59411
|
+
if (!existsSync31(refDir)) return out2;
|
|
58181
59412
|
for (const file of readdirSync6(refDir).sort()) {
|
|
58182
59413
|
if (!file.endsWith(".md")) continue;
|
|
58183
|
-
out2.push(describe(root,
|
|
59414
|
+
out2.push(describe(root, join46("references", file), `${SKILL_NAME} reference: ${basename3(file, ".md")}`));
|
|
58184
59415
|
}
|
|
58185
59416
|
return out2;
|
|
58186
59417
|
}
|
|
@@ -58203,8 +59434,8 @@ function readResource(uri, moduleDir) {
|
|
|
58203
59434
|
if (targetReal !== rootReal && !targetReal.startsWith(rootReal + sep6)) {
|
|
58204
59435
|
throw new ResourceError(`resource path escapes the skill root: ${uri}`);
|
|
58205
59436
|
}
|
|
58206
|
-
if (!
|
|
58207
|
-
return { uri, mimeType: "text/markdown", text:
|
|
59437
|
+
if (!statSync13(targetReal).isFile()) throw new ResourceError(`not a file: ${uri}`);
|
|
59438
|
+
return { uri, mimeType: "text/markdown", text: readFileSync28(targetReal, "utf8") };
|
|
58208
59439
|
}
|
|
58209
59440
|
var ResourceError = class extends Error {
|
|
58210
59441
|
};
|
|
@@ -58215,14 +59446,14 @@ function describe(root, rel2, fallbackTitle) {
|
|
|
58215
59446
|
title: fallbackTitle,
|
|
58216
59447
|
mimeType: "text/markdown"
|
|
58217
59448
|
};
|
|
58218
|
-
const summary = firstProse(
|
|
59449
|
+
const summary = firstProse(join46(root, rel2));
|
|
58219
59450
|
if (summary) decl.description = summary;
|
|
58220
59451
|
return decl;
|
|
58221
59452
|
}
|
|
58222
59453
|
function firstProse(file) {
|
|
58223
59454
|
let text;
|
|
58224
59455
|
try {
|
|
58225
|
-
text =
|
|
59456
|
+
text = readFileSync28(file, "utf8");
|
|
58226
59457
|
} catch {
|
|
58227
59458
|
return void 0;
|
|
58228
59459
|
}
|
|
@@ -58387,7 +59618,7 @@ async function runStdioServer(opts = {}) {
|
|
|
58387
59618
|
let restore;
|
|
58388
59619
|
if (!opts.captureStdout && output === process.stdout) {
|
|
58389
59620
|
const original = process.stdout.write;
|
|
58390
|
-
process.stdout.write = ((
|
|
59621
|
+
process.stdout.write = ((chunk2, ...rest) => process.stderr.write(chunk2, ...rest));
|
|
58391
59622
|
restore = () => {
|
|
58392
59623
|
process.stdout.write = original;
|
|
58393
59624
|
};
|
|
@@ -58635,6 +59866,8 @@ Usage:
|
|
|
58635
59866
|
ultra11y audit [--format sarif|github] (CI: SARIF for code scanning, or inline annotations + job summary)
|
|
58636
59867
|
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)
|
|
58637
59868
|
ultra11y report --in <audit.json> [--out <dir>] [--standard <pack>] [--format sarif|github] [--lang auto|en|fr]
|
|
59869
|
+
ultra11y report --in <audit.json> [--evidence [--evidence-max <n>]] [--out <dir>] (the Markdown report, illustrated with annotated crops)
|
|
59870
|
+
ultra11y report --in <audit.json> --html [--evidence] [--inline-budget <bytes>] [--out <dir>] (index.html + a printable single file)
|
|
58638
59871
|
ultra11y prd --in <audit.json> [--out <dir>] [--split criterion] [--format audit|doc|remediation] [--no-technical] [--standard <pack>] [--lang auto|en|fr]
|
|
58639
59872
|
ultra11y tickets --in <audit.json> [--provider auto|github|gitlab|jira] [--grain criterion|page|page-criterion|single|file] [--transport auto|cli|rest]
|
|
58640
59873
|
ultra11y tickets [--out <dir>] [--max-tickets <n>] [--dry-run] [--json] [--standard <pack>] [--format audit|remediation] [--lang auto|en|fr]
|
|
@@ -58659,6 +59892,7 @@ Usage:
|
|
|
58659
59892
|
ultra11y snapshot list [--root <dir>] [--json]
|
|
58660
59893
|
ultra11y pages --in <audit.json> [--standard <pack>] [--json] [--lang auto|en|fr] (the per-page criterion grid)
|
|
58661
59894
|
ultra11y pages --in <audit.json> --format report [--split page] [--out <dir>] (the per-page report, with screenshots)
|
|
59895
|
+
ultra11y pages --in <audit.json> --format report --out <dir> [--evidence [--evidence-max <n>]] [--html] (annotated crops of each non-conformity, and the HTML site)
|
|
58662
59896
|
ultra11y pages discover --sitemap <url> | --crawl <url> | --from-snapshots [--depth <n>] [--max <n>] [--write] [--json] (build the page sample)
|
|
58663
59897
|
ultra11y pages --in <audit.json> --standard <pack> --diff <external.json> (hold the grid against an audit someone else performed)
|
|
58664
59898
|
ultra11y import --from file <report.json> | --from ara <id> [--source <adapter>] [--out <dir>] [--json]
|
|
@@ -59055,6 +60289,10 @@ var VALUE_FLAGS2 = /* @__PURE__ */ new Set([
|
|
|
59055
60289
|
"model",
|
|
59056
60290
|
"pack",
|
|
59057
60291
|
"format",
|
|
60292
|
+
// Bytes of image data the single-file HTML report may carry inline (src/html-emit.ts).
|
|
60293
|
+
"inline-budget",
|
|
60294
|
+
// Hard cap on the crops one run writes (src/evidence.ts `DEFAULT_CAPS.total`).
|
|
60295
|
+
"evidence-max",
|
|
59058
60296
|
"guidance",
|
|
59059
60297
|
"runtime",
|
|
59060
60298
|
"cwd",
|
|
@@ -59089,6 +60327,10 @@ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
59089
60327
|
"cross-file",
|
|
59090
60328
|
"json",
|
|
59091
60329
|
"quiet",
|
|
60330
|
+
// The visual tier. Both must be declared here or `parseArgs` swallows the value that
|
|
60331
|
+
// follows and the run prints "unknown flag (ignored)" for a flag that in fact works.
|
|
60332
|
+
"html",
|
|
60333
|
+
"evidence",
|
|
59092
60334
|
"no-default-excludes",
|
|
59093
60335
|
"no-captures",
|
|
59094
60336
|
"require-captures",
|
|
@@ -59238,7 +60480,16 @@ function emitCiFormat(result, format, standard, lang, failOn) {
|
|
|
59238
60480
|
console.error(md);
|
|
59239
60481
|
}
|
|
59240
60482
|
if (process.env.ULTRA11Y_PR_COMMENT === "1") {
|
|
59241
|
-
const
|
|
60483
|
+
const digest = prComment(result, {
|
|
60484
|
+
standard,
|
|
60485
|
+
lang,
|
|
60486
|
+
// The run is known before the artifact exists, so the link is always safe. The artifact
|
|
60487
|
+
// NAME is only set by the action when it actually uploads one — naming an artifact that
|
|
60488
|
+
// was never uploaded sends the reader to a page that does not exist.
|
|
60489
|
+
...process.env.ULTRA11Y_RUN_URL ? { runUrl: process.env.ULTRA11Y_RUN_URL } : {},
|
|
60490
|
+
...process.env.ULTRA11Y_ARTIFACT_NAME ? { artifactName: process.env.ULTRA11Y_ARTIFACT_NAME } : {}
|
|
60491
|
+
});
|
|
60492
|
+
const c2 = pushPrComment(digest, standard);
|
|
59242
60493
|
console.error(
|
|
59243
60494
|
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}` : ""}.`
|
|
59244
60495
|
);
|
|
@@ -59324,9 +60575,9 @@ async function cmdAudit(p) {
|
|
|
59324
60575
|
const capturesFlag = typeof p.flags.captures === "string" && p.flags.captures ? p.flags.captures : void 0;
|
|
59325
60576
|
const capturesDir = capturesFlag ?? CAPTURES_DIR;
|
|
59326
60577
|
const scopedToDiff = p.flags.changed === true || p.flags.staged === true || since !== void 0;
|
|
59327
|
-
const capturesWanted = p.flags["no-captures"] !== true && !inputs.includes("-") && (capturesFlag !== void 0 ||
|
|
60578
|
+
const capturesWanted = p.flags["no-captures"] !== true && !inputs.includes("-") && (capturesFlag !== void 0 || existsSync32(capturesDir));
|
|
59328
60579
|
const useCaptures = capturesWanted && !scopedToDiff && !inputs.includes(capturesDir);
|
|
59329
|
-
const pagesWanted = p.flags["no-captures"] !== true && !inputs.includes("-") &&
|
|
60580
|
+
const pagesWanted = p.flags["no-captures"] !== true && !inputs.includes("-") && existsSync32(PAGES_DIR);
|
|
59330
60581
|
const usePages = pagesWanted && !scopedToDiff && !inputs.includes(PAGES_DIR);
|
|
59331
60582
|
const pagesInScope = pagesWanted && !scopedToDiff;
|
|
59332
60583
|
const auditInputs = [...inputs, ...useCaptures ? [capturesDir] : [], ...usePages ? [PAGES_DIR] : []];
|
|
@@ -59368,10 +60619,10 @@ async function cmdAudit(p) {
|
|
|
59368
60619
|
if (typeof p.flags.out === "string") {
|
|
59369
60620
|
const out2 = p.flags.out;
|
|
59370
60621
|
const asFile = out2.toLowerCase().endsWith(".json");
|
|
59371
|
-
const target = asFile ? out2 :
|
|
60622
|
+
const target = asFile ? out2 : join47(out2, "audit-latest.json");
|
|
59372
60623
|
try {
|
|
59373
|
-
|
|
59374
|
-
|
|
60624
|
+
mkdirSync17(asFile ? dirname14(out2) : out2, { recursive: true });
|
|
60625
|
+
writeFileSync19(target, JSON.stringify(result, null, 2) + "\n");
|
|
59375
60626
|
console.error(lang === "fr" ? `\u2192 audit \xE9crit dans ${target}` : `\u2192 audit written to ${target}`);
|
|
59376
60627
|
} catch {
|
|
59377
60628
|
}
|
|
@@ -59390,7 +60641,7 @@ async function cmdAudit(p) {
|
|
|
59390
60641
|
const baselineFlag = p.flags.baseline;
|
|
59391
60642
|
if (typeof baselineFlag === "string" && baselineFlag) {
|
|
59392
60643
|
let baseline = null;
|
|
59393
|
-
if (
|
|
60644
|
+
if (existsSync32(baselineFlag)) {
|
|
59394
60645
|
try {
|
|
59395
60646
|
const parsed = JSON.parse(readText(baselineFlag));
|
|
59396
60647
|
if (isCurrentAudit(parsed)) baseline = parsed;
|
|
@@ -59444,11 +60695,11 @@ async function cmdDev(p) {
|
|
|
59444
60695
|
return 2;
|
|
59445
60696
|
}
|
|
59446
60697
|
if (p.flags.next === true) {
|
|
59447
|
-
const dir =
|
|
60698
|
+
const dir = join47(root, ".ultra11y", "next");
|
|
59448
60699
|
const rel2 = ".ultra11y/next/overlay.jsx";
|
|
59449
60700
|
try {
|
|
59450
|
-
|
|
59451
|
-
|
|
60701
|
+
mkdirSync17(dir, { recursive: true });
|
|
60702
|
+
writeFileSync19(join47(dir, "overlay.jsx"), nextOverlayComponent(port));
|
|
59452
60703
|
} catch (e) {
|
|
59453
60704
|
console.error(`ultra11y dev: could not write ${rel2}: ${e instanceof Error ? e.message : String(e)}`);
|
|
59454
60705
|
return 1;
|
|
@@ -59497,7 +60748,7 @@ async function cmdPagesDiscover(p) {
|
|
|
59497
60748
|
const snaps = sampleFromSnapshots(readSnapshots(root));
|
|
59498
60749
|
if (!snaps.length) {
|
|
59499
60750
|
console.error(
|
|
59500
|
-
lang === "fr" ? `ultra11y pages discover : aucun instantan\xE9 sous ${
|
|
60751
|
+
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\`.`
|
|
59501
60752
|
);
|
|
59502
60753
|
return 1;
|
|
59503
60754
|
}
|
|
@@ -59577,9 +60828,9 @@ ${found} page(s) discovered, ${merged.added.length} new. Nothing was written \u2
|
|
|
59577
60828
|
}
|
|
59578
60829
|
return 0;
|
|
59579
60830
|
}
|
|
59580
|
-
const file =
|
|
60831
|
+
const file = join47(process.cwd(), ".ultra11yrc.json");
|
|
59581
60832
|
let doc = {};
|
|
59582
|
-
if (
|
|
60833
|
+
if (existsSync32(file)) {
|
|
59583
60834
|
try {
|
|
59584
60835
|
doc = JSON.parse(readText(file));
|
|
59585
60836
|
} catch {
|
|
@@ -59588,7 +60839,7 @@ ${found} page(s) discovered, ${merged.added.length} new. Nothing was written \u2
|
|
|
59588
60839
|
}
|
|
59589
60840
|
}
|
|
59590
60841
|
doc.sample = v.sample;
|
|
59591
|
-
|
|
60842
|
+
writeFileSync19(file, `${JSON.stringify(doc, null, 2)}
|
|
59592
60843
|
`);
|
|
59593
60844
|
if (!p.flags.json)
|
|
59594
60845
|
console.log(
|
|
@@ -59648,50 +60899,121 @@ async function cmdPages(p) {
|
|
|
59648
60899
|
const derived = derivePages(result, scope);
|
|
59649
60900
|
const split = p.flags.split === "page";
|
|
59650
60901
|
const outDir = typeof p.flags.out === "string" && p.flags.out ? p.flags.out : void 0;
|
|
59651
|
-
const shotOf = (id) =>
|
|
60902
|
+
const shotOf = (id) => join47(PAGES_DIR, id, "screen.png");
|
|
59652
60903
|
const shotsRelative = (fileDir) => {
|
|
59653
60904
|
const m = /* @__PURE__ */ new Map();
|
|
59654
60905
|
for (const pg of derived) {
|
|
59655
|
-
if (
|
|
60906
|
+
if (existsSync32(shotOf(pg.id))) m.set(pg.id, relative5(fileDir, shotOf(pg.id)).split("\\").join("/"));
|
|
59656
60907
|
}
|
|
59657
60908
|
return m;
|
|
59658
60909
|
};
|
|
60910
|
+
const shotPaths = (pages) => {
|
|
60911
|
+
const m = /* @__PURE__ */ new Map();
|
|
60912
|
+
for (const pg of pages) if (existsSync32(shotOf(pg.id))) m.set(pg.id, shotOf(pg.id));
|
|
60913
|
+
return m;
|
|
60914
|
+
};
|
|
59659
60915
|
const shotsCopiedInto = (dir) => {
|
|
59660
60916
|
const m = /* @__PURE__ */ new Map();
|
|
59661
|
-
const assets =
|
|
60917
|
+
const assets = join47(dir, "assets");
|
|
59662
60918
|
for (const pg of derived) {
|
|
59663
60919
|
const src = shotOf(pg.id);
|
|
59664
|
-
if (!
|
|
59665
|
-
|
|
59666
|
-
copyFileSync4(src,
|
|
60920
|
+
if (!existsSync32(src)) continue;
|
|
60921
|
+
mkdirSync17(assets, { recursive: true });
|
|
60922
|
+
copyFileSync4(src, join47(assets, `${pg.id}.png`));
|
|
59667
60923
|
m.set(pg.id, `./assets/${pg.id}.png`);
|
|
59668
60924
|
}
|
|
59669
60925
|
return m;
|
|
59670
60926
|
};
|
|
60927
|
+
const wantEvidence = p.flags.evidence === true;
|
|
60928
|
+
const wantHtml = p.flags.html === true;
|
|
59671
60929
|
if (!outDir) {
|
|
60930
|
+
if (wantEvidence || wantHtml) {
|
|
60931
|
+
console.error(
|
|
60932
|
+
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."
|
|
60933
|
+
);
|
|
60934
|
+
return 2;
|
|
60935
|
+
}
|
|
59672
60936
|
console.log(renderPagesDocument(result, derived, { standard, lang, screenshots: shotsRelative(".") }));
|
|
59673
60937
|
return 0;
|
|
59674
60938
|
}
|
|
59675
|
-
|
|
60939
|
+
mkdirSync17(outDir, { recursive: true });
|
|
60940
|
+
const manifest = wantEvidence ? writeEvidence(result, { outDir, ...evidenceCapsOf(p) }) : void 0;
|
|
60941
|
+
const cropFor = manifest ? (f) => {
|
|
60942
|
+
const c2 = manifest.crops.get(findingId(f));
|
|
60943
|
+
return c2 ? { href: c2.href, alt: c2.alt[lang] } : void 0;
|
|
60944
|
+
} : void 0;
|
|
60945
|
+
if (manifest) {
|
|
60946
|
+
const total = evidenceNotice(manifest, null, lang);
|
|
60947
|
+
console.error(
|
|
60948
|
+
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")}.`
|
|
60949
|
+
);
|
|
60950
|
+
for (const line of total) console.error(line);
|
|
60951
|
+
}
|
|
60952
|
+
const evidenceOpts = {
|
|
60953
|
+
...cropFor ? { cropFor } : {},
|
|
60954
|
+
...manifest ? { evidenceNotice: (id) => evidenceNotice(manifest, id, lang) } : {}
|
|
60955
|
+
};
|
|
59676
60956
|
if (!split) {
|
|
59677
|
-
const file =
|
|
59678
|
-
|
|
60957
|
+
const file = join47(outDir, `pages-${result.date}.md`);
|
|
60958
|
+
writeFileSync19(file, `${renderPagesDocument(result, derived, { standard, lang, screenshots: shotsCopiedInto(outDir), ...evidenceOpts })}
|
|
59679
60959
|
`);
|
|
60960
|
+
const html2 = wantHtml ? emitHtml(result, {
|
|
60961
|
+
outDir,
|
|
60962
|
+
standard,
|
|
60963
|
+
lang,
|
|
60964
|
+
layout: "pages",
|
|
60965
|
+
pages: true,
|
|
60966
|
+
screenshots: shotPaths(derived),
|
|
60967
|
+
...manifest ? { evidence: manifest } : {},
|
|
60968
|
+
inlineBudget: budgetOf(p)
|
|
60969
|
+
}) : void 0;
|
|
59680
60970
|
console.log(file);
|
|
59681
|
-
return 0;
|
|
60971
|
+
return html2?.imagesDropped ? 1 : 0;
|
|
59682
60972
|
}
|
|
59683
60973
|
const shots = shotsCopiedInto(outDir);
|
|
59684
60974
|
const sheet = (id) => `page-${id}.md`;
|
|
59685
60975
|
const hrefs = new Map(derived.map((pg) => [pg.id, `./${sheet(pg.id)}`]));
|
|
59686
60976
|
for (const pg of derived) {
|
|
59687
|
-
|
|
60977
|
+
writeFileSync19(join47(outDir, sheet(pg.id)), `${renderPageDocument(result, pg, { standard, lang, screenshots: shots, ...evidenceOpts })}
|
|
59688
60978
|
`);
|
|
59689
60979
|
}
|
|
59690
|
-
const index =
|
|
59691
|
-
|
|
60980
|
+
const index = join47(outDir, "index.md");
|
|
60981
|
+
writeFileSync19(index, `${renderPagesIndex(result, derived, { standard, lang, hrefs })}
|
|
59692
60982
|
`);
|
|
60983
|
+
const html = wantHtml ? emitHtml(result, {
|
|
60984
|
+
outDir,
|
|
60985
|
+
standard,
|
|
60986
|
+
lang,
|
|
60987
|
+
layout: "pages",
|
|
60988
|
+
pages: true,
|
|
60989
|
+
screenshots: shotPaths(derived),
|
|
60990
|
+
...manifest ? { evidence: manifest } : {},
|
|
60991
|
+
inlineBudget: budgetOf(p)
|
|
60992
|
+
}) : void 0;
|
|
59693
60993
|
console.log(index);
|
|
59694
|
-
return 0;
|
|
60994
|
+
return html?.imagesDropped ? 1 : 0;
|
|
60995
|
+
}
|
|
60996
|
+
function positiveIntFlag(p, name2) {
|
|
60997
|
+
const raw = p.flags[name2];
|
|
60998
|
+
if (typeof raw !== "string" || !raw) return void 0;
|
|
60999
|
+
const n = Number.parseInt(raw, 10);
|
|
61000
|
+
if (Number.isFinite(n) && n > 0) return n;
|
|
61001
|
+
console.error(`ultra11y: --${name2} expects a positive integer (got "${raw}"). Ignored \u2014 the default applies.`);
|
|
61002
|
+
return void 0;
|
|
61003
|
+
}
|
|
61004
|
+
function budgetOf(p) {
|
|
61005
|
+
return positiveIntFlag(p, "inline-budget");
|
|
61006
|
+
}
|
|
61007
|
+
function evidenceCapsOf(p) {
|
|
61008
|
+
const total = positiveIntFlag(p, "evidence-max");
|
|
61009
|
+
return total === void 0 ? {} : { caps: { total } };
|
|
61010
|
+
}
|
|
61011
|
+
function emitHtml(result, opts) {
|
|
61012
|
+
const res = writeHtml(result, opts);
|
|
61013
|
+
for (const n of res.notices) console.error(`ultra11y: ${n}`);
|
|
61014
|
+
console.error(`ultra11y: ${res.index}`);
|
|
61015
|
+
if (res.composite) console.error(`ultra11y: ${res.composite}`);
|
|
61016
|
+
return res;
|
|
59695
61017
|
}
|
|
59696
61018
|
async function cmdSnapshot(p) {
|
|
59697
61019
|
const sub = p.positionals[0];
|
|
@@ -59707,7 +61029,7 @@ async function cmdSnapshot(p) {
|
|
|
59707
61029
|
2
|
|
59708
61030
|
)
|
|
59709
61031
|
);
|
|
59710
|
-
else if (!snaps.length) console.log(lang === "fr" ? `Aucun instantan\xE9 dans ${
|
|
61032
|
+
else if (!snaps.length) console.log(lang === "fr" ? `Aucun instantan\xE9 dans ${join47(root, PAGES_DIR)}.` : `No snapshot in ${join47(root, PAGES_DIR)}.`);
|
|
59711
61033
|
else for (const s of snaps) console.log(`${s.meta.id} ${s.meta.name} ${s.meta.url}${s.meta.auth ? " [auth]" : ""}`);
|
|
59712
61034
|
return 0;
|
|
59713
61035
|
}
|
|
@@ -59754,7 +61076,7 @@ async function cmdSnapshot(p) {
|
|
|
59754
61076
|
console.error(`ultra11y snapshot write: could not write the snapshot: ${e instanceof Error ? e.message : String(e)}`);
|
|
59755
61077
|
return 1;
|
|
59756
61078
|
}
|
|
59757
|
-
const result = runAudit({ inputs: [
|
|
61079
|
+
const result = runAudit({ inputs: [join47(dir, "dom.html")], onWarn: (m) => console.error(m) });
|
|
59758
61080
|
const failOnRaw = p.flags["fail-on"];
|
|
59759
61081
|
const failOnParsed = parseFailOn(failOnRaw);
|
|
59760
61082
|
if (failOnRaw !== void 0 && failOnParsed === null) {
|
|
@@ -59786,9 +61108,9 @@ function cmdInit(p) {
|
|
|
59786
61108
|
if (want.baseline) {
|
|
59787
61109
|
const inputs = p.positionals.length ? p.positionals : ["."];
|
|
59788
61110
|
const result = runAudit({ inputs, onWarn: (m) => console.error(m) });
|
|
59789
|
-
|
|
59790
|
-
const bp =
|
|
59791
|
-
|
|
61111
|
+
mkdirSync17(join47(root, "audits"), { recursive: true });
|
|
61112
|
+
const bp = join47(root, "audits", "baseline.json");
|
|
61113
|
+
writeFileSync19(bp, JSON.stringify(result, null, 2) + "\n");
|
|
59792
61114
|
wrote.push(bp);
|
|
59793
61115
|
}
|
|
59794
61116
|
if (want.hook) wrote.push(writeHook(root, engineRel, failOn, legacy ? "baseline" : "staged"));
|
|
@@ -59913,11 +61235,29 @@ async function cmdReport(p) {
|
|
|
59913
61235
|
console.error(`ultra11y report: --format must be sarif|github (got "${String(p.flags.format)}").`);
|
|
59914
61236
|
return 2;
|
|
59915
61237
|
}
|
|
61238
|
+
if (ciFormat && p.flags.html === true) {
|
|
61239
|
+
console.error(
|
|
61240
|
+
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.`
|
|
61241
|
+
);
|
|
61242
|
+
return 2;
|
|
61243
|
+
}
|
|
59916
61244
|
if (ciFormat) {
|
|
59917
61245
|
emitCiFormat(result, ciFormat, standard, lang);
|
|
59918
61246
|
return 0;
|
|
59919
61247
|
}
|
|
59920
|
-
const
|
|
61248
|
+
const manifest = p.flags.evidence === true ? writeEvidence(result, { outDir: out2, ...evidenceCapsOf(p) }) : void 0;
|
|
61249
|
+
if (manifest) {
|
|
61250
|
+
for (const line of evidenceNotice(manifest, null, lang)) console.error(line);
|
|
61251
|
+
}
|
|
61252
|
+
const cropFor = manifest ? (f) => {
|
|
61253
|
+
const c2 = manifest.crops.get(findingId(f));
|
|
61254
|
+
return c2 ? { href: c2.href, alt: c2.alt[lang] } : void 0;
|
|
61255
|
+
} : void 0;
|
|
61256
|
+
const path = writeReport(result, { out: out2, lang, standard, ...cropFor ? { cropFor } : {} });
|
|
61257
|
+
let html;
|
|
61258
|
+
if (p.flags.html === true) {
|
|
61259
|
+
html = emitHtml(result, { outDir: out2, standard, lang, ...manifest ? { evidence: manifest } : {}, inlineBudget: budgetOf(p) });
|
|
61260
|
+
}
|
|
59921
61261
|
const untested = isCore(standard) ? [] : untestedNeedsRendering(result);
|
|
59922
61262
|
const partial = untested.length > 0;
|
|
59923
61263
|
if (partial && !p.flags.json) console.error(`\u{1F6A8} ${partialAuditBanner(lang, untested)}`);
|
|
@@ -59929,6 +61269,7 @@ async function cmdReport(p) {
|
|
|
59929
61269
|
conformancePct: result.conformancePct,
|
|
59930
61270
|
date: result.date,
|
|
59931
61271
|
standard: typeof p.flags.standard === "string" ? p.flags.standard : "wcag",
|
|
61272
|
+
...html ? { htmlPath: html.index, htmlSinglePath: html.composite } : {},
|
|
59932
61273
|
...partial ? { partialAudit: true, untestedCriteria: untested } : {}
|
|
59933
61274
|
},
|
|
59934
61275
|
null,
|
|
@@ -59936,7 +61277,7 @@ async function cmdReport(p) {
|
|
|
59936
61277
|
)
|
|
59937
61278
|
);
|
|
59938
61279
|
else console.log(path);
|
|
59939
|
-
return 0;
|
|
61280
|
+
return html?.imagesDropped ? 1 : 0;
|
|
59940
61281
|
}
|
|
59941
61282
|
async function cmdPrd(p) {
|
|
59942
61283
|
const inFlag = p.flags.in;
|
|
@@ -60068,8 +61409,8 @@ async function cmdTickets(p) {
|
|
|
60068
61409
|
const ticketsOut = typeof p.flags.out === "string" && p.flags.out ? p.flags.out : void 0;
|
|
60069
61410
|
let setPath;
|
|
60070
61411
|
if (ticketsOut) {
|
|
60071
|
-
|
|
60072
|
-
setPath =
|
|
61412
|
+
mkdirSync17(ticketsOut, { recursive: true });
|
|
61413
|
+
setPath = join47(ticketsOut, `issues-${result.date}.json`);
|
|
60073
61414
|
const payload = {
|
|
60074
61415
|
tool: "ultra11y",
|
|
60075
61416
|
kind: "issues",
|
|
@@ -60080,7 +61421,7 @@ async function cmdTickets(p) {
|
|
|
60080
61421
|
count: plan.tickets.length,
|
|
60081
61422
|
issues: plan.tickets
|
|
60082
61423
|
};
|
|
60083
|
-
|
|
61424
|
+
writeFileSync19(setPath, `${JSON.stringify(payload, null, 2)}
|
|
60084
61425
|
`);
|
|
60085
61426
|
if (!json) console.log(setPath);
|
|
60086
61427
|
}
|
|
@@ -60170,8 +61511,8 @@ async function cmdTickets(p) {
|
|
|
60170
61511
|
return pushed.failed > 0 ? 1 : 0;
|
|
60171
61512
|
}
|
|
60172
61513
|
function depsAt(root) {
|
|
60173
|
-
const pkgPath =
|
|
60174
|
-
if (!
|
|
61514
|
+
const pkgPath = join47(root, "package.json");
|
|
61515
|
+
if (!existsSync32(pkgPath)) return {};
|
|
60175
61516
|
try {
|
|
60176
61517
|
const pkg = JSON.parse(readText(pkgPath));
|
|
60177
61518
|
return { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
@@ -60197,24 +61538,24 @@ function cmdRender(p) {
|
|
|
60197
61538
|
console.error(`ultra11y render: --runner must be playwright|cypress|auto (got "${forced}").`);
|
|
60198
61539
|
return 2;
|
|
60199
61540
|
}
|
|
60200
|
-
const detected = detectE2eRunner(depsAt(root), (f) =>
|
|
61541
|
+
const detected = detectE2eRunner(depsAt(root), (f) => existsSync32(join47(root, f)));
|
|
60201
61542
|
const runners = forced && forced !== "auto" ? [forced] : detected;
|
|
60202
61543
|
if (!runners.length) {
|
|
60203
61544
|
console.error(e2eSetupPlan([], {}, lang));
|
|
60204
61545
|
return 1;
|
|
60205
61546
|
}
|
|
60206
61547
|
const engineRef = engineRefFor(root);
|
|
60207
|
-
const dir =
|
|
61548
|
+
const dir = join47(root, ".ultra11y", "e2e");
|
|
60208
61549
|
const paths = {};
|
|
60209
61550
|
try {
|
|
60210
|
-
|
|
61551
|
+
mkdirSync17(dir, { recursive: true });
|
|
60211
61552
|
if (runners.includes("playwright")) {
|
|
60212
|
-
|
|
61553
|
+
writeFileSync19(join47(dir, "playwright.mjs"), playwrightFixture(engineRef));
|
|
60213
61554
|
paths.playwright = ".ultra11y/e2e/playwright.mjs";
|
|
60214
61555
|
}
|
|
60215
61556
|
if (runners.includes("cypress")) {
|
|
60216
|
-
|
|
60217
|
-
|
|
61557
|
+
writeFileSync19(join47(dir, "cypress-plugin.mjs"), cypressPlugin(engineRef));
|
|
61558
|
+
writeFileSync19(join47(dir, "cypress-commands.mjs"), cypressCommands());
|
|
60218
61559
|
paths.cypressPlugin = ".ultra11y/e2e/cypress-plugin.mjs";
|
|
60219
61560
|
paths.cypressCommands = ".ultra11y/e2e/cypress-commands.mjs";
|
|
60220
61561
|
}
|
|
@@ -60229,7 +61570,7 @@ function cmdRender(p) {
|
|
|
60229
61570
|
if (p.flags.scaffold === true) {
|
|
60230
61571
|
const out2 = typeof p.flags.out === "string" && p.flags.out ? p.flags.out : "ultra11y-render.tsx";
|
|
60231
61572
|
try {
|
|
60232
|
-
|
|
61573
|
+
writeFileSync19(out2, ssrHarness());
|
|
60233
61574
|
} catch (e) {
|
|
60234
61575
|
console.error(`ultra11y render: could not write ${out2}: ${e instanceof Error ? e.message : String(e)}`);
|
|
60235
61576
|
return 1;
|
|
@@ -60243,29 +61584,29 @@ Fill in COMPONENTS, run it (e.g. npx tsx ${out2}), then: node scripts/ultra11y.m
|
|
|
60243
61584
|
}
|
|
60244
61585
|
if (p.flags.setup === true) {
|
|
60245
61586
|
const rel2 = ".ultra11y/capture-setup.mjs";
|
|
60246
|
-
const out2 =
|
|
61587
|
+
const out2 = join47(root, rel2);
|
|
60247
61588
|
try {
|
|
60248
|
-
|
|
60249
|
-
|
|
61589
|
+
mkdirSync17(dirname14(out2), { recursive: true });
|
|
61590
|
+
writeFileSync19(out2, captureSetup());
|
|
60250
61591
|
} catch (e) {
|
|
60251
61592
|
console.error(`ultra11y render: could not write ${out2}: ${e instanceof Error ? e.message : String(e)}`);
|
|
60252
61593
|
return 1;
|
|
60253
61594
|
}
|
|
60254
61595
|
let setupDeps = {};
|
|
60255
|
-
const setupPkg =
|
|
60256
|
-
if (
|
|
61596
|
+
const setupPkg = join47(root, "package.json");
|
|
61597
|
+
if (existsSync32(setupPkg)) {
|
|
60257
61598
|
try {
|
|
60258
61599
|
const pkg = JSON.parse(readText(setupPkg));
|
|
60259
61600
|
setupDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
60260
61601
|
} catch {
|
|
60261
61602
|
}
|
|
60262
61603
|
}
|
|
60263
|
-
const tr = detectTestRunner(setupDeps, (f) =>
|
|
61604
|
+
const tr = detectTestRunner(setupDeps, (f) => existsSync32(join47(root, f)));
|
|
60264
61605
|
console.log(captureSetupPlan(tr, rel2, lang));
|
|
60265
61606
|
const gaLine = ".ultra11y/captures/*.html text eol=lf linguist-generated=true";
|
|
60266
|
-
const gaPath =
|
|
61607
|
+
const gaPath = join47(root, ".gitattributes");
|
|
60267
61608
|
try {
|
|
60268
|
-
const existing =
|
|
61609
|
+
const existing = existsSync32(gaPath) ? readFileSync29(gaPath, "utf8") : "";
|
|
60269
61610
|
if (!existing.includes(".ultra11y/captures/")) {
|
|
60270
61611
|
appendFileSync(gaPath, (existing && !existing.endsWith("\n") ? "\n" : "") + gaLine + "\n");
|
|
60271
61612
|
console.log(lang === "fr" ? `.gitattributes : ajout\xE9 \xAB ${gaLine} \xBB` : `.gitattributes: added "${gaLine}"`);
|
|
@@ -60273,8 +61614,8 @@ Fill in COMPONENTS, run it (e.g. npx tsx ${out2}), then: node scripts/ultra11y.m
|
|
|
60273
61614
|
} catch {
|
|
60274
61615
|
}
|
|
60275
61616
|
try {
|
|
60276
|
-
const giPath =
|
|
60277
|
-
if (
|
|
61617
|
+
const giPath = join47(root, ".gitignore");
|
|
61618
|
+
if (existsSync32(giPath) && /^\s*\/?\.ultra11y(\/\**)?\/?\s*$/m.test(readFileSync29(giPath, "utf8")))
|
|
60278
61619
|
console.error(
|
|
60279
61620
|
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/").'
|
|
60280
61621
|
);
|
|
@@ -60284,8 +61625,8 @@ Fill in COMPONENTS, run it (e.g. npx tsx ${out2}), then: node scripts/ultra11y.m
|
|
|
60284
61625
|
}
|
|
60285
61626
|
if (p.flags.storybook === true || typeof p.flags.storybook === "string") {
|
|
60286
61627
|
const sbDir = p.positionals[0] ?? "storybook-static";
|
|
60287
|
-
const indexPath =
|
|
60288
|
-
if (!
|
|
61628
|
+
const indexPath = existsSync32(join47(sbDir, "index.json")) ? join47(sbDir, "index.json") : join47(sbDir, "stories.json");
|
|
61629
|
+
if (!existsSync32(indexPath)) {
|
|
60289
61630
|
console.error(
|
|
60290
61631
|
lang === "fr" ? `ultra11y render : aucun index Storybook (index.json/stories.json) dans ${sbDir}.` : `ultra11y render: no Storybook index (index.json/stories.json) in ${sbDir}.`
|
|
60291
61632
|
);
|
|
@@ -60295,7 +61636,7 @@ Fill in COMPONENTS, run it (e.g. npx tsx ${out2}), then: node scripts/ultra11y.m
|
|
|
60295
61636
|
const provById = new Map(stories.map((s) => [s.id, storyProvenance(s)]));
|
|
60296
61637
|
const capturesFlag = typeof p.flags.captures === "string" && p.flags.captures ? p.flags.captures : void 0;
|
|
60297
61638
|
const htmlDir = capturesFlag ?? sbDir;
|
|
60298
|
-
const htmlFiles =
|
|
61639
|
+
const htmlFiles = existsSync32(htmlDir) ? discover([htmlDir]).files.filter((f) => /\.html?$/i.test(f)) : [];
|
|
60299
61640
|
const outDir = ".ultra11y/captures";
|
|
60300
61641
|
let attributed = 0;
|
|
60301
61642
|
let skipped = 0;
|
|
@@ -60317,8 +61658,8 @@ Fill in COMPONENTS, run it (e.g. npx tsx ${out2}), then: node scripts/ultra11y.m
|
|
|
60317
61658
|
continue;
|
|
60318
61659
|
}
|
|
60319
61660
|
try {
|
|
60320
|
-
|
|
60321
|
-
|
|
61661
|
+
mkdirSync17(outDir, { recursive: true });
|
|
61662
|
+
writeFileSync19(join47(outDir, `${hitId}.html`), `${formatCaptureComment(prov)}
|
|
60322
61663
|
${raw}${raw.endsWith("\n") ? "" : "\n"}`);
|
|
60323
61664
|
attributed++;
|
|
60324
61665
|
} catch {
|
|
@@ -60340,11 +61681,11 @@ ${raw}${raw.endsWith("\n") ? "" : "\n"}`);
|
|
|
60340
61681
|
}
|
|
60341
61682
|
if (p.flags.coverage === true) {
|
|
60342
61683
|
const capturesFlag = typeof p.flags.captures === "string" && p.flags.captures ? p.flags.captures : void 0;
|
|
60343
|
-
const capturesDir = capturesFlag ??
|
|
61684
|
+
const capturesDir = capturesFlag ?? join47(root, ".ultra11y/captures");
|
|
60344
61685
|
const graphExt = [...GRAPH_ONLY_EXT, ...asList(p.flags.ext) ?? []];
|
|
60345
61686
|
const sourceFiles = discover([root], { include: asList(p.flags.include), exclude: asList(p.flags.exclude), ext: graphExt }).files;
|
|
60346
61687
|
const graph = buildGraphStreaming(sourceFiles);
|
|
60347
|
-
const capFiles =
|
|
61688
|
+
const capFiles = existsSync32(capturesDir) ? discover([capturesDir]).files : [];
|
|
60348
61689
|
const entries = capFiles.map((f) => ({ file: toPosix(f), provenance: parseCaptureProvenance(readText(f)) }));
|
|
60349
61690
|
const cov = computeCaptureCoverage(graph, entries);
|
|
60350
61691
|
if (p.flags.json) console.log(JSON.stringify(cov, null, 2));
|
|
@@ -60352,15 +61693,15 @@ ${raw}${raw.endsWith("\n") ? "" : "\n"}`);
|
|
|
60352
61693
|
return 0;
|
|
60353
61694
|
}
|
|
60354
61695
|
let deps = {};
|
|
60355
|
-
const pkgPath =
|
|
60356
|
-
if (
|
|
61696
|
+
const pkgPath = join47(root, "package.json");
|
|
61697
|
+
if (existsSync32(pkgPath)) {
|
|
60357
61698
|
try {
|
|
60358
61699
|
const pkg = JSON.parse(readText(pkgPath));
|
|
60359
61700
|
deps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
60360
61701
|
} catch {
|
|
60361
61702
|
}
|
|
60362
61703
|
}
|
|
60363
|
-
const detection = detectFrameworks(deps, (f) =>
|
|
61704
|
+
const detection = detectFrameworks(deps, (f) => existsSync32(join47(root, f)));
|
|
60364
61705
|
if (p.flags.json) console.log(JSON.stringify(detection, null, 2));
|
|
60365
61706
|
else console.log(renderPlan(detection, lang));
|
|
60366
61707
|
return 0;
|
|
@@ -60559,9 +61900,9 @@ function applyAdjudicationFile(p, adj, lang) {
|
|
|
60559
61900
|
return 1;
|
|
60560
61901
|
}
|
|
60561
61902
|
const out2 = typeof p.flags.out === "string" ? p.flags.out : ".";
|
|
60562
|
-
|
|
60563
|
-
const auditPath =
|
|
60564
|
-
|
|
61903
|
+
mkdirSync17(out2, { recursive: true });
|
|
61904
|
+
const auditPath = join47(out2, "audit-latest.json");
|
|
61905
|
+
writeFileSync19(auditPath, JSON.stringify(r.audit, null, 2) + "\n");
|
|
60565
61906
|
if (p.flags.json)
|
|
60566
61907
|
console.log(
|
|
60567
61908
|
JSON.stringify(
|
|
@@ -60669,9 +62010,9 @@ async function cmdJudge(p) {
|
|
|
60669
62010
|
if (r.issues.length > 40) console.error(` \u2026 +${r.issues.length - 40}`);
|
|
60670
62011
|
return 1;
|
|
60671
62012
|
}
|
|
60672
|
-
|
|
60673
|
-
const auditPath =
|
|
60674
|
-
|
|
62013
|
+
mkdirSync17(out2, { recursive: true });
|
|
62014
|
+
const auditPath = join47(out2, "audit-latest.json");
|
|
62015
|
+
writeFileSync19(auditPath, `${JSON.stringify(r.audit, null, 2)}
|
|
60675
62016
|
`);
|
|
60676
62017
|
console.log(
|
|
60677
62018
|
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}`
|
|
@@ -60740,7 +62081,7 @@ async function cmdScan(p) {
|
|
|
60740
62081
|
return 0;
|
|
60741
62082
|
}
|
|
60742
62083
|
for (const target of p.positionals.filter((a) => a !== "-")) {
|
|
60743
|
-
if (/^https?:\/\//i.test(target) ||
|
|
62084
|
+
if (/^https?:\/\//i.test(target) || existsSync32(target)) continue;
|
|
60744
62085
|
console.error(
|
|
60745
62086
|
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.`
|
|
60746
62087
|
);
|
|
@@ -60867,7 +62208,7 @@ async function cmdScan(p) {
|
|
|
60867
62208
|
}
|
|
60868
62209
|
let merged = mergeDynamic(audit2, dynamic, lang);
|
|
60869
62210
|
if (dynamic.snapshots?.length) {
|
|
60870
|
-
const doms = dynamic.snapshots.map((id) =>
|
|
62211
|
+
const doms = dynamic.snapshots.map((id) => join47(snapshotRoot ?? ".", PAGES_DIR, id, "dom.html")).filter((f) => existsSync32(f));
|
|
60871
62212
|
if (doms.length) {
|
|
60872
62213
|
const snapAudit = runAudit({ inputs: doms, onWarn: (m) => console.error(m) });
|
|
60873
62214
|
merged = mergeSnapshotAudit(merged, snapAudit);
|
|
@@ -60878,12 +62219,12 @@ async function cmdScan(p) {
|
|
|
60878
62219
|
attributePages(merged, scope);
|
|
60879
62220
|
}
|
|
60880
62221
|
}
|
|
60881
|
-
|
|
60882
|
-
|
|
62222
|
+
mkdirSync17(out2, { recursive: true });
|
|
62223
|
+
writeFileSync19(join47(out2, "audit-latest.json"), JSON.stringify(merged, null, 2) + "\n");
|
|
60883
62224
|
if (p.flags.json) console.log(JSON.stringify(merged, null, 2));
|
|
60884
62225
|
else {
|
|
60885
62226
|
console.log(
|
|
60886
|
-
lang === "fr" ? `Audit statique + dynamique fusionn\xE9 \u2192 ${
|
|
62227
|
+
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).`
|
|
60887
62228
|
);
|
|
60888
62229
|
if (dynamic.snapshots?.length)
|
|
60889
62230
|
console.log(
|
|
@@ -61125,9 +62466,9 @@ async function cmdImport(p) {
|
|
|
61125
62466
|
return 1;
|
|
61126
62467
|
}
|
|
61127
62468
|
if (outDir) {
|
|
61128
|
-
|
|
61129
|
-
const rawFile =
|
|
61130
|
-
|
|
62469
|
+
mkdirSync17(outDir, { recursive: true });
|
|
62470
|
+
const rawFile = join47(outDir, `external-${adapterId}-${arg}.raw.json`);
|
|
62471
|
+
writeFileSync19(rawFile, rawText.endsWith("\n") ? rawText : `${rawText}
|
|
61131
62472
|
`);
|
|
61132
62473
|
if (!p.flags.json) console.log(rawFile);
|
|
61133
62474
|
}
|
|
@@ -61161,9 +62502,9 @@ async function cmdImport(p) {
|
|
|
61161
62502
|
return 0;
|
|
61162
62503
|
}
|
|
61163
62504
|
if (outDir) {
|
|
61164
|
-
|
|
61165
|
-
const file =
|
|
61166
|
-
|
|
62505
|
+
mkdirSync17(outDir, { recursive: true });
|
|
62506
|
+
const file = join47(outDir, "external-latest.json");
|
|
62507
|
+
writeFileSync19(file, `${JSON.stringify(audit2, null, 2)}
|
|
61167
62508
|
`);
|
|
61168
62509
|
console.log(file);
|
|
61169
62510
|
} else {
|
|
@@ -61268,7 +62609,7 @@ function cmdOrchestrate(p) {
|
|
|
61268
62609
|
}
|
|
61269
62610
|
const engineAbs = realpathSync6(fileURLToPath5(import.meta.url));
|
|
61270
62611
|
if (p.flags.list === true) {
|
|
61271
|
-
if (!
|
|
62612
|
+
if (!existsSync32(runFlag)) {
|
|
61272
62613
|
console.error(`ultra11y orchestrate: run dir not found: ${runFlag}.`);
|
|
61273
62614
|
return 2;
|
|
61274
62615
|
}
|
|
@@ -61295,7 +62636,7 @@ function cmdOrchestrate(p) {
|
|
|
61295
62636
|
);
|
|
61296
62637
|
} else {
|
|
61297
62638
|
console.log(
|
|
61298
|
-
lang === "fr" ? `Suivez ${
|
|
62639
|
+
lang === "fr" ? `Suivez ${join47(runFlag, "orchestration", "RUNBOOK.md")} s\xE9quentiellement (chemin \xE9co).` : `Follow ${join47(runFlag, "orchestration", "RUNBOOK.md")} sequentially (the eco path).`
|
|
61299
62640
|
);
|
|
61300
62641
|
}
|
|
61301
62642
|
if (p.flags.phase === void 0 && workflows.length === 0 && p.flags.eco !== true) {
|