ultra11y 5.3.3 → 5.4.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/package.json +1 -1
- package/scripts/ultra11y.mjs +1721 -1272
- package/skills/review-a11y/SKILL.md +1 -1
- package/skills/review-a11y/scripts/ultra11y.mjs +1721 -1272
- package/skills/ultra11y/SKILL.md +2 -2
- package/skills/ultra11y/references/ci.md +46 -4
- package/skills/ultra11y/references/judgment.md +50 -12
- package/skills/ultra11y/references/pages.md +13 -4
- package/skills/ultra11y/scripts/ultra11y.mjs +1721 -1272
- package/src/adjudicate.ts +229 -41
- package/src/audit.ts +163 -2
- package/src/cli.ts +216 -18
- package/src/ledger.ts +250 -0
- package/src/orchestrate-templates.ts +2 -2
- package/src/report.ts +24 -6
- package/src/rules/rendered.ts +39 -1
- package/src/types.ts +10 -3
|
@@ -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 writeFileSync20, mkdirSync as mkdirSync18, existsSync as existsSync35, readFileSync as readFileSync33, appendFileSync, copyFileSync as copyFileSync4 } from "fs";
|
|
5
|
+
import { join as join51, relative as relative5, resolve as resolve16, sep as sep7, dirname as dirname15 } from "path";
|
|
6
6
|
import { fileURLToPath as fileURLToPath5, pathToFileURL as pathToFileURL3 } from "url";
|
|
7
7
|
|
|
8
8
|
// src/types.ts
|
|
9
|
-
var VERSION = "5.
|
|
9
|
+
var VERSION = "5.4.0";
|
|
10
10
|
var SCHEMA_VERSION = 2;
|
|
11
11
|
|
|
12
12
|
// src/audit.ts
|
|
@@ -38491,176 +38491,795 @@ function toFinding(doc, ruleId, def, rf, ruleAdvisory) {
|
|
|
38491
38491
|
};
|
|
38492
38492
|
}
|
|
38493
38493
|
|
|
38494
|
-
// src/
|
|
38495
|
-
|
|
38496
|
-
|
|
38497
|
-
|
|
38494
|
+
// src/rules/rendered.ts
|
|
38495
|
+
import { readFileSync as readFileSync15 } from "fs";
|
|
38496
|
+
|
|
38497
|
+
// src/color.ts
|
|
38498
|
+
var NAMED = {
|
|
38499
|
+
transparent: { r: 0, g: 0, b: 0, a: 0 },
|
|
38500
|
+
white: { r: 255, g: 255, b: 255, a: 1 },
|
|
38501
|
+
black: { r: 0, g: 0, b: 0, a: 1 },
|
|
38502
|
+
red: { r: 255, g: 0, b: 0, a: 1 },
|
|
38503
|
+
lime: { r: 0, g: 255, b: 0, a: 1 },
|
|
38504
|
+
green: { r: 0, g: 128, b: 0, a: 1 },
|
|
38505
|
+
blue: { r: 0, g: 0, b: 255, a: 1 },
|
|
38506
|
+
yellow: { r: 255, g: 255, b: 0, a: 1 },
|
|
38507
|
+
cyan: { r: 0, g: 255, b: 255, a: 1 },
|
|
38508
|
+
aqua: { r: 0, g: 255, b: 255, a: 1 },
|
|
38509
|
+
magenta: { r: 255, g: 0, b: 255, a: 1 },
|
|
38510
|
+
fuchsia: { r: 255, g: 0, b: 255, a: 1 },
|
|
38511
|
+
silver: { r: 192, g: 192, b: 192, a: 1 },
|
|
38512
|
+
gray: { r: 128, g: 128, b: 128, a: 1 },
|
|
38513
|
+
grey: { r: 128, g: 128, b: 128, a: 1 },
|
|
38514
|
+
maroon: { r: 128, g: 0, b: 0, a: 1 },
|
|
38515
|
+
olive: { r: 128, g: 128, b: 0, a: 1 },
|
|
38516
|
+
purple: { r: 128, g: 0, b: 128, a: 1 },
|
|
38517
|
+
teal: { r: 0, g: 128, b: 128, a: 1 },
|
|
38518
|
+
navy: { r: 0, g: 0, b: 128, a: 1 },
|
|
38519
|
+
orange: { r: 255, g: 165, b: 0, a: 1 }
|
|
38520
|
+
};
|
|
38521
|
+
function hex(part) {
|
|
38522
|
+
return parseInt(part.length === 1 ? part + part : part, 16);
|
|
38498
38523
|
}
|
|
38499
|
-
|
|
38500
|
-
|
|
38501
|
-
|
|
38502
|
-
|
|
38503
|
-
|
|
38524
|
+
function parseColor(input) {
|
|
38525
|
+
const s = input.trim().toLowerCase();
|
|
38526
|
+
if (!s) return null;
|
|
38527
|
+
if (s in NAMED) return { ...NAMED[s] };
|
|
38528
|
+
if (s.startsWith("#")) {
|
|
38529
|
+
const h = s.slice(1);
|
|
38530
|
+
if (/^[0-9a-f]{3}$/.test(h)) return { r: hex(h[0]), g: hex(h[1]), b: hex(h[2]), a: 1 };
|
|
38531
|
+
if (/^[0-9a-f]{4}$/.test(h)) return { r: hex(h[0]), g: hex(h[1]), b: hex(h[2]), a: hex(h[3]) / 255 };
|
|
38532
|
+
if (/^[0-9a-f]{6}$/.test(h)) return { r: hex(h.slice(0, 2)), g: hex(h.slice(2, 4)), b: hex(h.slice(4, 6)), a: 1 };
|
|
38533
|
+
if (/^[0-9a-f]{8}$/.test(h)) return { r: hex(h.slice(0, 2)), g: hex(h.slice(2, 4)), b: hex(h.slice(4, 6)), a: hex(h.slice(6, 8)) / 255 };
|
|
38534
|
+
return null;
|
|
38504
38535
|
}
|
|
38505
|
-
|
|
38536
|
+
const m = /^rgba?\(([^)]+)\)$/.exec(s);
|
|
38537
|
+
if (m) {
|
|
38538
|
+
const parts2 = m[1].split(/[,/\s]+/).filter(Boolean);
|
|
38539
|
+
if (parts2.length < 3) return null;
|
|
38540
|
+
const chan = (p) => p.endsWith("%") ? Math.round(parseFloat(p) / 100 * 255) : parseFloat(p);
|
|
38541
|
+
const r = chan(parts2[0]);
|
|
38542
|
+
const g = chan(parts2[1]);
|
|
38543
|
+
const b = chan(parts2[2]);
|
|
38544
|
+
const a = parts2[3] !== void 0 ? parts2[3].endsWith("%") ? parseFloat(parts2[3]) / 100 : parseFloat(parts2[3]) : 1;
|
|
38545
|
+
if ([r, g, b, a].some((n) => Number.isNaN(n))) return null;
|
|
38546
|
+
return { r, g, b, a };
|
|
38547
|
+
}
|
|
38548
|
+
return null;
|
|
38506
38549
|
}
|
|
38507
|
-
function
|
|
38508
|
-
|
|
38509
|
-
return
|
|
38550
|
+
function channelLuminance(c2) {
|
|
38551
|
+
const cs = c2 / 255;
|
|
38552
|
+
return cs <= 0.03928 ? cs / 12.92 : ((cs + 0.055) / 1.055) ** 2.4;
|
|
38510
38553
|
}
|
|
38511
|
-
function
|
|
38512
|
-
return
|
|
38554
|
+
function relativeLuminance({ r, g, b }) {
|
|
38555
|
+
return 0.2126 * channelLuminance(r) + 0.7152 * channelLuminance(g) + 0.0722 * channelLuminance(b);
|
|
38513
38556
|
}
|
|
38514
|
-
function
|
|
38515
|
-
|
|
38557
|
+
function contrastRatio(a, b) {
|
|
38558
|
+
const la = relativeLuminance(a);
|
|
38559
|
+
const lb = relativeLuminance(b);
|
|
38560
|
+
const [hi, lo] = la >= lb ? [la, lb] : [lb, la];
|
|
38561
|
+
return (hi + 0.05) / (lo + 0.05);
|
|
38516
38562
|
}
|
|
38517
|
-
function
|
|
38518
|
-
const
|
|
38519
|
-
|
|
38520
|
-
|
|
38563
|
+
function parseInlineStyle(style) {
|
|
38564
|
+
const out2 = /* @__PURE__ */ new Map();
|
|
38565
|
+
for (const decl of style.split(";")) {
|
|
38566
|
+
const i2 = decl.indexOf(":");
|
|
38567
|
+
if (i2 === -1) continue;
|
|
38568
|
+
const prop = decl.slice(0, i2).trim().toLowerCase();
|
|
38569
|
+
const val = decl.slice(i2 + 1).trim();
|
|
38570
|
+
if (prop && val) out2.set(prop, val);
|
|
38571
|
+
}
|
|
38572
|
+
return out2;
|
|
38521
38573
|
}
|
|
38522
|
-
|
|
38523
|
-
|
|
38524
|
-
|
|
38525
|
-
|
|
38526
|
-
|
|
38527
|
-
|
|
38574
|
+
|
|
38575
|
+
// src/pixel.ts
|
|
38576
|
+
import { deflateSync, inflateSync } from "zlib";
|
|
38577
|
+
var SIGNATURE = [137, 80, 78, 71, 13, 10, 26, 10];
|
|
38578
|
+
function paeth(a, b, c2) {
|
|
38579
|
+
const p = a + b - c2;
|
|
38580
|
+
const pa = Math.abs(p - a);
|
|
38581
|
+
const pb = Math.abs(p - b);
|
|
38582
|
+
const pc = Math.abs(p - c2);
|
|
38583
|
+
if (pa <= pb && pa <= pc) return a;
|
|
38584
|
+
return pb <= pc ? b : c2;
|
|
38585
|
+
}
|
|
38586
|
+
function decodePng(buf) {
|
|
38587
|
+
if (buf.length < 8 + 25) return null;
|
|
38588
|
+
for (let i2 = 0; i2 < SIGNATURE.length; i2++) if (buf[i2] !== SIGNATURE[i2]) return null;
|
|
38589
|
+
let width = 0;
|
|
38590
|
+
let height = 0;
|
|
38591
|
+
let bitDepth = 0;
|
|
38592
|
+
let colorType = 0;
|
|
38593
|
+
let interlace = 0;
|
|
38594
|
+
const idat = [];
|
|
38595
|
+
let off = 8;
|
|
38596
|
+
while (off + 8 <= buf.length) {
|
|
38597
|
+
const len = buf.readUInt32BE(off);
|
|
38598
|
+
const type = buf.toString("ascii", off + 4, off + 8);
|
|
38599
|
+
const dataStart = off + 8;
|
|
38600
|
+
const dataEnd = dataStart + len;
|
|
38601
|
+
if (dataEnd + 4 > buf.length) return null;
|
|
38602
|
+
if (type === "IHDR") {
|
|
38603
|
+
if (len < 13) return null;
|
|
38604
|
+
width = buf.readUInt32BE(dataStart);
|
|
38605
|
+
height = buf.readUInt32BE(dataStart + 4);
|
|
38606
|
+
bitDepth = buf[dataStart + 8] ?? 0;
|
|
38607
|
+
colorType = buf[dataStart + 9] ?? 0;
|
|
38608
|
+
interlace = buf[dataStart + 12] ?? 0;
|
|
38609
|
+
} else if (type === "IDAT") {
|
|
38610
|
+
idat.push(buf.subarray(dataStart, dataEnd));
|
|
38611
|
+
} else if (type === "IEND") {
|
|
38612
|
+
break;
|
|
38528
38613
|
}
|
|
38529
|
-
|
|
38530
|
-
|
|
38531
|
-
|
|
38532
|
-
|
|
38614
|
+
off = dataEnd + 4;
|
|
38615
|
+
}
|
|
38616
|
+
if (!width || !height || !idat.length) return null;
|
|
38617
|
+
if (bitDepth !== 8) return null;
|
|
38618
|
+
if (colorType !== 2 && colorType !== 6) return null;
|
|
38619
|
+
if (interlace !== 0) return null;
|
|
38620
|
+
const channels = colorType === 6 ? 4 : 3;
|
|
38621
|
+
let raw;
|
|
38622
|
+
try {
|
|
38623
|
+
raw = inflateSync(Buffer.concat(idat));
|
|
38624
|
+
} catch {
|
|
38625
|
+
return null;
|
|
38626
|
+
}
|
|
38627
|
+
const stride = width * channels;
|
|
38628
|
+
if (raw.length < height * (stride + 1)) return null;
|
|
38629
|
+
const out2 = Buffer.alloc(height * stride);
|
|
38630
|
+
for (let y = 0; y < height; y++) {
|
|
38631
|
+
const filter = raw[y * (stride + 1)] ?? 0;
|
|
38632
|
+
const lineIn = y * (stride + 1) + 1;
|
|
38633
|
+
const lineOut = y * stride;
|
|
38634
|
+
const prevOut = lineOut - stride;
|
|
38635
|
+
for (let i2 = 0; i2 < stride; i2++) {
|
|
38636
|
+
const x = raw[lineIn + i2] ?? 0;
|
|
38637
|
+
const a = i2 >= channels ? out2[lineOut + i2 - channels] ?? 0 : 0;
|
|
38638
|
+
const b = y > 0 ? out2[prevOut + i2] ?? 0 : 0;
|
|
38639
|
+
const c2 = y > 0 && i2 >= channels ? out2[prevOut + i2 - channels] ?? 0 : 0;
|
|
38640
|
+
let v;
|
|
38641
|
+
switch (filter) {
|
|
38642
|
+
case 0:
|
|
38643
|
+
v = x;
|
|
38644
|
+
break;
|
|
38645
|
+
case 1:
|
|
38646
|
+
v = x + a;
|
|
38647
|
+
break;
|
|
38648
|
+
case 2:
|
|
38649
|
+
v = x + b;
|
|
38650
|
+
break;
|
|
38651
|
+
case 3:
|
|
38652
|
+
v = x + (a + b >> 1);
|
|
38653
|
+
break;
|
|
38654
|
+
case 4:
|
|
38655
|
+
v = x + paeth(a, b, c2);
|
|
38656
|
+
break;
|
|
38657
|
+
default:
|
|
38658
|
+
return null;
|
|
38659
|
+
}
|
|
38660
|
+
out2[lineOut + i2] = v & 255;
|
|
38533
38661
|
}
|
|
38534
|
-
|
|
38535
|
-
|
|
38536
|
-
|
|
38537
|
-
|
|
38662
|
+
}
|
|
38663
|
+
return {
|
|
38664
|
+
width,
|
|
38665
|
+
height,
|
|
38666
|
+
at(x, y) {
|
|
38667
|
+
if (x < 0 || y < 0 || x >= width || y >= height) return void 0;
|
|
38668
|
+
const p = y * stride + x * channels;
|
|
38669
|
+
return {
|
|
38670
|
+
r: out2[p] ?? 0,
|
|
38671
|
+
g: out2[p + 1] ?? 0,
|
|
38672
|
+
b: out2[p + 2] ?? 0,
|
|
38673
|
+
a: channels === 4 ? (out2[p + 3] ?? 255) / 255 : 1
|
|
38674
|
+
};
|
|
38538
38675
|
}
|
|
38539
|
-
if (attr(n, "aria-hidden") === "true") return;
|
|
38540
|
-
for (const c2 of n.children) walk3(c2);
|
|
38541
38676
|
};
|
|
38542
|
-
for (const c2 of el.children) walk3(c2);
|
|
38543
|
-
return collapse(out2);
|
|
38544
38677
|
}
|
|
38545
|
-
function
|
|
38546
|
-
const
|
|
38547
|
-
|
|
38548
|
-
const
|
|
38549
|
-
|
|
38550
|
-
|
|
38551
|
-
|
|
38678
|
+
function clamp(img, r) {
|
|
38679
|
+
const x = Math.max(0, Math.round(r.x));
|
|
38680
|
+
const y = Math.max(0, Math.round(r.y));
|
|
38681
|
+
const x2 = Math.min(img.width, Math.round(r.x + r.w));
|
|
38682
|
+
const y2 = Math.min(img.height, Math.round(r.y + r.h));
|
|
38683
|
+
if (x2 <= x || y2 <= y) return null;
|
|
38684
|
+
return { x, y, w: x2 - x, h: y2 - y };
|
|
38685
|
+
}
|
|
38686
|
+
var MAX_SAMPLES = 4096;
|
|
38687
|
+
var DOMINANCE = 0.6;
|
|
38688
|
+
var BUCKET = 16;
|
|
38689
|
+
function dominantBackground(img, rect) {
|
|
38690
|
+
const r = clamp(img, rect);
|
|
38691
|
+
if (!r) return null;
|
|
38692
|
+
const step = Math.max(1, Math.floor(Math.sqrt(r.w * r.h / MAX_SAMPLES)));
|
|
38693
|
+
const counts = /* @__PURE__ */ new Map();
|
|
38694
|
+
let total = 0;
|
|
38695
|
+
for (let y = r.y; y < r.y + r.h; y += step) {
|
|
38696
|
+
for (let x = r.x; x < r.x + r.w; x += step) {
|
|
38697
|
+
const p = img.at(x, y);
|
|
38698
|
+
if (!p) continue;
|
|
38699
|
+
const key = Math.round(p.r / BUCKET) << 16 | Math.round(p.g / BUCKET) << 8 | Math.round(p.b / BUCKET);
|
|
38700
|
+
const cur = counts.get(key) ?? { n: 0, r: 0, g: 0, b: 0 };
|
|
38701
|
+
cur.n++;
|
|
38702
|
+
cur.r += p.r;
|
|
38703
|
+
cur.g += p.g;
|
|
38704
|
+
cur.b += p.b;
|
|
38705
|
+
counts.set(key, cur);
|
|
38706
|
+
total++;
|
|
38707
|
+
}
|
|
38552
38708
|
}
|
|
38553
|
-
|
|
38709
|
+
if (!total) return null;
|
|
38710
|
+
let best = null;
|
|
38711
|
+
for (const c2 of counts.values()) if (!best || c2.n > best.n) best = c2;
|
|
38712
|
+
if (!best || best.n / total < DOMINANCE) return null;
|
|
38713
|
+
return { r: Math.round(best.r / best.n), g: Math.round(best.g / best.n), b: Math.round(best.b / best.n), a: 1 };
|
|
38554
38714
|
}
|
|
38555
|
-
var
|
|
38556
|
-
|
|
38557
|
-
|
|
38558
|
-
|
|
38559
|
-
|
|
38560
|
-
|
|
38561
|
-
const ariaLabel = (boundAttr(el, "aria-label") ?? "").trim();
|
|
38562
|
-
if (ariaLabel) return ariaLabel;
|
|
38563
|
-
if (el.tag === "img" || el.tag === "area") {
|
|
38564
|
-
return (boundAttr(el, "alt") ?? "").trim();
|
|
38715
|
+
var CRC_TABLE = /* @__PURE__ */ (() => {
|
|
38716
|
+
const t2 = new Uint32Array(256);
|
|
38717
|
+
for (let n = 0; n < 256; n++) {
|
|
38718
|
+
let c2 = n;
|
|
38719
|
+
for (let k = 0; k < 8; k++) c2 = c2 & 1 ? 3988292384 ^ c2 >>> 1 : c2 >>> 1;
|
|
38720
|
+
t2[n] = c2 >>> 0;
|
|
38565
38721
|
}
|
|
38566
|
-
|
|
38567
|
-
|
|
38568
|
-
|
|
38569
|
-
|
|
38570
|
-
|
|
38571
|
-
|
|
38572
|
-
|
|
38722
|
+
return t2;
|
|
38723
|
+
})();
|
|
38724
|
+
function crc32(buf) {
|
|
38725
|
+
let c2 = 4294967295;
|
|
38726
|
+
for (let i2 = 0; i2 < buf.length; i2++) c2 = CRC_TABLE[(c2 ^ buf[i2]) & 255] ^ c2 >>> 8;
|
|
38727
|
+
return (c2 ^ 4294967295) >>> 0;
|
|
38728
|
+
}
|
|
38729
|
+
function chunk(type, data2) {
|
|
38730
|
+
const len = Buffer.alloc(4);
|
|
38731
|
+
len.writeUInt32BE(data2.length);
|
|
38732
|
+
const body3 = Buffer.concat([Buffer.from(type, "latin1"), data2]);
|
|
38733
|
+
const crc = Buffer.alloc(4);
|
|
38734
|
+
crc.writeUInt32BE(crc32(body3));
|
|
38735
|
+
return Buffer.concat([len, body3, crc]);
|
|
38736
|
+
}
|
|
38737
|
+
function encodePng(r) {
|
|
38738
|
+
const expected = r.width * r.height * 4;
|
|
38739
|
+
if (r.width <= 0 || r.height <= 0) throw new Error(`encodePng: refusing a ${r.width}\xD7${r.height} raster`);
|
|
38740
|
+
if (r.data.length !== expected) {
|
|
38741
|
+
throw new Error(`encodePng: ${r.width}\xD7${r.height} needs ${expected} bytes of RGBA, got ${r.data.length}`);
|
|
38742
|
+
}
|
|
38743
|
+
const stride = r.width * 4;
|
|
38744
|
+
const raw = Buffer.alloc(r.height * (stride + 1));
|
|
38745
|
+
for (let y = 0; y < r.height; y++) {
|
|
38746
|
+
raw[y * (stride + 1)] = 0;
|
|
38747
|
+
r.data.copy(raw, y * (stride + 1) + 1, y * stride, (y + 1) * stride);
|
|
38748
|
+
}
|
|
38749
|
+
const ihdr = Buffer.alloc(13);
|
|
38750
|
+
ihdr.writeUInt32BE(r.width, 0);
|
|
38751
|
+
ihdr.writeUInt32BE(r.height, 4);
|
|
38752
|
+
ihdr[8] = 8;
|
|
38753
|
+
ihdr[9] = 6;
|
|
38754
|
+
return Buffer.concat([Buffer.from(SIGNATURE), chunk("IHDR", ihdr), chunk("IDAT", deflateSync(raw, { level: 9 })), chunk("IEND", Buffer.alloc(0))]);
|
|
38755
|
+
}
|
|
38756
|
+
function cropImage(img, rect) {
|
|
38757
|
+
const r = clamp(img, rect);
|
|
38758
|
+
if (!r) return null;
|
|
38759
|
+
const data2 = Buffer.alloc(r.w * r.h * 4);
|
|
38760
|
+
let i2 = 0;
|
|
38761
|
+
for (let y = r.y; y < r.y + r.h; y++) {
|
|
38762
|
+
for (let x = r.x; x < r.x + r.w; x++) {
|
|
38763
|
+
const p = img.at(x, y);
|
|
38764
|
+
data2[i2] = p?.r ?? 0;
|
|
38765
|
+
data2[i2 + 1] = p?.g ?? 0;
|
|
38766
|
+
data2[i2 + 2] = p?.b ?? 0;
|
|
38767
|
+
data2[i2 + 3] = Math.round((p?.a ?? 1) * 255);
|
|
38768
|
+
i2 += 4;
|
|
38573
38769
|
}
|
|
38574
38770
|
}
|
|
38575
|
-
|
|
38576
|
-
if (content) return content;
|
|
38577
|
-
return (attr(el, "title") ?? "").trim();
|
|
38771
|
+
return { raster: { width: r.w, height: r.h, data: data2 }, rect: r };
|
|
38578
38772
|
}
|
|
38579
|
-
|
|
38580
|
-
|
|
38581
|
-
|
|
38582
|
-
|
|
38583
|
-
if (
|
|
38584
|
-
|
|
38585
|
-
|
|
38586
|
-
|
|
38773
|
+
function blend(raster, x, y, c2) {
|
|
38774
|
+
if (x < 0 || y < 0 || x >= raster.width || y >= raster.height) return;
|
|
38775
|
+
const p = (y * raster.width + x) * 4;
|
|
38776
|
+
const a = Math.min(1, Math.max(0, c2.a));
|
|
38777
|
+
if (a >= 1) {
|
|
38778
|
+
raster.data[p] = c2.r;
|
|
38779
|
+
raster.data[p + 1] = c2.g;
|
|
38780
|
+
raster.data[p + 2] = c2.b;
|
|
38781
|
+
raster.data[p + 3] = 255;
|
|
38782
|
+
return;
|
|
38587
38783
|
}
|
|
38588
|
-
|
|
38784
|
+
const inv = 1 - a;
|
|
38785
|
+
raster.data[p] = Math.round(c2.r * a + raster.data[p] * inv);
|
|
38786
|
+
raster.data[p + 1] = Math.round(c2.g * a + raster.data[p + 1] * inv);
|
|
38787
|
+
raster.data[p + 2] = Math.round(c2.b * a + raster.data[p + 2] * inv);
|
|
38788
|
+
raster.data[p + 3] = Math.max(raster.data[p + 3], Math.round(a * 255));
|
|
38589
38789
|
}
|
|
38590
|
-
function
|
|
38591
|
-
|
|
38592
|
-
|
|
38593
|
-
|
|
38594
|
-
const
|
|
38595
|
-
|
|
38596
|
-
|
|
38597
|
-
|
|
38790
|
+
function fillRect(raster, rect, color) {
|
|
38791
|
+
const x0 = Math.max(0, Math.round(rect.x));
|
|
38792
|
+
const y0 = Math.max(0, Math.round(rect.y));
|
|
38793
|
+
const x1 = Math.min(raster.width, Math.round(rect.x + rect.w));
|
|
38794
|
+
const y1 = Math.min(raster.height, Math.round(rect.y + rect.h));
|
|
38795
|
+
for (let y = y0; y < y1; y++) for (let x = x0; x < x1; x++) blend(raster, x, y, color);
|
|
38796
|
+
}
|
|
38797
|
+
function strokeRect(raster, rect, color, width, dash) {
|
|
38798
|
+
const t2 = Math.max(1, Math.round(width));
|
|
38799
|
+
const x0 = Math.round(rect.x);
|
|
38800
|
+
const y0 = Math.round(rect.y);
|
|
38801
|
+
const x1 = Math.round(rect.x + rect.w);
|
|
38802
|
+
const y1 = Math.round(rect.y + rect.h);
|
|
38803
|
+
if (x1 <= x0 || y1 <= y0) return;
|
|
38804
|
+
const on = (coord) => {
|
|
38805
|
+
if (!dash || dash <= 1) return true;
|
|
38806
|
+
const half = dash / 2;
|
|
38807
|
+
return Math.floor(coord / half) % 2 === 0;
|
|
38808
|
+
};
|
|
38809
|
+
for (let y = y0; y < y1; y++) {
|
|
38810
|
+
for (let x = x0; x < x1; x++) {
|
|
38811
|
+
const top = y < y0 + t2;
|
|
38812
|
+
const bottom = y >= y1 - t2;
|
|
38813
|
+
const left = x < x0 + t2;
|
|
38814
|
+
const right = x >= x1 - t2;
|
|
38815
|
+
if (!top && !bottom && !left && !right) continue;
|
|
38816
|
+
if (!on(top || bottom ? x : y)) continue;
|
|
38817
|
+
blend(raster, x, y, color);
|
|
38818
|
+
}
|
|
38598
38819
|
}
|
|
38599
|
-
if (ancestors(el).some((a) => a.tag === "label")) return { hasLabel: true, via: "wrapping" };
|
|
38600
|
-
if ((attr(el, "title") ?? "").trim()) return { hasLabel: true, via: "title" };
|
|
38601
|
-
return { hasLabel: false, via: null };
|
|
38602
38820
|
}
|
|
38603
38821
|
|
|
38604
|
-
// src/rules/
|
|
38605
|
-
var
|
|
38606
|
-
|
|
38607
|
-
|
|
38608
|
-
|
|
38609
|
-
|
|
38610
|
-
|
|
38822
|
+
// src/rules/rendered.ts
|
|
38823
|
+
var SKIP_TAGS = /* @__PURE__ */ new Set(["script", "style", "head", "title", "meta", "noscript", "link", "html", "br", "wbr"]);
|
|
38824
|
+
function styleAt(doc, i2) {
|
|
38825
|
+
return doc.signals?.styles?.get(i2);
|
|
38826
|
+
}
|
|
38827
|
+
function hasDirectText(el) {
|
|
38828
|
+
return el.children.some((c2) => c2.type === "text" && c2.data.trim() !== "");
|
|
38829
|
+
}
|
|
38830
|
+
function invisible(css) {
|
|
38831
|
+
if (css.display === "none" || css.visibility === "hidden") return true;
|
|
38832
|
+
const op = Number.parseFloat(css.opacity ?? "1");
|
|
38833
|
+
return Number.isFinite(op) && op === 0;
|
|
38834
|
+
}
|
|
38835
|
+
function backdropOf(doc, index, el) {
|
|
38836
|
+
for (let p = el; p; p = p.parent) {
|
|
38837
|
+
const i2 = index.get(p);
|
|
38838
|
+
if (i2 === void 0) continue;
|
|
38839
|
+
const css = styleAt(doc, i2)?.css;
|
|
38840
|
+
if (!css) continue;
|
|
38841
|
+
const img = css.backgroundImage;
|
|
38842
|
+
if (img && img !== "none") return void 0;
|
|
38843
|
+
const c2 = parseColor(css.backgroundColor ?? "");
|
|
38844
|
+
if (c2 && c2.a >= 1) return { color: c2, fromImage: false };
|
|
38845
|
+
}
|
|
38846
|
+
return void 0;
|
|
38847
|
+
}
|
|
38848
|
+
function isLargeText(css) {
|
|
38849
|
+
const px = Number.parseFloat(css.fontSize ?? "");
|
|
38850
|
+
if (!Number.isFinite(px)) return false;
|
|
38851
|
+
const weight = Number.parseInt(css.fontWeight ?? "400", 10);
|
|
38852
|
+
const bold = Number.isFinite(weight) ? weight >= 700 : css.fontWeight === "bold";
|
|
38853
|
+
return px >= 24 || px >= 18.66 && bold;
|
|
38854
|
+
}
|
|
38855
|
+
function elementIndex(doc) {
|
|
38856
|
+
const m = /* @__PURE__ */ new Map();
|
|
38857
|
+
doc.elements.forEach((el, i2) => m.set(el, i2));
|
|
38858
|
+
return m;
|
|
38859
|
+
}
|
|
38860
|
+
var renderedContrast = {
|
|
38861
|
+
id: "rendered-contrast",
|
|
38862
|
+
criteria: ["1.4.3"],
|
|
38863
|
+
severity: "majeur",
|
|
38611
38864
|
run(doc) {
|
|
38865
|
+
if (!doc.signals?.styles) return [];
|
|
38866
|
+
const index = elementIndex(doc);
|
|
38612
38867
|
const out2 = [];
|
|
38613
|
-
for (const el of
|
|
38614
|
-
|
|
38615
|
-
|
|
38616
|
-
if (
|
|
38617
|
-
const
|
|
38618
|
-
|
|
38619
|
-
|
|
38620
|
-
if (
|
|
38621
|
-
|
|
38622
|
-
|
|
38623
|
-
|
|
38624
|
-
|
|
38625
|
-
el,
|
|
38626
|
-
msgId: "img-alt-missing.title-only",
|
|
38627
|
-
params: { tag: el.tag },
|
|
38628
|
-
advisory: true,
|
|
38629
|
-
severity: "mineur"
|
|
38630
|
-
});
|
|
38631
|
-
continue;
|
|
38632
|
-
}
|
|
38868
|
+
for (const [el, i2] of index) {
|
|
38869
|
+
if (SKIP_TAGS.has(el.tag) || !hasDirectText(el)) continue;
|
|
38870
|
+
const css = styleAt(doc, i2)?.css;
|
|
38871
|
+
if (!css || invisible(css)) continue;
|
|
38872
|
+
const fg = parseColor(css.color ?? "");
|
|
38873
|
+
if (!fg || fg.a < 1) continue;
|
|
38874
|
+
const bd = backdropOf(doc, index, el);
|
|
38875
|
+
if (!bd) continue;
|
|
38876
|
+
const ratio = contrastRatio(fg, bd.color);
|
|
38877
|
+
const large = isLargeText(css);
|
|
38878
|
+
const min = large ? 3 : 4.5;
|
|
38879
|
+
if (ratio >= min) continue;
|
|
38633
38880
|
out2.push({
|
|
38634
|
-
criteriaId: "1.
|
|
38881
|
+
criteriaId: "1.4.3",
|
|
38635
38882
|
el,
|
|
38636
|
-
msgId: "
|
|
38637
|
-
params: {
|
|
38883
|
+
msgId: "rendered-contrast",
|
|
38884
|
+
params: { ratio: ratio.toFixed(2), min, textSize: large ? "large" : "normal" }
|
|
38638
38885
|
});
|
|
38639
38886
|
}
|
|
38640
38887
|
return out2;
|
|
38641
38888
|
}
|
|
38642
38889
|
};
|
|
38643
|
-
var
|
|
38644
|
-
id: "
|
|
38645
|
-
criteria: ["1.
|
|
38890
|
+
var renderedContrastPixel = {
|
|
38891
|
+
id: "rendered-contrast-pixel",
|
|
38892
|
+
criteria: ["1.4.3"],
|
|
38646
38893
|
severity: "majeur",
|
|
38647
38894
|
run(doc) {
|
|
38895
|
+
const shot = doc.signals?.screenshot;
|
|
38896
|
+
const boxes = doc.signals?.boxes;
|
|
38897
|
+
if (!shot || !boxes || !doc.signals?.styles) return [];
|
|
38898
|
+
let img = null;
|
|
38899
|
+
try {
|
|
38900
|
+
img = decodePng(readFileSync15(shot));
|
|
38901
|
+
} catch {
|
|
38902
|
+
return [];
|
|
38903
|
+
}
|
|
38904
|
+
if (!img) return [];
|
|
38905
|
+
const index = elementIndex(doc);
|
|
38648
38906
|
const out2 = [];
|
|
38649
|
-
for (const el of
|
|
38650
|
-
if (el.tag
|
|
38651
|
-
const
|
|
38652
|
-
|
|
38653
|
-
|
|
38654
|
-
const
|
|
38655
|
-
if (
|
|
38656
|
-
|
|
38657
|
-
|
|
38658
|
-
|
|
38659
|
-
|
|
38660
|
-
|
|
38661
|
-
|
|
38662
|
-
|
|
38663
|
-
|
|
38907
|
+
for (const [el, i2] of index) {
|
|
38908
|
+
if (SKIP_TAGS.has(el.tag) || !hasDirectText(el)) continue;
|
|
38909
|
+
const css = styleAt(doc, i2)?.css;
|
|
38910
|
+
if (!css || invisible(css)) continue;
|
|
38911
|
+
if (backdropOf(doc, index, el)) continue;
|
|
38912
|
+
const fg = parseColor(css.color ?? "");
|
|
38913
|
+
if (!fg || fg.a < 1) continue;
|
|
38914
|
+
const box = boxes.get(i2);
|
|
38915
|
+
if (!box || box.w < 4 || box.h < 4) continue;
|
|
38916
|
+
const bg = dominantBackground(img, box);
|
|
38917
|
+
if (!bg) continue;
|
|
38918
|
+
const ratio = contrastRatio(fg, bg);
|
|
38919
|
+
const large = isLargeText(css);
|
|
38920
|
+
const min = large ? 3 : 4.5;
|
|
38921
|
+
if (ratio >= min) continue;
|
|
38922
|
+
out2.push({
|
|
38923
|
+
criteriaId: "1.4.3",
|
|
38924
|
+
el,
|
|
38925
|
+
msgId: "rendered-contrast-pixel",
|
|
38926
|
+
params: { ratio: ratio.toFixed(2), min, textSize: large ? "large" : "normal" }
|
|
38927
|
+
});
|
|
38928
|
+
}
|
|
38929
|
+
return out2;
|
|
38930
|
+
}
|
|
38931
|
+
};
|
|
38932
|
+
function hasNonColourAffordance(css, parentCss) {
|
|
38933
|
+
const deco = css.textDecorationLine ?? "";
|
|
38934
|
+
if (deco && deco !== "none") return true;
|
|
38935
|
+
if ((css.borderBottomStyle ?? "none") !== "none" && Number.parseFloat(css.borderBottomWidth ?? "0") > 0) return true;
|
|
38936
|
+
const bg = parseColor(css.backgroundColor ?? "");
|
|
38937
|
+
if (bg && bg.a > 0) return true;
|
|
38938
|
+
const w = Number.parseInt(css.fontWeight ?? "400", 10);
|
|
38939
|
+
const pw = Number.parseInt(parentCss?.fontWeight ?? "400", 10);
|
|
38940
|
+
if (Number.isFinite(w) && Number.isFinite(pw) && w >= pw + 200) return true;
|
|
38941
|
+
return false;
|
|
38942
|
+
}
|
|
38943
|
+
var TEXT_BLOCK = /* @__PURE__ */ new Set(["p", "li", "dd", "dt", "td", "th", "blockquote", "figcaption", "caption"]);
|
|
38944
|
+
var renderedLinkColourOnly = {
|
|
38945
|
+
id: "rendered-link-colour-only",
|
|
38946
|
+
criteria: ["1.4.1"],
|
|
38947
|
+
severity: "majeur",
|
|
38948
|
+
run(doc) {
|
|
38949
|
+
if (!doc.signals?.styles) return [];
|
|
38950
|
+
const index = elementIndex(doc);
|
|
38951
|
+
const out2 = [];
|
|
38952
|
+
for (const [el, i2] of index) {
|
|
38953
|
+
if (el.tag !== "a" || !hasDirectText(el)) continue;
|
|
38954
|
+
const parent = el.parent;
|
|
38955
|
+
if (!parent || !TEXT_BLOCK.has(parent.tag)) continue;
|
|
38956
|
+
const css = styleAt(doc, i2)?.css;
|
|
38957
|
+
const pi = index.get(parent);
|
|
38958
|
+
const parentCss = pi === void 0 ? void 0 : styleAt(doc, pi)?.css;
|
|
38959
|
+
if (!css || !parentCss || invisible(css)) continue;
|
|
38960
|
+
if (hasNonColourAffordance(css, parentCss)) continue;
|
|
38961
|
+
const linkColor = parseColor(css.color ?? "");
|
|
38962
|
+
const textColor = parseColor(parentCss.color ?? "");
|
|
38963
|
+
if (!linkColor || !textColor) continue;
|
|
38964
|
+
const distinct = contrastRatio(linkColor, textColor);
|
|
38965
|
+
if (distinct <= 1.02) continue;
|
|
38966
|
+
out2.push({
|
|
38967
|
+
criteriaId: "1.4.1",
|
|
38968
|
+
el,
|
|
38969
|
+
msgId: "rendered-link-colour-only",
|
|
38970
|
+
params: { ratio: distinct.toFixed(2) }
|
|
38971
|
+
});
|
|
38972
|
+
}
|
|
38973
|
+
return out2;
|
|
38974
|
+
}
|
|
38975
|
+
};
|
|
38976
|
+
var BOUNDED_CONTROLS = /* @__PURE__ */ new Set(["input", "select", "textarea", "button"]);
|
|
38977
|
+
var SIDES = ["Top", "Right", "Bottom", "Left"];
|
|
38978
|
+
function borderColours(css) {
|
|
38979
|
+
const out2 = [];
|
|
38980
|
+
for (const s of SIDES) {
|
|
38981
|
+
const style = css[`border${s}Style`];
|
|
38982
|
+
const width = Number.parseFloat(css[`border${s}Width`] ?? "0");
|
|
38983
|
+
if (!style || style === "none" || style === "hidden" || !(width > 0)) continue;
|
|
38984
|
+
const c2 = parseColor(css[`border${s}Color`] ?? "");
|
|
38985
|
+
if (c2 && c2.a >= 1) out2.push(c2);
|
|
38986
|
+
}
|
|
38987
|
+
return out2;
|
|
38988
|
+
}
|
|
38989
|
+
var renderedNonTextContrast = {
|
|
38990
|
+
id: "rendered-nontext-contrast",
|
|
38991
|
+
criteria: ["1.4.11"],
|
|
38992
|
+
severity: "majeur",
|
|
38993
|
+
run(doc) {
|
|
38994
|
+
if (!doc.signals?.styles) return [];
|
|
38995
|
+
const index = elementIndex(doc);
|
|
38996
|
+
const out2 = [];
|
|
38997
|
+
for (const [el, i2] of index) {
|
|
38998
|
+
if (!BOUNDED_CONTROLS.has(el.tag)) continue;
|
|
38999
|
+
const type = (el.attribs.type ?? "").toLowerCase();
|
|
39000
|
+
if (el.tag === "input" && (type === "hidden" || type === "image")) continue;
|
|
39001
|
+
const css = styleAt(doc, i2)?.css;
|
|
39002
|
+
if (!css || invisible(css)) continue;
|
|
39003
|
+
if (css.boxShadow && css.boxShadow !== "none") continue;
|
|
39004
|
+
if ((css.outlineStyle ?? "none") !== "none" && Number.parseFloat(css.outlineWidth ?? "0") > 0) continue;
|
|
39005
|
+
const surrounding = el.parent ? backdropOf(doc, index, el.parent) : void 0;
|
|
39006
|
+
if (!surrounding) continue;
|
|
39007
|
+
const own = parseColor(css.backgroundColor ?? "");
|
|
39008
|
+
const fill = own && own.a >= 1 ? own : surrounding.color;
|
|
39009
|
+
if (contrastRatio(fill, surrounding.color) >= 3) continue;
|
|
39010
|
+
const borders = borderColours(css);
|
|
39011
|
+
if (borders.some((b) => contrastRatio(b, surrounding.color) >= 3 || contrastRatio(b, fill) >= 3)) continue;
|
|
39012
|
+
out2.push({
|
|
39013
|
+
criteriaId: "1.4.11",
|
|
39014
|
+
el,
|
|
39015
|
+
msgId: "rendered-nontext-contrast",
|
|
39016
|
+
params: { ratio: contrastRatio(fill, surrounding.color).toFixed(2), control: el.tag }
|
|
39017
|
+
});
|
|
39018
|
+
}
|
|
39019
|
+
return out2;
|
|
39020
|
+
}
|
|
39021
|
+
};
|
|
39022
|
+
var FOCUS_SEL = /:focus(-visible|-within)?\b/;
|
|
39023
|
+
function killsOutline(d) {
|
|
39024
|
+
const o = (d.outline ?? "").trim();
|
|
39025
|
+
if (o === "none" || /^0(px)?( |$)/.test(o) || /\bnone\b/.test(o)) return true;
|
|
39026
|
+
if ((d.outlineStyle ?? "") === "none") return true;
|
|
39027
|
+
return d.outlineWidth !== void 0 && Number.parseFloat(d.outlineWidth) === 0;
|
|
39028
|
+
}
|
|
39029
|
+
function restoresIndicator(d) {
|
|
39030
|
+
if (d.boxShadow && d.boxShadow !== "none") return true;
|
|
39031
|
+
if (d.outlineStyle && d.outlineStyle !== "none" && Number.parseFloat(d.outlineWidth ?? "1") > 0) return true;
|
|
39032
|
+
if (d.outline && !killsOutline(d)) return true;
|
|
39033
|
+
if (d.textDecorationLine && d.textDecorationLine !== "none") return true;
|
|
39034
|
+
for (const k of Object.keys(d)) {
|
|
39035
|
+
if (/^border(Top|Right|Bottom|Left)?(Color|Width|Style)?$/.test(k) && d[k] && d[k] !== "none") return true;
|
|
39036
|
+
if (/^background(Color|Image)?$/.test(k) && d[k] && d[k] !== "none" && d[k] !== "rgba(0, 0, 0, 0)") return true;
|
|
39037
|
+
}
|
|
39038
|
+
return d.filter !== void 0 || d.transform !== void 0 || d.color !== void 0;
|
|
39039
|
+
}
|
|
39040
|
+
var renderedFocusNotVisible = {
|
|
39041
|
+
id: "rendered-focus-not-visible",
|
|
39042
|
+
criteria: ["2.4.7"],
|
|
39043
|
+
severity: "majeur",
|
|
39044
|
+
scope: "page",
|
|
39045
|
+
run(doc) {
|
|
39046
|
+
const css = doc.signals?.css;
|
|
39047
|
+
if (!css || css.unreadable > 0 || css.truncated) return [];
|
|
39048
|
+
const focusRules = css.rules.filter((r) => FOCUS_SEL.test(r.selector));
|
|
39049
|
+
if (!focusRules.length) return [];
|
|
39050
|
+
if (!focusRules.some((r) => killsOutline(r.decls))) return [];
|
|
39051
|
+
if (focusRules.some((r) => restoresIndicator(r.decls))) return [];
|
|
39052
|
+
const anchor = doc.elements.find((e) => ["a", "button", "input", "select", "textarea"].includes(e.tag));
|
|
39053
|
+
if (!anchor) return [];
|
|
39054
|
+
const killer = focusRules.find((r) => killsOutline(r.decls));
|
|
39055
|
+
return [{ criteriaId: "2.4.7", el: doc.elements[0] ?? anchor, msgId: "rendered-focus-not-visible", params: { selector: killer?.selector ?? ":focus" } }];
|
|
39056
|
+
}
|
|
39057
|
+
};
|
|
39058
|
+
var DOC_SEL = /^\s*(\*|:root|html|body)\s*$/i;
|
|
39059
|
+
function quarterTurn(transform) {
|
|
39060
|
+
const m = /rotate[ZY]?\(\s*(-?[\d.]+)deg\s*\)/i.exec(transform);
|
|
39061
|
+
if (!m) return false;
|
|
39062
|
+
const deg = Math.abs(Number.parseFloat(m[1])) % 180;
|
|
39063
|
+
return Math.abs(deg - 90) < 1;
|
|
39064
|
+
}
|
|
39065
|
+
var renderedOrientationLock = {
|
|
39066
|
+
id: "rendered-orientation-lock",
|
|
39067
|
+
criteria: ["1.3.4"],
|
|
39068
|
+
severity: "majeur",
|
|
39069
|
+
scope: "page",
|
|
39070
|
+
run(doc) {
|
|
39071
|
+
const css = doc.signals?.css;
|
|
39072
|
+
if (!css || css.unreadable > 0) return [];
|
|
39073
|
+
for (const r of css.rules) {
|
|
39074
|
+
if (!r.media || !/orientation\s*:/i.test(r.media)) continue;
|
|
39075
|
+
if (!r.selector.split(",").some((s) => DOC_SEL.test(s))) continue;
|
|
39076
|
+
const t2 = r.decls.transform ?? "";
|
|
39077
|
+
if (!quarterTurn(t2)) continue;
|
|
39078
|
+
const el = doc.elements[0];
|
|
39079
|
+
if (!el) continue;
|
|
39080
|
+
return [{ criteriaId: "1.3.4", el, msgId: "rendered-orientation-lock", params: { media: r.media, transform: t2 } }];
|
|
39081
|
+
}
|
|
39082
|
+
return [];
|
|
39083
|
+
}
|
|
39084
|
+
};
|
|
39085
|
+
var renderedRules = [
|
|
39086
|
+
renderedContrast,
|
|
39087
|
+
renderedContrastPixel,
|
|
39088
|
+
renderedLinkColourOnly,
|
|
39089
|
+
renderedNonTextContrast,
|
|
39090
|
+
renderedFocusNotVisible,
|
|
39091
|
+
renderedOrientationLock
|
|
39092
|
+
];
|
|
39093
|
+
var SIGNALS_REQUIRED = {
|
|
39094
|
+
// Computed colours, per element.
|
|
39095
|
+
"rendered-contrast": (s) => !!s.styles && !s.truncated,
|
|
39096
|
+
"rendered-link-colour-only": (s) => !!s.styles && !s.truncated,
|
|
39097
|
+
"rendered-nontext-contrast": (s) => !!s.styles && !s.truncated,
|
|
39098
|
+
// The pixel fallback needs the screenshot AND the boxes to sample it by.
|
|
39099
|
+
"rendered-contrast-pixel": (s) => !!s.screenshot && !!s.boxes && !s.truncated,
|
|
39100
|
+
// These two read the page's own stylesheets, and both decline on an unreadable sheet.
|
|
39101
|
+
"rendered-focus-not-visible": (s) => !!s.css && s.css.unreadable === 0 && !s.css.truncated,
|
|
39102
|
+
"rendered-orientation-lock": (s) => !!s.css && s.css.unreadable === 0
|
|
39103
|
+
};
|
|
39104
|
+
function renderedTestedScs(signals) {
|
|
39105
|
+
const scs = /* @__PURE__ */ new Set();
|
|
39106
|
+
for (const rule of renderedRules) {
|
|
39107
|
+
if (SIGNALS_REQUIRED[rule.id]?.(signals)) for (const sc of rule.criteria) scs.add(sc);
|
|
39108
|
+
}
|
|
39109
|
+
return [...scs].sort();
|
|
39110
|
+
}
|
|
39111
|
+
var RENDERED_SIGNAL_RULES = Object.keys(SIGNALS_REQUIRED);
|
|
39112
|
+
|
|
39113
|
+
// src/name.ts
|
|
39114
|
+
var collapse = (s) => s.replace(/\s+/g, " ").trim();
|
|
39115
|
+
function mayInjectContent(el) {
|
|
39116
|
+
return descendants(el).some((d) => d.tag === "slot" || d.tag !== "#fragment" && !isIntrinsic(d.tag));
|
|
39117
|
+
}
|
|
39118
|
+
var HIDDEN_STYLE = /(^|;)\s*(display\s*:\s*none|visibility\s*:\s*(hidden|collapse))\s*(;|$)/i;
|
|
39119
|
+
function isDisplayHidden(el) {
|
|
39120
|
+
for (const node of [el, ...ancestors(el)]) {
|
|
39121
|
+
if (hasAttr(node, "hidden")) return true;
|
|
39122
|
+
if (HIDDEN_STYLE.test(attr(node, "style") ?? "")) return true;
|
|
39123
|
+
}
|
|
39124
|
+
return false;
|
|
39125
|
+
}
|
|
39126
|
+
function isHiddenFromAT(el) {
|
|
39127
|
+
if (isDisplayHidden(el)) return true;
|
|
39128
|
+
return [el, ...ancestors(el)].some((node) => attr(node, "aria-hidden") === "true");
|
|
39129
|
+
}
|
|
39130
|
+
function isPresentational(el) {
|
|
39131
|
+
return ["presentation", "none"].includes((attr(el, "role") ?? "").trim().toLowerCase());
|
|
39132
|
+
}
|
|
39133
|
+
function isNameExempt(el) {
|
|
39134
|
+
return isHiddenFromAT(el) || isPresentational(el);
|
|
39135
|
+
}
|
|
39136
|
+
function embeddedImageName(n, doc) {
|
|
39137
|
+
const labelled = (attr(n, "aria-labelledby") ?? "").trim();
|
|
39138
|
+
if (labelled) return doc ? ariaLabelledbyText(n, doc) || labelled : labelled;
|
|
39139
|
+
return (boundAttr(n, "aria-label") ?? attr(n, "alt") ?? attr(n, "title") ?? "").trim();
|
|
39140
|
+
}
|
|
39141
|
+
function nameFromContent(el, doc) {
|
|
39142
|
+
let out2 = "";
|
|
39143
|
+
const walk3 = (n) => {
|
|
39144
|
+
if (n.type === "text") {
|
|
39145
|
+
out2 += n.data;
|
|
39146
|
+
return;
|
|
39147
|
+
}
|
|
39148
|
+
if (n.tag === "img") {
|
|
39149
|
+
const a = embeddedImageName(n, doc);
|
|
39150
|
+
if (a) out2 += " " + a;
|
|
39151
|
+
return;
|
|
39152
|
+
}
|
|
39153
|
+
if (n.tag === "svg") {
|
|
39154
|
+
const title2 = descendants(n).find((d) => d.tag === "title");
|
|
39155
|
+
if (title2) out2 += " " + nameFromContent(title2, doc);
|
|
39156
|
+
return;
|
|
39157
|
+
}
|
|
39158
|
+
if (attr(n, "aria-hidden") === "true") return;
|
|
39159
|
+
for (const c2 of n.children) walk3(c2);
|
|
39160
|
+
};
|
|
39161
|
+
for (const c2 of el.children) walk3(c2);
|
|
39162
|
+
return collapse(out2);
|
|
39163
|
+
}
|
|
39164
|
+
function ariaLabelledbyText(el, doc) {
|
|
39165
|
+
const ids = attr(el, "aria-labelledby");
|
|
39166
|
+
if (!ids) return "";
|
|
39167
|
+
const parts2 = [];
|
|
39168
|
+
for (const id of ids.split(/\s+/).filter(Boolean)) {
|
|
39169
|
+
const ref = doc.byId.get(id);
|
|
39170
|
+
if (ref) parts2.push(nameFromContent(ref) || (attr(ref, "aria-label") ?? "").trim());
|
|
39171
|
+
}
|
|
39172
|
+
return collapse(parts2.join(" "));
|
|
39173
|
+
}
|
|
39174
|
+
var BUTTON_INPUT = /* @__PURE__ */ new Set(["button", "submit", "reset"]);
|
|
39175
|
+
var NAMELESS_BY_DEFAULT = /* @__PURE__ */ new Set(["submit", "reset"]);
|
|
39176
|
+
function accessibleName(el, doc) {
|
|
39177
|
+
const labelledby = ariaLabelledbyText(el, doc);
|
|
39178
|
+
if (labelledby) return labelledby;
|
|
39179
|
+
if (hasBoundAttr(el, "aria-labelledby") && !attr(el, "aria-labelledby")) return "\xA0";
|
|
39180
|
+
const ariaLabel = (boundAttr(el, "aria-label") ?? "").trim();
|
|
39181
|
+
if (ariaLabel) return ariaLabel;
|
|
39182
|
+
if (el.tag === "img" || el.tag === "area") {
|
|
39183
|
+
return (boundAttr(el, "alt") ?? "").trim();
|
|
39184
|
+
}
|
|
39185
|
+
if (el.tag === "input") {
|
|
39186
|
+
const type = (attr(el, "type") ?? "text").toLowerCase();
|
|
39187
|
+
if (BUTTON_INPUT.has(type)) {
|
|
39188
|
+
const value = (attr(el, "value") ?? "").trim();
|
|
39189
|
+
if (value) return value;
|
|
39190
|
+
if (NAMELESS_BY_DEFAULT.has(type)) return type === "submit" ? "Submit" : "Reset";
|
|
39191
|
+
return (attr(el, "title") ?? "").trim();
|
|
39192
|
+
}
|
|
39193
|
+
}
|
|
39194
|
+
const content = nameFromContent(el, doc);
|
|
39195
|
+
if (content) return content;
|
|
39196
|
+
return (attr(el, "title") ?? "").trim();
|
|
39197
|
+
}
|
|
39198
|
+
var FIELD_TAGS = /* @__PURE__ */ new Set(["input", "select", "textarea"]);
|
|
39199
|
+
var NON_LABELABLE_INPUT = /* @__PURE__ */ new Set(["hidden", "submit", "reset", "button", "image"]);
|
|
39200
|
+
function isFormField(el) {
|
|
39201
|
+
if (!FIELD_TAGS.has(el.tag)) return false;
|
|
39202
|
+
if (hasAttr(el, "hidden")) return false;
|
|
39203
|
+
if (el.tag === "input") {
|
|
39204
|
+
const type = (attr(el, "type") ?? "text").toLowerCase();
|
|
39205
|
+
return !NON_LABELABLE_INPUT.has(type);
|
|
39206
|
+
}
|
|
39207
|
+
return true;
|
|
39208
|
+
}
|
|
39209
|
+
function controlLabel(el, doc) {
|
|
39210
|
+
if (attr(el, "aria-labelledby") && ariaLabelledbyText(el, doc) || hasBoundAttr(el, "aria-labelledby") && !attr(el, "aria-labelledby"))
|
|
39211
|
+
return { hasLabel: true, via: "aria-labelledby" };
|
|
39212
|
+
if ((boundAttr(el, "aria-label") ?? "").trim()) return { hasLabel: true, via: "aria-label" };
|
|
39213
|
+
const id = attr(el, "id");
|
|
39214
|
+
if (id) {
|
|
39215
|
+
const lbl = doc.elements.find((e) => e.tag === "label" && attr(e, "for") === id);
|
|
39216
|
+
if (lbl) return { hasLabel: true, via: "for" };
|
|
39217
|
+
}
|
|
39218
|
+
if (ancestors(el).some((a) => a.tag === "label")) return { hasLabel: true, via: "wrapping" };
|
|
39219
|
+
if ((attr(el, "title") ?? "").trim()) return { hasLabel: true, via: "title" };
|
|
39220
|
+
return { hasLabel: false, via: null };
|
|
39221
|
+
}
|
|
39222
|
+
|
|
39223
|
+
// src/rules/images.ts
|
|
39224
|
+
var isHidden = isNameExempt;
|
|
39225
|
+
var named = (el) => !!(boundAttr(el, "aria-label") ?? "").trim() || hasBoundAttr(el, "aria-labelledby");
|
|
39226
|
+
var imgAltMissing = {
|
|
39227
|
+
id: "img-alt-missing",
|
|
39228
|
+
criteria: ["1.1.1"],
|
|
39229
|
+
severity: "bloquant",
|
|
39230
|
+
run(doc) {
|
|
39231
|
+
const out2 = [];
|
|
39232
|
+
for (const el of doc.elements) {
|
|
39233
|
+
const isImg = el.tag === "img" || el.tag === "area" || (attr(el, "role") ?? "") === "img";
|
|
39234
|
+
if (!isImg) continue;
|
|
39235
|
+
if (isHidden(el) && el.tag !== "area") continue;
|
|
39236
|
+
const altLiteral = attr(el, "alt");
|
|
39237
|
+
const whitespaceAlt = altLiteral !== void 0 && altLiteral !== "" && altLiteral.trim() === "";
|
|
39238
|
+
if (hasBoundAttr(el, "alt") && !whitespaceAlt || named(el)) continue;
|
|
39239
|
+
if (hasDynamicSpread(el)) continue;
|
|
39240
|
+
if (el.tag !== "img" && el.tag !== "area" && accessibleName(el, doc).trim() !== "") continue;
|
|
39241
|
+
if ((attr(el, "title") ?? "").trim() !== "") {
|
|
39242
|
+
out2.push({
|
|
39243
|
+
criteriaId: "1.1.1",
|
|
39244
|
+
el,
|
|
39245
|
+
msgId: "img-alt-missing.title-only",
|
|
39246
|
+
params: { tag: el.tag },
|
|
39247
|
+
advisory: true,
|
|
39248
|
+
severity: "mineur"
|
|
39249
|
+
});
|
|
39250
|
+
continue;
|
|
39251
|
+
}
|
|
39252
|
+
out2.push({
|
|
39253
|
+
criteriaId: "1.1.1",
|
|
39254
|
+
el,
|
|
39255
|
+
msgId: "img-alt-missing",
|
|
39256
|
+
params: { tag: el.tag }
|
|
39257
|
+
});
|
|
39258
|
+
}
|
|
39259
|
+
return out2;
|
|
39260
|
+
}
|
|
39261
|
+
};
|
|
39262
|
+
var decorativeAltMisuse = {
|
|
39263
|
+
id: "decorative-alt-misuse",
|
|
39264
|
+
criteria: ["1.1.1"],
|
|
39265
|
+
severity: "majeur",
|
|
39266
|
+
run(doc) {
|
|
39267
|
+
const out2 = [];
|
|
39268
|
+
for (const el of doc.elements) {
|
|
39269
|
+
if (el.tag !== "img") continue;
|
|
39270
|
+
const alt = attr(el, "alt");
|
|
39271
|
+
const role = (attr(el, "role") ?? "").trim();
|
|
39272
|
+
const ariaLabel = (attr(el, "aria-label") ?? "").trim();
|
|
39273
|
+
const title2 = (attr(el, "title") ?? "").trim();
|
|
39274
|
+
if (alt === "" && (ariaLabel || title2)) {
|
|
39275
|
+
out2.push({
|
|
39276
|
+
criteriaId: "1.1.1",
|
|
39277
|
+
el,
|
|
39278
|
+
msgId: "decorative-alt-misuse.empty-but-named"
|
|
39279
|
+
});
|
|
39280
|
+
} else if (["presentation", "none"].includes(role) && alt?.trim()) {
|
|
39281
|
+
out2.push({
|
|
39282
|
+
criteriaId: "1.1.1",
|
|
38664
39283
|
el,
|
|
38665
39284
|
msgId: "decorative-alt-misuse.role-but-alt",
|
|
38666
39285
|
params: { role }
|
|
@@ -40896,831 +41515,231 @@ var navLandmarkUnnamed = {
|
|
|
40896
41515
|
criteria: ["1.3.1"],
|
|
40897
41516
|
severity: "mineur",
|
|
40898
41517
|
scope: "page",
|
|
40899
|
-
run(doc) {
|
|
40900
|
-
const navs = navLandmarks(doc);
|
|
40901
|
-
if (navs.length < 2) return [];
|
|
40902
|
-
return navs.filter((n) => !navIsNamed(n)).map((el) => ({ criteriaId: "1.3.1", el, msgId: "nav-landmark-unnamed" }));
|
|
40903
|
-
}
|
|
40904
|
-
};
|
|
40905
|
-
var navigationRules = [
|
|
40906
|
-
skipLinkTargetMissing,
|
|
40907
|
-
positiveTabindex,
|
|
40908
|
-
missingMainLandmark,
|
|
40909
|
-
multipleMainLandmark,
|
|
40910
|
-
navLandmarkMissing,
|
|
40911
|
-
navLandmarkUnnamed
|
|
40912
|
-
];
|
|
40913
|
-
|
|
40914
|
-
// src/rules/multimedia.ts
|
|
40915
|
-
function mutedStatically(el) {
|
|
40916
|
-
if (!hasAttr(el, "muted")) return false;
|
|
40917
|
-
const v = (attr(el, "muted") ?? "").trim().toLowerCase();
|
|
40918
|
-
return v === "" || v === "muted" || v === "true" || v === "{true}";
|
|
40919
|
-
}
|
|
40920
|
-
var autoplayMedia = {
|
|
40921
|
-
id: "autoplay-media",
|
|
40922
|
-
criteria: ["1.4.2", "2.2.2"],
|
|
40923
|
-
severity: "majeur",
|
|
40924
|
-
run(doc) {
|
|
40925
|
-
const out2 = [];
|
|
40926
|
-
for (const el of doc.elements) {
|
|
40927
|
-
if (el.tag !== "audio" && el.tag !== "video") continue;
|
|
40928
|
-
if (!hasAttr(el, "autoplay")) continue;
|
|
40929
|
-
if (hasAttr(el, "controls")) continue;
|
|
40930
|
-
if (el.tag === "video" && mutedStatically(el)) {
|
|
40931
|
-
out2.push({
|
|
40932
|
-
criteriaId: "2.2.2",
|
|
40933
|
-
el,
|
|
40934
|
-
msgId: "autoplay-media.muted-video",
|
|
40935
|
-
advisory: true
|
|
40936
|
-
});
|
|
40937
|
-
continue;
|
|
40938
|
-
}
|
|
40939
|
-
out2.push({
|
|
40940
|
-
criteriaId: el.tag === "audio" ? "1.4.2" : "2.2.2",
|
|
40941
|
-
el,
|
|
40942
|
-
msgId: "autoplay-media.audible",
|
|
40943
|
-
params: { tag: el.tag }
|
|
40944
|
-
});
|
|
40945
|
-
}
|
|
40946
|
-
return out2;
|
|
40947
|
-
}
|
|
40948
|
-
};
|
|
40949
|
-
var mediaNoTrack = {
|
|
40950
|
-
id: "media-no-track",
|
|
40951
|
-
criteria: ["1.2.2"],
|
|
40952
|
-
severity: "majeur",
|
|
40953
|
-
advisory: true,
|
|
40954
|
-
run(doc) {
|
|
40955
|
-
const out2 = [];
|
|
40956
|
-
for (const el of doc.elements) {
|
|
40957
|
-
if (el.tag !== "video") continue;
|
|
40958
|
-
if (mutedStatically(el)) continue;
|
|
40959
|
-
if (descendants(el).some((d) => d.tag === "track")) continue;
|
|
40960
|
-
out2.push({
|
|
40961
|
-
criteriaId: "1.2.2",
|
|
40962
|
-
el,
|
|
40963
|
-
msgId: "media-no-track"
|
|
40964
|
-
});
|
|
40965
|
-
}
|
|
40966
|
-
return out2;
|
|
40967
|
-
}
|
|
40968
|
-
};
|
|
40969
|
-
var multimediaRules = [autoplayMedia, mediaNoTrack];
|
|
40970
|
-
|
|
40971
|
-
// src/rules/presentation.ts
|
|
40972
|
-
var metaViewportZoomBlock = {
|
|
40973
|
-
id: "meta-viewport-zoom-block",
|
|
40974
|
-
criteria: ["1.4.4"],
|
|
40975
|
-
severity: "majeur",
|
|
40976
|
-
run(doc) {
|
|
40977
|
-
const out2 = [];
|
|
40978
|
-
for (const el of doc.elements) {
|
|
40979
|
-
if (el.tag !== "meta" || (attr(el, "name") ?? "").toLowerCase() !== "viewport") continue;
|
|
40980
|
-
const content = (attr(el, "content") ?? "").toLowerCase();
|
|
40981
|
-
const pairs = /* @__PURE__ */ new Map();
|
|
40982
|
-
for (const part of content.split(/[,;]/)) {
|
|
40983
|
-
const [k, v] = part.split("=").map((s) => s.trim());
|
|
40984
|
-
if (k) pairs.set(k, v ?? "");
|
|
40985
|
-
}
|
|
40986
|
-
const userScalable = pairs.get("user-scalable");
|
|
40987
|
-
const maxScale = pairs.get("maximum-scale");
|
|
40988
|
-
const maxScaleNum = maxScale !== void 0 && maxScale.trim() !== "" ? Number(maxScale) : Number.NaN;
|
|
40989
|
-
const blocked = userScalable === "no" || userScalable === "0" || Number.isFinite(maxScaleNum) && maxScaleNum > 0 && maxScaleNum < 2;
|
|
40990
|
-
if (!blocked) continue;
|
|
40991
|
-
out2.push({
|
|
40992
|
-
criteriaId: "1.4.4",
|
|
40993
|
-
el,
|
|
40994
|
-
msgId: "meta-viewport-zoom-block",
|
|
40995
|
-
params: { blockedBy: userScalable === "no" || userScalable === "0" ? "user-scalable" : "maximum-scale", maxScale: maxScale ?? "" }
|
|
40996
|
-
});
|
|
40997
|
-
}
|
|
40998
|
-
return out2;
|
|
40999
|
-
}
|
|
41000
|
-
};
|
|
41001
|
-
var CSS_CONTENT = /content\s*:\s*(["'])((?:\\.|(?!\1).)*)\1/gi;
|
|
41002
|
-
var INFORMATIVE_WORD = new RegExp("\\p{L}{3,}", "u");
|
|
41003
|
-
var cssGeneratedContentInformative = {
|
|
41004
|
-
id: "css-generated-content-informative",
|
|
41005
|
-
criteria: ["1.3.1"],
|
|
41006
|
-
severity: "mineur",
|
|
41007
|
-
advisory: true,
|
|
41008
|
-
run(doc) {
|
|
41009
|
-
const out2 = [];
|
|
41010
|
-
for (const el of doc.elements) {
|
|
41011
|
-
if (el.tag !== "style") continue;
|
|
41012
|
-
const css = textContent(el);
|
|
41013
|
-
const seen = /* @__PURE__ */ new Set();
|
|
41014
|
-
CSS_CONTENT.lastIndex = 0;
|
|
41015
|
-
let m;
|
|
41016
|
-
while (m = CSS_CONTENT.exec(css)) {
|
|
41017
|
-
const value = m[2] ?? "";
|
|
41018
|
-
if (value.startsWith("\\")) continue;
|
|
41019
|
-
if (!INFORMATIVE_WORD.test(value)) continue;
|
|
41020
|
-
if (seen.has(value)) continue;
|
|
41021
|
-
seen.add(value);
|
|
41022
|
-
out2.push({ criteriaId: "1.3.1", el, msgId: "css-generated-content-informative", params: { text: value.slice(0, 40) }, advisory: true });
|
|
41023
|
-
}
|
|
41024
|
-
}
|
|
41025
|
-
return out2;
|
|
41026
|
-
}
|
|
41027
|
-
};
|
|
41028
|
-
var presentationRules = [metaViewportZoomBlock, cssGeneratedContentInformative];
|
|
41029
|
-
|
|
41030
|
-
// src/color.ts
|
|
41031
|
-
var NAMED = {
|
|
41032
|
-
transparent: { r: 0, g: 0, b: 0, a: 0 },
|
|
41033
|
-
white: { r: 255, g: 255, b: 255, a: 1 },
|
|
41034
|
-
black: { r: 0, g: 0, b: 0, a: 1 },
|
|
41035
|
-
red: { r: 255, g: 0, b: 0, a: 1 },
|
|
41036
|
-
lime: { r: 0, g: 255, b: 0, a: 1 },
|
|
41037
|
-
green: { r: 0, g: 128, b: 0, a: 1 },
|
|
41038
|
-
blue: { r: 0, g: 0, b: 255, a: 1 },
|
|
41039
|
-
yellow: { r: 255, g: 255, b: 0, a: 1 },
|
|
41040
|
-
cyan: { r: 0, g: 255, b: 255, a: 1 },
|
|
41041
|
-
aqua: { r: 0, g: 255, b: 255, a: 1 },
|
|
41042
|
-
magenta: { r: 255, g: 0, b: 255, a: 1 },
|
|
41043
|
-
fuchsia: { r: 255, g: 0, b: 255, a: 1 },
|
|
41044
|
-
silver: { r: 192, g: 192, b: 192, a: 1 },
|
|
41045
|
-
gray: { r: 128, g: 128, b: 128, a: 1 },
|
|
41046
|
-
grey: { r: 128, g: 128, b: 128, a: 1 },
|
|
41047
|
-
maroon: { r: 128, g: 0, b: 0, a: 1 },
|
|
41048
|
-
olive: { r: 128, g: 128, b: 0, a: 1 },
|
|
41049
|
-
purple: { r: 128, g: 0, b: 128, a: 1 },
|
|
41050
|
-
teal: { r: 0, g: 128, b: 128, a: 1 },
|
|
41051
|
-
navy: { r: 0, g: 0, b: 128, a: 1 },
|
|
41052
|
-
orange: { r: 255, g: 165, b: 0, a: 1 }
|
|
41053
|
-
};
|
|
41054
|
-
function hex(part) {
|
|
41055
|
-
return parseInt(part.length === 1 ? part + part : part, 16);
|
|
41056
|
-
}
|
|
41057
|
-
function parseColor(input) {
|
|
41058
|
-
const s = input.trim().toLowerCase();
|
|
41059
|
-
if (!s) return null;
|
|
41060
|
-
if (s in NAMED) return { ...NAMED[s] };
|
|
41061
|
-
if (s.startsWith("#")) {
|
|
41062
|
-
const h = s.slice(1);
|
|
41063
|
-
if (/^[0-9a-f]{3}$/.test(h)) return { r: hex(h[0]), g: hex(h[1]), b: hex(h[2]), a: 1 };
|
|
41064
|
-
if (/^[0-9a-f]{4}$/.test(h)) return { r: hex(h[0]), g: hex(h[1]), b: hex(h[2]), a: hex(h[3]) / 255 };
|
|
41065
|
-
if (/^[0-9a-f]{6}$/.test(h)) return { r: hex(h.slice(0, 2)), g: hex(h.slice(2, 4)), b: hex(h.slice(4, 6)), a: 1 };
|
|
41066
|
-
if (/^[0-9a-f]{8}$/.test(h)) return { r: hex(h.slice(0, 2)), g: hex(h.slice(2, 4)), b: hex(h.slice(4, 6)), a: hex(h.slice(6, 8)) / 255 };
|
|
41067
|
-
return null;
|
|
41068
|
-
}
|
|
41069
|
-
const m = /^rgba?\(([^)]+)\)$/.exec(s);
|
|
41070
|
-
if (m) {
|
|
41071
|
-
const parts2 = m[1].split(/[,/\s]+/).filter(Boolean);
|
|
41072
|
-
if (parts2.length < 3) return null;
|
|
41073
|
-
const chan = (p) => p.endsWith("%") ? Math.round(parseFloat(p) / 100 * 255) : parseFloat(p);
|
|
41074
|
-
const r = chan(parts2[0]);
|
|
41075
|
-
const g = chan(parts2[1]);
|
|
41076
|
-
const b = chan(parts2[2]);
|
|
41077
|
-
const a = parts2[3] !== void 0 ? parts2[3].endsWith("%") ? parseFloat(parts2[3]) / 100 : parseFloat(parts2[3]) : 1;
|
|
41078
|
-
if ([r, g, b, a].some((n) => Number.isNaN(n))) return null;
|
|
41079
|
-
return { r, g, b, a };
|
|
41080
|
-
}
|
|
41081
|
-
return null;
|
|
41082
|
-
}
|
|
41083
|
-
function channelLuminance(c2) {
|
|
41084
|
-
const cs = c2 / 255;
|
|
41085
|
-
return cs <= 0.03928 ? cs / 12.92 : ((cs + 0.055) / 1.055) ** 2.4;
|
|
41086
|
-
}
|
|
41087
|
-
function relativeLuminance({ r, g, b }) {
|
|
41088
|
-
return 0.2126 * channelLuminance(r) + 0.7152 * channelLuminance(g) + 0.0722 * channelLuminance(b);
|
|
41089
|
-
}
|
|
41090
|
-
function contrastRatio(a, b) {
|
|
41091
|
-
const la = relativeLuminance(a);
|
|
41092
|
-
const lb = relativeLuminance(b);
|
|
41093
|
-
const [hi, lo] = la >= lb ? [la, lb] : [lb, la];
|
|
41094
|
-
return (hi + 0.05) / (lo + 0.05);
|
|
41095
|
-
}
|
|
41096
|
-
function parseInlineStyle(style) {
|
|
41097
|
-
const out2 = /* @__PURE__ */ new Map();
|
|
41098
|
-
for (const decl of style.split(";")) {
|
|
41099
|
-
const i2 = decl.indexOf(":");
|
|
41100
|
-
if (i2 === -1) continue;
|
|
41101
|
-
const prop = decl.slice(0, i2).trim().toLowerCase();
|
|
41102
|
-
const val = decl.slice(i2 + 1).trim();
|
|
41103
|
-
if (prop && val) out2.set(prop, val);
|
|
41104
|
-
}
|
|
41105
|
-
return out2;
|
|
41106
|
-
}
|
|
41107
|
-
|
|
41108
|
-
// src/rules/colors.ts
|
|
41109
|
-
var SKIP_TAGS = /* @__PURE__ */ new Set(["script", "style", "head", "title", "meta", "noscript", "link"]);
|
|
41110
|
-
function styleOf(el) {
|
|
41111
|
-
const s = attr(el, "style");
|
|
41112
|
-
return s ? parseInlineStyle(s) : /* @__PURE__ */ new Map();
|
|
41113
|
-
}
|
|
41114
|
-
function colorFromValue(v) {
|
|
41115
|
-
const whole = parseColor(v);
|
|
41116
|
-
if (whole) return whole;
|
|
41117
|
-
for (const tok of v.split(/\s+/)) {
|
|
41118
|
-
const c2 = parseColor(tok);
|
|
41119
|
-
if (c2) return c2;
|
|
41120
|
-
}
|
|
41121
|
-
return null;
|
|
41122
|
-
}
|
|
41123
|
-
function fgOf(el) {
|
|
41124
|
-
for (let p = el; p; p = p.parent) {
|
|
41125
|
-
const v = styleOf(p).get("color");
|
|
41126
|
-
if (v) {
|
|
41127
|
-
const c2 = colorFromValue(v);
|
|
41128
|
-
if (c2) return c2;
|
|
41129
|
-
}
|
|
41130
|
-
}
|
|
41131
|
-
return null;
|
|
41132
|
-
}
|
|
41133
|
-
function bgOf(el) {
|
|
41134
|
-
for (let p = el; p; p = p.parent) {
|
|
41135
|
-
const st = styleOf(p);
|
|
41136
|
-
const v = st.get("background-color") ?? st.get("background");
|
|
41137
|
-
if (v) {
|
|
41138
|
-
const c2 = colorFromValue(v);
|
|
41139
|
-
if (c2) return c2;
|
|
41140
|
-
}
|
|
41141
|
-
}
|
|
41142
|
-
return null;
|
|
41143
|
-
}
|
|
41144
|
-
function fontPx(el) {
|
|
41145
|
-
for (let p = el; p; p = p.parent) {
|
|
41146
|
-
const v = styleOf(p).get("font-size");
|
|
41147
|
-
if (!v) continue;
|
|
41148
|
-
const m = /^([\d.]+)(px|pt|rem|em|%)?$/.exec(v.trim());
|
|
41149
|
-
if (!m) return "unknown";
|
|
41150
|
-
const n = parseFloat(m[1]);
|
|
41151
|
-
if (Number.isNaN(n)) return "unknown";
|
|
41152
|
-
const unit = m[2] ?? "px";
|
|
41153
|
-
if (unit === "pt") return n * 4 / 3;
|
|
41154
|
-
if (unit === "rem" || unit === "em") return n * 16;
|
|
41155
|
-
if (unit === "%") return n / 100 * 16;
|
|
41156
|
-
return n;
|
|
41157
|
-
}
|
|
41158
|
-
return null;
|
|
41159
|
-
}
|
|
41160
|
-
function isBold(el) {
|
|
41161
|
-
for (let p = el; p; p = p.parent) {
|
|
41162
|
-
const v = styleOf(p).get("font-weight");
|
|
41163
|
-
if (v) return v === "bold" || Number(v) >= 700;
|
|
41164
|
-
}
|
|
41165
|
-
return false;
|
|
41166
|
-
}
|
|
41167
|
-
function isLarge(el) {
|
|
41168
|
-
const px = fontPx(el);
|
|
41169
|
-
if (px === null) return false;
|
|
41170
|
-
if (px === "unknown") return true;
|
|
41171
|
-
return px >= 24 || px >= 18.66 && isBold(el);
|
|
41172
|
-
}
|
|
41173
|
-
function hasDirectText(el) {
|
|
41174
|
-
return el.children.some((c2) => c2.type === "text" && c2.data.trim() !== "");
|
|
41175
|
-
}
|
|
41176
|
-
var contrastLiteral = {
|
|
41177
|
-
id: "contrast-literal",
|
|
41178
|
-
criteria: ["1.4.3"],
|
|
41179
|
-
severity: "majeur",
|
|
41180
|
-
run(doc) {
|
|
41181
|
-
const out2 = [];
|
|
41182
|
-
for (const el of doc.elements) {
|
|
41183
|
-
if (SKIP_TAGS.has(el.tag) || !hasDirectText(el)) continue;
|
|
41184
|
-
const fg = fgOf(el);
|
|
41185
|
-
const bg = bgOf(el);
|
|
41186
|
-
if (!fg || !bg || fg.a < 1 || bg.a < 1) continue;
|
|
41187
|
-
const ratio = contrastRatio(fg, bg);
|
|
41188
|
-
const large = isLarge(el);
|
|
41189
|
-
const min = large ? 3 : 4.5;
|
|
41190
|
-
if (ratio >= min) continue;
|
|
41191
|
-
out2.push({
|
|
41192
|
-
criteriaId: "1.4.3",
|
|
41193
|
-
el,
|
|
41194
|
-
msgId: "contrast-literal",
|
|
41195
|
-
params: { ratio: ratio.toFixed(2), min, textSize: large ? "large" : "normal" }
|
|
41196
|
-
});
|
|
41197
|
-
}
|
|
41198
|
-
return out2;
|
|
41199
|
-
}
|
|
41200
|
-
};
|
|
41201
|
-
var colorsRules = [contrastLiteral];
|
|
41202
|
-
|
|
41203
|
-
// src/rules/rendered.ts
|
|
41204
|
-
import { readFileSync as readFileSync15 } from "fs";
|
|
41205
|
-
|
|
41206
|
-
// src/pixel.ts
|
|
41207
|
-
import { deflateSync, inflateSync } from "zlib";
|
|
41208
|
-
var SIGNATURE = [137, 80, 78, 71, 13, 10, 26, 10];
|
|
41209
|
-
function paeth(a, b, c2) {
|
|
41210
|
-
const p = a + b - c2;
|
|
41211
|
-
const pa = Math.abs(p - a);
|
|
41212
|
-
const pb = Math.abs(p - b);
|
|
41213
|
-
const pc = Math.abs(p - c2);
|
|
41214
|
-
if (pa <= pb && pa <= pc) return a;
|
|
41215
|
-
return pb <= pc ? b : c2;
|
|
41216
|
-
}
|
|
41217
|
-
function decodePng(buf) {
|
|
41218
|
-
if (buf.length < 8 + 25) return null;
|
|
41219
|
-
for (let i2 = 0; i2 < SIGNATURE.length; i2++) if (buf[i2] !== SIGNATURE[i2]) return null;
|
|
41220
|
-
let width = 0;
|
|
41221
|
-
let height = 0;
|
|
41222
|
-
let bitDepth = 0;
|
|
41223
|
-
let colorType = 0;
|
|
41224
|
-
let interlace = 0;
|
|
41225
|
-
const idat = [];
|
|
41226
|
-
let off = 8;
|
|
41227
|
-
while (off + 8 <= buf.length) {
|
|
41228
|
-
const len = buf.readUInt32BE(off);
|
|
41229
|
-
const type = buf.toString("ascii", off + 4, off + 8);
|
|
41230
|
-
const dataStart = off + 8;
|
|
41231
|
-
const dataEnd = dataStart + len;
|
|
41232
|
-
if (dataEnd + 4 > buf.length) return null;
|
|
41233
|
-
if (type === "IHDR") {
|
|
41234
|
-
if (len < 13) return null;
|
|
41235
|
-
width = buf.readUInt32BE(dataStart);
|
|
41236
|
-
height = buf.readUInt32BE(dataStart + 4);
|
|
41237
|
-
bitDepth = buf[dataStart + 8] ?? 0;
|
|
41238
|
-
colorType = buf[dataStart + 9] ?? 0;
|
|
41239
|
-
interlace = buf[dataStart + 12] ?? 0;
|
|
41240
|
-
} else if (type === "IDAT") {
|
|
41241
|
-
idat.push(buf.subarray(dataStart, dataEnd));
|
|
41242
|
-
} else if (type === "IEND") {
|
|
41243
|
-
break;
|
|
41244
|
-
}
|
|
41245
|
-
off = dataEnd + 4;
|
|
41246
|
-
}
|
|
41247
|
-
if (!width || !height || !idat.length) return null;
|
|
41248
|
-
if (bitDepth !== 8) return null;
|
|
41249
|
-
if (colorType !== 2 && colorType !== 6) return null;
|
|
41250
|
-
if (interlace !== 0) return null;
|
|
41251
|
-
const channels = colorType === 6 ? 4 : 3;
|
|
41252
|
-
let raw;
|
|
41253
|
-
try {
|
|
41254
|
-
raw = inflateSync(Buffer.concat(idat));
|
|
41255
|
-
} catch {
|
|
41256
|
-
return null;
|
|
41257
|
-
}
|
|
41258
|
-
const stride = width * channels;
|
|
41259
|
-
if (raw.length < height * (stride + 1)) return null;
|
|
41260
|
-
const out2 = Buffer.alloc(height * stride);
|
|
41261
|
-
for (let y = 0; y < height; y++) {
|
|
41262
|
-
const filter = raw[y * (stride + 1)] ?? 0;
|
|
41263
|
-
const lineIn = y * (stride + 1) + 1;
|
|
41264
|
-
const lineOut = y * stride;
|
|
41265
|
-
const prevOut = lineOut - stride;
|
|
41266
|
-
for (let i2 = 0; i2 < stride; i2++) {
|
|
41267
|
-
const x = raw[lineIn + i2] ?? 0;
|
|
41268
|
-
const a = i2 >= channels ? out2[lineOut + i2 - channels] ?? 0 : 0;
|
|
41269
|
-
const b = y > 0 ? out2[prevOut + i2] ?? 0 : 0;
|
|
41270
|
-
const c2 = y > 0 && i2 >= channels ? out2[prevOut + i2 - channels] ?? 0 : 0;
|
|
41271
|
-
let v;
|
|
41272
|
-
switch (filter) {
|
|
41273
|
-
case 0:
|
|
41274
|
-
v = x;
|
|
41275
|
-
break;
|
|
41276
|
-
case 1:
|
|
41277
|
-
v = x + a;
|
|
41278
|
-
break;
|
|
41279
|
-
case 2:
|
|
41280
|
-
v = x + b;
|
|
41281
|
-
break;
|
|
41282
|
-
case 3:
|
|
41283
|
-
v = x + (a + b >> 1);
|
|
41284
|
-
break;
|
|
41285
|
-
case 4:
|
|
41286
|
-
v = x + paeth(a, b, c2);
|
|
41287
|
-
break;
|
|
41288
|
-
default:
|
|
41289
|
-
return null;
|
|
41290
|
-
}
|
|
41291
|
-
out2[lineOut + i2] = v & 255;
|
|
41292
|
-
}
|
|
41293
|
-
}
|
|
41294
|
-
return {
|
|
41295
|
-
width,
|
|
41296
|
-
height,
|
|
41297
|
-
at(x, y) {
|
|
41298
|
-
if (x < 0 || y < 0 || x >= width || y >= height) return void 0;
|
|
41299
|
-
const p = y * stride + x * channels;
|
|
41300
|
-
return {
|
|
41301
|
-
r: out2[p] ?? 0,
|
|
41302
|
-
g: out2[p + 1] ?? 0,
|
|
41303
|
-
b: out2[p + 2] ?? 0,
|
|
41304
|
-
a: channels === 4 ? (out2[p + 3] ?? 255) / 255 : 1
|
|
41305
|
-
};
|
|
41306
|
-
}
|
|
41307
|
-
};
|
|
41308
|
-
}
|
|
41309
|
-
function clamp(img, r) {
|
|
41310
|
-
const x = Math.max(0, Math.round(r.x));
|
|
41311
|
-
const y = Math.max(0, Math.round(r.y));
|
|
41312
|
-
const x2 = Math.min(img.width, Math.round(r.x + r.w));
|
|
41313
|
-
const y2 = Math.min(img.height, Math.round(r.y + r.h));
|
|
41314
|
-
if (x2 <= x || y2 <= y) return null;
|
|
41315
|
-
return { x, y, w: x2 - x, h: y2 - y };
|
|
41316
|
-
}
|
|
41317
|
-
var MAX_SAMPLES = 4096;
|
|
41318
|
-
var DOMINANCE = 0.6;
|
|
41319
|
-
var BUCKET = 16;
|
|
41320
|
-
function dominantBackground(img, rect) {
|
|
41321
|
-
const r = clamp(img, rect);
|
|
41322
|
-
if (!r) return null;
|
|
41323
|
-
const step = Math.max(1, Math.floor(Math.sqrt(r.w * r.h / MAX_SAMPLES)));
|
|
41324
|
-
const counts = /* @__PURE__ */ new Map();
|
|
41325
|
-
let total = 0;
|
|
41326
|
-
for (let y = r.y; y < r.y + r.h; y += step) {
|
|
41327
|
-
for (let x = r.x; x < r.x + r.w; x += step) {
|
|
41328
|
-
const p = img.at(x, y);
|
|
41329
|
-
if (!p) continue;
|
|
41330
|
-
const key = Math.round(p.r / BUCKET) << 16 | Math.round(p.g / BUCKET) << 8 | Math.round(p.b / BUCKET);
|
|
41331
|
-
const cur = counts.get(key) ?? { n: 0, r: 0, g: 0, b: 0 };
|
|
41332
|
-
cur.n++;
|
|
41333
|
-
cur.r += p.r;
|
|
41334
|
-
cur.g += p.g;
|
|
41335
|
-
cur.b += p.b;
|
|
41336
|
-
counts.set(key, cur);
|
|
41337
|
-
total++;
|
|
41338
|
-
}
|
|
41339
|
-
}
|
|
41340
|
-
if (!total) return null;
|
|
41341
|
-
let best = null;
|
|
41342
|
-
for (const c2 of counts.values()) if (!best || c2.n > best.n) best = c2;
|
|
41343
|
-
if (!best || best.n / total < DOMINANCE) return null;
|
|
41344
|
-
return { r: Math.round(best.r / best.n), g: Math.round(best.g / best.n), b: Math.round(best.b / best.n), a: 1 };
|
|
41345
|
-
}
|
|
41346
|
-
var CRC_TABLE = /* @__PURE__ */ (() => {
|
|
41347
|
-
const t2 = new Uint32Array(256);
|
|
41348
|
-
for (let n = 0; n < 256; n++) {
|
|
41349
|
-
let c2 = n;
|
|
41350
|
-
for (let k = 0; k < 8; k++) c2 = c2 & 1 ? 3988292384 ^ c2 >>> 1 : c2 >>> 1;
|
|
41351
|
-
t2[n] = c2 >>> 0;
|
|
41352
|
-
}
|
|
41353
|
-
return t2;
|
|
41354
|
-
})();
|
|
41355
|
-
function crc32(buf) {
|
|
41356
|
-
let c2 = 4294967295;
|
|
41357
|
-
for (let i2 = 0; i2 < buf.length; i2++) c2 = CRC_TABLE[(c2 ^ buf[i2]) & 255] ^ c2 >>> 8;
|
|
41358
|
-
return (c2 ^ 4294967295) >>> 0;
|
|
41359
|
-
}
|
|
41360
|
-
function chunk(type, data2) {
|
|
41361
|
-
const len = Buffer.alloc(4);
|
|
41362
|
-
len.writeUInt32BE(data2.length);
|
|
41363
|
-
const body3 = Buffer.concat([Buffer.from(type, "latin1"), data2]);
|
|
41364
|
-
const crc = Buffer.alloc(4);
|
|
41365
|
-
crc.writeUInt32BE(crc32(body3));
|
|
41366
|
-
return Buffer.concat([len, body3, crc]);
|
|
41367
|
-
}
|
|
41368
|
-
function encodePng(r) {
|
|
41369
|
-
const expected = r.width * r.height * 4;
|
|
41370
|
-
if (r.width <= 0 || r.height <= 0) throw new Error(`encodePng: refusing a ${r.width}\xD7${r.height} raster`);
|
|
41371
|
-
if (r.data.length !== expected) {
|
|
41372
|
-
throw new Error(`encodePng: ${r.width}\xD7${r.height} needs ${expected} bytes of RGBA, got ${r.data.length}`);
|
|
41373
|
-
}
|
|
41374
|
-
const stride = r.width * 4;
|
|
41375
|
-
const raw = Buffer.alloc(r.height * (stride + 1));
|
|
41376
|
-
for (let y = 0; y < r.height; y++) {
|
|
41377
|
-
raw[y * (stride + 1)] = 0;
|
|
41378
|
-
r.data.copy(raw, y * (stride + 1) + 1, y * stride, (y + 1) * stride);
|
|
41379
|
-
}
|
|
41380
|
-
const ihdr = Buffer.alloc(13);
|
|
41381
|
-
ihdr.writeUInt32BE(r.width, 0);
|
|
41382
|
-
ihdr.writeUInt32BE(r.height, 4);
|
|
41383
|
-
ihdr[8] = 8;
|
|
41384
|
-
ihdr[9] = 6;
|
|
41385
|
-
return Buffer.concat([Buffer.from(SIGNATURE), chunk("IHDR", ihdr), chunk("IDAT", deflateSync(raw, { level: 9 })), chunk("IEND", Buffer.alloc(0))]);
|
|
41386
|
-
}
|
|
41387
|
-
function cropImage(img, rect) {
|
|
41388
|
-
const r = clamp(img, rect);
|
|
41389
|
-
if (!r) return null;
|
|
41390
|
-
const data2 = Buffer.alloc(r.w * r.h * 4);
|
|
41391
|
-
let i2 = 0;
|
|
41392
|
-
for (let y = r.y; y < r.y + r.h; y++) {
|
|
41393
|
-
for (let x = r.x; x < r.x + r.w; x++) {
|
|
41394
|
-
const p = img.at(x, y);
|
|
41395
|
-
data2[i2] = p?.r ?? 0;
|
|
41396
|
-
data2[i2 + 1] = p?.g ?? 0;
|
|
41397
|
-
data2[i2 + 2] = p?.b ?? 0;
|
|
41398
|
-
data2[i2 + 3] = Math.round((p?.a ?? 1) * 255);
|
|
41399
|
-
i2 += 4;
|
|
41400
|
-
}
|
|
41401
|
-
}
|
|
41402
|
-
return { raster: { width: r.w, height: r.h, data: data2 }, rect: r };
|
|
41403
|
-
}
|
|
41404
|
-
function blend(raster, x, y, c2) {
|
|
41405
|
-
if (x < 0 || y < 0 || x >= raster.width || y >= raster.height) return;
|
|
41406
|
-
const p = (y * raster.width + x) * 4;
|
|
41407
|
-
const a = Math.min(1, Math.max(0, c2.a));
|
|
41408
|
-
if (a >= 1) {
|
|
41409
|
-
raster.data[p] = c2.r;
|
|
41410
|
-
raster.data[p + 1] = c2.g;
|
|
41411
|
-
raster.data[p + 2] = c2.b;
|
|
41412
|
-
raster.data[p + 3] = 255;
|
|
41413
|
-
return;
|
|
41414
|
-
}
|
|
41415
|
-
const inv = 1 - a;
|
|
41416
|
-
raster.data[p] = Math.round(c2.r * a + raster.data[p] * inv);
|
|
41417
|
-
raster.data[p + 1] = Math.round(c2.g * a + raster.data[p + 1] * inv);
|
|
41418
|
-
raster.data[p + 2] = Math.round(c2.b * a + raster.data[p + 2] * inv);
|
|
41419
|
-
raster.data[p + 3] = Math.max(raster.data[p + 3], Math.round(a * 255));
|
|
41420
|
-
}
|
|
41421
|
-
function fillRect(raster, rect, color) {
|
|
41422
|
-
const x0 = Math.max(0, Math.round(rect.x));
|
|
41423
|
-
const y0 = Math.max(0, Math.round(rect.y));
|
|
41424
|
-
const x1 = Math.min(raster.width, Math.round(rect.x + rect.w));
|
|
41425
|
-
const y1 = Math.min(raster.height, Math.round(rect.y + rect.h));
|
|
41426
|
-
for (let y = y0; y < y1; y++) for (let x = x0; x < x1; x++) blend(raster, x, y, color);
|
|
41427
|
-
}
|
|
41428
|
-
function strokeRect(raster, rect, color, width, dash) {
|
|
41429
|
-
const t2 = Math.max(1, Math.round(width));
|
|
41430
|
-
const x0 = Math.round(rect.x);
|
|
41431
|
-
const y0 = Math.round(rect.y);
|
|
41432
|
-
const x1 = Math.round(rect.x + rect.w);
|
|
41433
|
-
const y1 = Math.round(rect.y + rect.h);
|
|
41434
|
-
if (x1 <= x0 || y1 <= y0) return;
|
|
41435
|
-
const on = (coord) => {
|
|
41436
|
-
if (!dash || dash <= 1) return true;
|
|
41437
|
-
const half = dash / 2;
|
|
41438
|
-
return Math.floor(coord / half) % 2 === 0;
|
|
41439
|
-
};
|
|
41440
|
-
for (let y = y0; y < y1; y++) {
|
|
41441
|
-
for (let x = x0; x < x1; x++) {
|
|
41442
|
-
const top = y < y0 + t2;
|
|
41443
|
-
const bottom = y >= y1 - t2;
|
|
41444
|
-
const left = x < x0 + t2;
|
|
41445
|
-
const right = x >= x1 - t2;
|
|
41446
|
-
if (!top && !bottom && !left && !right) continue;
|
|
41447
|
-
if (!on(top || bottom ? x : y)) continue;
|
|
41448
|
-
blend(raster, x, y, color);
|
|
41449
|
-
}
|
|
41450
|
-
}
|
|
41451
|
-
}
|
|
41452
|
-
|
|
41453
|
-
// src/rules/rendered.ts
|
|
41454
|
-
var SKIP_TAGS2 = /* @__PURE__ */ new Set(["script", "style", "head", "title", "meta", "noscript", "link", "html", "br", "wbr"]);
|
|
41455
|
-
function styleAt(doc, i2) {
|
|
41456
|
-
return doc.signals?.styles?.get(i2);
|
|
41457
|
-
}
|
|
41458
|
-
function hasDirectText2(el) {
|
|
41459
|
-
return el.children.some((c2) => c2.type === "text" && c2.data.trim() !== "");
|
|
41460
|
-
}
|
|
41461
|
-
function invisible(css) {
|
|
41462
|
-
if (css.display === "none" || css.visibility === "hidden") return true;
|
|
41463
|
-
const op = Number.parseFloat(css.opacity ?? "1");
|
|
41464
|
-
return Number.isFinite(op) && op === 0;
|
|
41465
|
-
}
|
|
41466
|
-
function backdropOf(doc, index, el) {
|
|
41467
|
-
for (let p = el; p; p = p.parent) {
|
|
41468
|
-
const i2 = index.get(p);
|
|
41469
|
-
if (i2 === void 0) continue;
|
|
41470
|
-
const css = styleAt(doc, i2)?.css;
|
|
41471
|
-
if (!css) continue;
|
|
41472
|
-
const img = css.backgroundImage;
|
|
41473
|
-
if (img && img !== "none") return void 0;
|
|
41474
|
-
const c2 = parseColor(css.backgroundColor ?? "");
|
|
41475
|
-
if (c2 && c2.a >= 1) return { color: c2, fromImage: false };
|
|
41518
|
+
run(doc) {
|
|
41519
|
+
const navs = navLandmarks(doc);
|
|
41520
|
+
if (navs.length < 2) return [];
|
|
41521
|
+
return navs.filter((n) => !navIsNamed(n)).map((el) => ({ criteriaId: "1.3.1", el, msgId: "nav-landmark-unnamed" }));
|
|
41476
41522
|
}
|
|
41477
|
-
|
|
41478
|
-
|
|
41479
|
-
|
|
41480
|
-
|
|
41481
|
-
|
|
41482
|
-
|
|
41483
|
-
|
|
41484
|
-
|
|
41485
|
-
|
|
41486
|
-
|
|
41487
|
-
|
|
41488
|
-
|
|
41489
|
-
return
|
|
41523
|
+
};
|
|
41524
|
+
var navigationRules = [
|
|
41525
|
+
skipLinkTargetMissing,
|
|
41526
|
+
positiveTabindex,
|
|
41527
|
+
missingMainLandmark,
|
|
41528
|
+
multipleMainLandmark,
|
|
41529
|
+
navLandmarkMissing,
|
|
41530
|
+
navLandmarkUnnamed
|
|
41531
|
+
];
|
|
41532
|
+
|
|
41533
|
+
// src/rules/multimedia.ts
|
|
41534
|
+
function mutedStatically(el) {
|
|
41535
|
+
if (!hasAttr(el, "muted")) return false;
|
|
41536
|
+
const v = (attr(el, "muted") ?? "").trim().toLowerCase();
|
|
41537
|
+
return v === "" || v === "muted" || v === "true" || v === "{true}";
|
|
41490
41538
|
}
|
|
41491
|
-
var
|
|
41492
|
-
id: "
|
|
41493
|
-
criteria: ["1.4.
|
|
41539
|
+
var autoplayMedia = {
|
|
41540
|
+
id: "autoplay-media",
|
|
41541
|
+
criteria: ["1.4.2", "2.2.2"],
|
|
41494
41542
|
severity: "majeur",
|
|
41495
41543
|
run(doc) {
|
|
41496
|
-
if (!doc.signals?.styles) return [];
|
|
41497
|
-
const index = elementIndex(doc);
|
|
41498
41544
|
const out2 = [];
|
|
41499
|
-
for (const
|
|
41500
|
-
if (
|
|
41501
|
-
|
|
41502
|
-
if (
|
|
41503
|
-
|
|
41504
|
-
|
|
41505
|
-
|
|
41506
|
-
|
|
41507
|
-
|
|
41508
|
-
|
|
41509
|
-
|
|
41510
|
-
|
|
41545
|
+
for (const el of doc.elements) {
|
|
41546
|
+
if (el.tag !== "audio" && el.tag !== "video") continue;
|
|
41547
|
+
if (!hasAttr(el, "autoplay")) continue;
|
|
41548
|
+
if (hasAttr(el, "controls")) continue;
|
|
41549
|
+
if (el.tag === "video" && mutedStatically(el)) {
|
|
41550
|
+
out2.push({
|
|
41551
|
+
criteriaId: "2.2.2",
|
|
41552
|
+
el,
|
|
41553
|
+
msgId: "autoplay-media.muted-video",
|
|
41554
|
+
advisory: true
|
|
41555
|
+
});
|
|
41556
|
+
continue;
|
|
41557
|
+
}
|
|
41511
41558
|
out2.push({
|
|
41512
|
-
criteriaId: "1.4.
|
|
41559
|
+
criteriaId: el.tag === "audio" ? "1.4.2" : "2.2.2",
|
|
41513
41560
|
el,
|
|
41514
|
-
msgId: "
|
|
41515
|
-
params: {
|
|
41561
|
+
msgId: "autoplay-media.audible",
|
|
41562
|
+
params: { tag: el.tag }
|
|
41516
41563
|
});
|
|
41517
41564
|
}
|
|
41518
41565
|
return out2;
|
|
41519
41566
|
}
|
|
41520
41567
|
};
|
|
41521
|
-
var
|
|
41522
|
-
id: "
|
|
41523
|
-
criteria: ["1.
|
|
41568
|
+
var mediaNoTrack = {
|
|
41569
|
+
id: "media-no-track",
|
|
41570
|
+
criteria: ["1.2.2"],
|
|
41524
41571
|
severity: "majeur",
|
|
41572
|
+
advisory: true,
|
|
41525
41573
|
run(doc) {
|
|
41526
|
-
const shot = doc.signals?.screenshot;
|
|
41527
|
-
const boxes = doc.signals?.boxes;
|
|
41528
|
-
if (!shot || !boxes || !doc.signals?.styles) return [];
|
|
41529
|
-
let img = null;
|
|
41530
|
-
try {
|
|
41531
|
-
img = decodePng(readFileSync15(shot));
|
|
41532
|
-
} catch {
|
|
41533
|
-
return [];
|
|
41534
|
-
}
|
|
41535
|
-
if (!img) return [];
|
|
41536
|
-
const index = elementIndex(doc);
|
|
41537
41574
|
const out2 = [];
|
|
41538
|
-
for (const
|
|
41539
|
-
if (
|
|
41540
|
-
|
|
41541
|
-
if (
|
|
41542
|
-
if (backdropOf(doc, index, el)) continue;
|
|
41543
|
-
const fg = parseColor(css.color ?? "");
|
|
41544
|
-
if (!fg || fg.a < 1) continue;
|
|
41545
|
-
const box = boxes.get(i2);
|
|
41546
|
-
if (!box || box.w < 4 || box.h < 4) continue;
|
|
41547
|
-
const bg = dominantBackground(img, box);
|
|
41548
|
-
if (!bg) continue;
|
|
41549
|
-
const ratio = contrastRatio(fg, bg);
|
|
41550
|
-
const large = isLargeText(css);
|
|
41551
|
-
const min = large ? 3 : 4.5;
|
|
41552
|
-
if (ratio >= min) continue;
|
|
41575
|
+
for (const el of doc.elements) {
|
|
41576
|
+
if (el.tag !== "video") continue;
|
|
41577
|
+
if (mutedStatically(el)) continue;
|
|
41578
|
+
if (descendants(el).some((d) => d.tag === "track")) continue;
|
|
41553
41579
|
out2.push({
|
|
41554
|
-
criteriaId: "1.
|
|
41580
|
+
criteriaId: "1.2.2",
|
|
41555
41581
|
el,
|
|
41556
|
-
msgId: "
|
|
41557
|
-
params: { ratio: ratio.toFixed(2), min, textSize: large ? "large" : "normal" }
|
|
41582
|
+
msgId: "media-no-track"
|
|
41558
41583
|
});
|
|
41559
41584
|
}
|
|
41560
41585
|
return out2;
|
|
41561
41586
|
}
|
|
41562
41587
|
};
|
|
41563
|
-
|
|
41564
|
-
|
|
41565
|
-
|
|
41566
|
-
|
|
41567
|
-
|
|
41568
|
-
|
|
41569
|
-
const w = Number.parseInt(css.fontWeight ?? "400", 10);
|
|
41570
|
-
const pw = Number.parseInt(parentCss?.fontWeight ?? "400", 10);
|
|
41571
|
-
if (Number.isFinite(w) && Number.isFinite(pw) && w >= pw + 200) return true;
|
|
41572
|
-
return false;
|
|
41573
|
-
}
|
|
41574
|
-
var TEXT_BLOCK = /* @__PURE__ */ new Set(["p", "li", "dd", "dt", "td", "th", "blockquote", "figcaption", "caption"]);
|
|
41575
|
-
var renderedLinkColourOnly = {
|
|
41576
|
-
id: "rendered-link-colour-only",
|
|
41577
|
-
criteria: ["1.4.1"],
|
|
41588
|
+
var multimediaRules = [autoplayMedia, mediaNoTrack];
|
|
41589
|
+
|
|
41590
|
+
// src/rules/presentation.ts
|
|
41591
|
+
var metaViewportZoomBlock = {
|
|
41592
|
+
id: "meta-viewport-zoom-block",
|
|
41593
|
+
criteria: ["1.4.4"],
|
|
41578
41594
|
severity: "majeur",
|
|
41579
41595
|
run(doc) {
|
|
41580
|
-
if (!doc.signals?.styles) return [];
|
|
41581
|
-
const index = elementIndex(doc);
|
|
41582
41596
|
const out2 = [];
|
|
41583
|
-
for (const
|
|
41584
|
-
if (el.tag !== "
|
|
41585
|
-
const
|
|
41586
|
-
|
|
41587
|
-
const
|
|
41588
|
-
|
|
41589
|
-
|
|
41590
|
-
|
|
41591
|
-
|
|
41592
|
-
const
|
|
41593
|
-
const
|
|
41594
|
-
|
|
41595
|
-
|
|
41596
|
-
if (distinct <= 1.02) continue;
|
|
41597
|
+
for (const el of doc.elements) {
|
|
41598
|
+
if (el.tag !== "meta" || (attr(el, "name") ?? "").toLowerCase() !== "viewport") continue;
|
|
41599
|
+
const content = (attr(el, "content") ?? "").toLowerCase();
|
|
41600
|
+
const pairs = /* @__PURE__ */ new Map();
|
|
41601
|
+
for (const part of content.split(/[,;]/)) {
|
|
41602
|
+
const [k, v] = part.split("=").map((s) => s.trim());
|
|
41603
|
+
if (k) pairs.set(k, v ?? "");
|
|
41604
|
+
}
|
|
41605
|
+
const userScalable = pairs.get("user-scalable");
|
|
41606
|
+
const maxScale = pairs.get("maximum-scale");
|
|
41607
|
+
const maxScaleNum = maxScale !== void 0 && maxScale.trim() !== "" ? Number(maxScale) : Number.NaN;
|
|
41608
|
+
const blocked = userScalable === "no" || userScalable === "0" || Number.isFinite(maxScaleNum) && maxScaleNum > 0 && maxScaleNum < 2;
|
|
41609
|
+
if (!blocked) continue;
|
|
41597
41610
|
out2.push({
|
|
41598
|
-
criteriaId: "1.4.
|
|
41611
|
+
criteriaId: "1.4.4",
|
|
41599
41612
|
el,
|
|
41600
|
-
msgId: "
|
|
41601
|
-
params: {
|
|
41613
|
+
msgId: "meta-viewport-zoom-block",
|
|
41614
|
+
params: { blockedBy: userScalable === "no" || userScalable === "0" ? "user-scalable" : "maximum-scale", maxScale: maxScale ?? "" }
|
|
41602
41615
|
});
|
|
41603
41616
|
}
|
|
41604
41617
|
return out2;
|
|
41605
41618
|
}
|
|
41606
41619
|
};
|
|
41607
|
-
var
|
|
41608
|
-
var
|
|
41609
|
-
|
|
41610
|
-
|
|
41611
|
-
|
|
41612
|
-
|
|
41613
|
-
|
|
41614
|
-
if (!style || style === "none" || style === "hidden" || !(width > 0)) continue;
|
|
41615
|
-
const c2 = parseColor(css[`border${s}Color`] ?? "");
|
|
41616
|
-
if (c2 && c2.a >= 1) out2.push(c2);
|
|
41617
|
-
}
|
|
41618
|
-
return out2;
|
|
41619
|
-
}
|
|
41620
|
-
var renderedNonTextContrast = {
|
|
41621
|
-
id: "rendered-nontext-contrast",
|
|
41622
|
-
criteria: ["1.4.11"],
|
|
41623
|
-
severity: "majeur",
|
|
41620
|
+
var CSS_CONTENT = /content\s*:\s*(["'])((?:\\.|(?!\1).)*)\1/gi;
|
|
41621
|
+
var INFORMATIVE_WORD = new RegExp("\\p{L}{3,}", "u");
|
|
41622
|
+
var cssGeneratedContentInformative = {
|
|
41623
|
+
id: "css-generated-content-informative",
|
|
41624
|
+
criteria: ["1.3.1"],
|
|
41625
|
+
severity: "mineur",
|
|
41626
|
+
advisory: true,
|
|
41624
41627
|
run(doc) {
|
|
41625
|
-
if (!doc.signals?.styles) return [];
|
|
41626
|
-
const index = elementIndex(doc);
|
|
41627
41628
|
const out2 = [];
|
|
41628
|
-
for (const
|
|
41629
|
-
if (
|
|
41630
|
-
const
|
|
41631
|
-
|
|
41632
|
-
|
|
41633
|
-
|
|
41634
|
-
|
|
41635
|
-
|
|
41636
|
-
|
|
41637
|
-
|
|
41638
|
-
|
|
41639
|
-
|
|
41640
|
-
|
|
41641
|
-
|
|
41642
|
-
if (borders.some((b) => contrastRatio(b, surrounding.color) >= 3 || contrastRatio(b, fill) >= 3)) continue;
|
|
41643
|
-
out2.push({
|
|
41644
|
-
criteriaId: "1.4.11",
|
|
41645
|
-
el,
|
|
41646
|
-
msgId: "rendered-nontext-contrast",
|
|
41647
|
-
params: { ratio: contrastRatio(fill, surrounding.color).toFixed(2), control: el.tag }
|
|
41648
|
-
});
|
|
41629
|
+
for (const el of doc.elements) {
|
|
41630
|
+
if (el.tag !== "style") continue;
|
|
41631
|
+
const css = textContent(el);
|
|
41632
|
+
const seen = /* @__PURE__ */ new Set();
|
|
41633
|
+
CSS_CONTENT.lastIndex = 0;
|
|
41634
|
+
let m;
|
|
41635
|
+
while (m = CSS_CONTENT.exec(css)) {
|
|
41636
|
+
const value = m[2] ?? "";
|
|
41637
|
+
if (value.startsWith("\\")) continue;
|
|
41638
|
+
if (!INFORMATIVE_WORD.test(value)) continue;
|
|
41639
|
+
if (seen.has(value)) continue;
|
|
41640
|
+
seen.add(value);
|
|
41641
|
+
out2.push({ criteriaId: "1.3.1", el, msgId: "css-generated-content-informative", params: { text: value.slice(0, 40) }, advisory: true });
|
|
41642
|
+
}
|
|
41649
41643
|
}
|
|
41650
41644
|
return out2;
|
|
41651
41645
|
}
|
|
41652
41646
|
};
|
|
41653
|
-
var
|
|
41654
|
-
|
|
41655
|
-
|
|
41656
|
-
|
|
41657
|
-
|
|
41658
|
-
|
|
41647
|
+
var presentationRules = [metaViewportZoomBlock, cssGeneratedContentInformative];
|
|
41648
|
+
|
|
41649
|
+
// src/rules/colors.ts
|
|
41650
|
+
var SKIP_TAGS2 = /* @__PURE__ */ new Set(["script", "style", "head", "title", "meta", "noscript", "link"]);
|
|
41651
|
+
function styleOf(el) {
|
|
41652
|
+
const s = attr(el, "style");
|
|
41653
|
+
return s ? parseInlineStyle(s) : /* @__PURE__ */ new Map();
|
|
41659
41654
|
}
|
|
41660
|
-
function
|
|
41661
|
-
|
|
41662
|
-
if (
|
|
41663
|
-
|
|
41664
|
-
|
|
41665
|
-
|
|
41666
|
-
if (/^border(Top|Right|Bottom|Left)?(Color|Width|Style)?$/.test(k) && d[k] && d[k] !== "none") return true;
|
|
41667
|
-
if (/^background(Color|Image)?$/.test(k) && d[k] && d[k] !== "none" && d[k] !== "rgba(0, 0, 0, 0)") return true;
|
|
41655
|
+
function colorFromValue(v) {
|
|
41656
|
+
const whole = parseColor(v);
|
|
41657
|
+
if (whole) return whole;
|
|
41658
|
+
for (const tok of v.split(/\s+/)) {
|
|
41659
|
+
const c2 = parseColor(tok);
|
|
41660
|
+
if (c2) return c2;
|
|
41668
41661
|
}
|
|
41669
|
-
return
|
|
41662
|
+
return null;
|
|
41670
41663
|
}
|
|
41671
|
-
|
|
41672
|
-
|
|
41673
|
-
|
|
41674
|
-
|
|
41675
|
-
|
|
41676
|
-
|
|
41677
|
-
|
|
41678
|
-
if (!css || css.unreadable > 0 || css.truncated) return [];
|
|
41679
|
-
const focusRules = css.rules.filter((r) => FOCUS_SEL.test(r.selector));
|
|
41680
|
-
if (!focusRules.length) return [];
|
|
41681
|
-
if (!focusRules.some((r) => killsOutline(r.decls))) return [];
|
|
41682
|
-
if (focusRules.some((r) => restoresIndicator(r.decls))) return [];
|
|
41683
|
-
const anchor = doc.elements.find((e) => ["a", "button", "input", "select", "textarea"].includes(e.tag));
|
|
41684
|
-
if (!anchor) return [];
|
|
41685
|
-
const killer = focusRules.find((r) => killsOutline(r.decls));
|
|
41686
|
-
return [{ criteriaId: "2.4.7", el: doc.elements[0] ?? anchor, msgId: "rendered-focus-not-visible", params: { selector: killer?.selector ?? ":focus" } }];
|
|
41664
|
+
function fgOf(el) {
|
|
41665
|
+
for (let p = el; p; p = p.parent) {
|
|
41666
|
+
const v = styleOf(p).get("color");
|
|
41667
|
+
if (v) {
|
|
41668
|
+
const c2 = colorFromValue(v);
|
|
41669
|
+
if (c2) return c2;
|
|
41670
|
+
}
|
|
41687
41671
|
}
|
|
41688
|
-
|
|
41689
|
-
var DOC_SEL = /^\s*(\*|:root|html|body)\s*$/i;
|
|
41690
|
-
function quarterTurn(transform) {
|
|
41691
|
-
const m = /rotate[ZY]?\(\s*(-?[\d.]+)deg\s*\)/i.exec(transform);
|
|
41692
|
-
if (!m) return false;
|
|
41693
|
-
const deg = Math.abs(Number.parseFloat(m[1])) % 180;
|
|
41694
|
-
return Math.abs(deg - 90) < 1;
|
|
41672
|
+
return null;
|
|
41695
41673
|
}
|
|
41696
|
-
|
|
41697
|
-
|
|
41698
|
-
|
|
41674
|
+
function bgOf(el) {
|
|
41675
|
+
for (let p = el; p; p = p.parent) {
|
|
41676
|
+
const st = styleOf(p);
|
|
41677
|
+
const v = st.get("background-color") ?? st.get("background");
|
|
41678
|
+
if (v) {
|
|
41679
|
+
const c2 = colorFromValue(v);
|
|
41680
|
+
if (c2) return c2;
|
|
41681
|
+
}
|
|
41682
|
+
}
|
|
41683
|
+
return null;
|
|
41684
|
+
}
|
|
41685
|
+
function fontPx(el) {
|
|
41686
|
+
for (let p = el; p; p = p.parent) {
|
|
41687
|
+
const v = styleOf(p).get("font-size");
|
|
41688
|
+
if (!v) continue;
|
|
41689
|
+
const m = /^([\d.]+)(px|pt|rem|em|%)?$/.exec(v.trim());
|
|
41690
|
+
if (!m) return "unknown";
|
|
41691
|
+
const n = parseFloat(m[1]);
|
|
41692
|
+
if (Number.isNaN(n)) return "unknown";
|
|
41693
|
+
const unit = m[2] ?? "px";
|
|
41694
|
+
if (unit === "pt") return n * 4 / 3;
|
|
41695
|
+
if (unit === "rem" || unit === "em") return n * 16;
|
|
41696
|
+
if (unit === "%") return n / 100 * 16;
|
|
41697
|
+
return n;
|
|
41698
|
+
}
|
|
41699
|
+
return null;
|
|
41700
|
+
}
|
|
41701
|
+
function isBold(el) {
|
|
41702
|
+
for (let p = el; p; p = p.parent) {
|
|
41703
|
+
const v = styleOf(p).get("font-weight");
|
|
41704
|
+
if (v) return v === "bold" || Number(v) >= 700;
|
|
41705
|
+
}
|
|
41706
|
+
return false;
|
|
41707
|
+
}
|
|
41708
|
+
function isLarge(el) {
|
|
41709
|
+
const px = fontPx(el);
|
|
41710
|
+
if (px === null) return false;
|
|
41711
|
+
if (px === "unknown") return true;
|
|
41712
|
+
return px >= 24 || px >= 18.66 && isBold(el);
|
|
41713
|
+
}
|
|
41714
|
+
function hasDirectText2(el) {
|
|
41715
|
+
return el.children.some((c2) => c2.type === "text" && c2.data.trim() !== "");
|
|
41716
|
+
}
|
|
41717
|
+
var contrastLiteral = {
|
|
41718
|
+
id: "contrast-literal",
|
|
41719
|
+
criteria: ["1.4.3"],
|
|
41699
41720
|
severity: "majeur",
|
|
41700
|
-
scope: "page",
|
|
41701
41721
|
run(doc) {
|
|
41702
|
-
const
|
|
41703
|
-
|
|
41704
|
-
|
|
41705
|
-
|
|
41706
|
-
|
|
41707
|
-
|
|
41708
|
-
|
|
41709
|
-
const
|
|
41710
|
-
|
|
41711
|
-
|
|
41722
|
+
const out2 = [];
|
|
41723
|
+
for (const el of doc.elements) {
|
|
41724
|
+
if (SKIP_TAGS2.has(el.tag) || !hasDirectText2(el)) continue;
|
|
41725
|
+
const fg = fgOf(el);
|
|
41726
|
+
const bg = bgOf(el);
|
|
41727
|
+
if (!fg || !bg || fg.a < 1 || bg.a < 1) continue;
|
|
41728
|
+
const ratio = contrastRatio(fg, bg);
|
|
41729
|
+
const large = isLarge(el);
|
|
41730
|
+
const min = large ? 3 : 4.5;
|
|
41731
|
+
if (ratio >= min) continue;
|
|
41732
|
+
out2.push({
|
|
41733
|
+
criteriaId: "1.4.3",
|
|
41734
|
+
el,
|
|
41735
|
+
msgId: "contrast-literal",
|
|
41736
|
+
params: { ratio: ratio.toFixed(2), min, textSize: large ? "large" : "normal" }
|
|
41737
|
+
});
|
|
41712
41738
|
}
|
|
41713
|
-
return
|
|
41739
|
+
return out2;
|
|
41714
41740
|
}
|
|
41715
41741
|
};
|
|
41716
|
-
var
|
|
41717
|
-
renderedContrast,
|
|
41718
|
-
renderedContrastPixel,
|
|
41719
|
-
renderedLinkColourOnly,
|
|
41720
|
-
renderedNonTextContrast,
|
|
41721
|
-
renderedFocusNotVisible,
|
|
41722
|
-
renderedOrientationLock
|
|
41723
|
-
];
|
|
41742
|
+
var colorsRules = [contrastLiteral];
|
|
41724
41743
|
|
|
41725
41744
|
// src/rules/timing.ts
|
|
41726
41745
|
var metaRefreshRedirect = {
|
|
@@ -47390,10 +47409,84 @@ var APPLICABLE = {
|
|
|
47390
47409
|
"3.1.1": (d) => isFullDocument(d)
|
|
47391
47410
|
// Language of Page — html-lang-missing / lang-invalid
|
|
47392
47411
|
};
|
|
47393
|
-
|
|
47412
|
+
var SUBJECT_MATTER = {
|
|
47413
|
+
// ---- Time-based media (WCAG 1.2.x → RGAA theme 4) ----
|
|
47414
|
+
// Captions, transcripts, audio description and sign language all presuppose a media element.
|
|
47415
|
+
// `track` counts on its own: a stray <track> means media is being assembled somewhere.
|
|
47416
|
+
"1.2.1": hasMedia,
|
|
47417
|
+
"1.2.2": hasMedia,
|
|
47418
|
+
"1.2.3": hasMedia,
|
|
47419
|
+
"1.2.4": hasMedia,
|
|
47420
|
+
"1.2.5": hasMedia,
|
|
47421
|
+
// ---- Motion actuation (WCAG 2.5.4) ----
|
|
47422
|
+
// Applicable only where device motion drives the interface, which is a source-visible API.
|
|
47423
|
+
"2.5.4": hasMotionApi,
|
|
47424
|
+
// ---- Forms (WCAG 3.3.x) ----
|
|
47425
|
+
// Error identification, labels and instructions, error suggestion, error prevention,
|
|
47426
|
+
// redundant entry and accessible authentication all presuppose user input. 3.3.4 and 3.3.8
|
|
47427
|
+
// are narrower still (legal/financial transactions, authentication), so "any form control"
|
|
47428
|
+
// is the deliberately over-inclusive predicate for them.
|
|
47429
|
+
"3.3.1": hasFormControl,
|
|
47430
|
+
"3.3.2": hasFormControl,
|
|
47431
|
+
"3.3.3": hasFormControl,
|
|
47432
|
+
"3.3.4": hasFormControl,
|
|
47433
|
+
"3.3.7": hasFormControl,
|
|
47434
|
+
"3.3.8": hasFormControl,
|
|
47435
|
+
// Identify Input Purpose — about autocomplete on fields collecting user data.
|
|
47436
|
+
"1.3.5": hasFormControl
|
|
47437
|
+
};
|
|
47438
|
+
function hasMedia(d) {
|
|
47439
|
+
return has(d, "audio", "video", "track", "source", "object", "embed", "iframe", "marquee", "canvas");
|
|
47440
|
+
}
|
|
47441
|
+
function hasMotionApi(d) {
|
|
47442
|
+
return /\b(?:devicemotion|deviceorientation|DeviceMotionEvent|DeviceOrientationEvent|Accelerometer|Gyroscope|requestPermission)\b/i.test(d.source);
|
|
47443
|
+
}
|
|
47444
|
+
function hasFormControl(d) {
|
|
47445
|
+
if (has(d, "input", "select", "textarea", "form", "fieldset", "output", "datalist")) return true;
|
|
47446
|
+
return d.elements.some((e) => {
|
|
47447
|
+
const role = (e.attribs.role ?? "").toLowerCase();
|
|
47448
|
+
if (role && /^(textbox|combobox|listbox|checkbox|radio|radiogroup|slider|spinbutton|searchbox|switch|form)$/.test(role)) return true;
|
|
47449
|
+
return e.attribs.contenteditable !== void 0;
|
|
47450
|
+
});
|
|
47451
|
+
}
|
|
47452
|
+
var RESIDUAL_TRAIL = {
|
|
47453
|
+
// Rendering criteria the SNAPSHOT tier decides offline, from computed styles + stylesheets.
|
|
47454
|
+
"1.3.4": "Decided on a page's own stylesheets (an orientation media query that rotates the document) \u2014 record a snapshot with `scan --sample` (or an E2E capture), then re-audit.",
|
|
47455
|
+
"1.4.1": "Decided on computed colours (a link distinguished by colour alone) \u2014 record a snapshot with `scan --sample`, then re-audit.",
|
|
47456
|
+
"1.4.3": "Decided on computed text/background colours, with a screenshot fallback where the backdrop is an image \u2014 record a snapshot with `scan --sample`, then re-audit.",
|
|
47457
|
+
"1.4.11": "Decided on computed colours of UI components and graphics \u2014 record a snapshot with `scan --sample`, then re-audit.",
|
|
47458
|
+
"2.4.7": "Decided on the page's own stylesheets (a `:focus` rule that removes the indicator without restoring one), or by the live focus probe \u2014 record a snapshot with `scan --sample`, then re-audit.",
|
|
47459
|
+
// Rendering criteria that need a LIVE browser: they are measured by acting on the page.
|
|
47460
|
+
"1.4.4": "Needs a live browser: the page is re-measured at 200% zoom \u2014 `scan <url> --runtime local --merge <audit.json>`.",
|
|
47461
|
+
"1.4.10": "Needs a live browser: the page is re-measured in a 320px viewport (reflow) \u2014 `scan <url> --merge <audit.json>`.",
|
|
47462
|
+
"1.4.12": "Needs a live browser: text spacing overrides are injected and the layout re-measured \u2014 `scan <url> --runtime local --merge <audit.json>`.",
|
|
47463
|
+
"1.4.13": "Needs a live browser: hover/focus-triggered content is opened and probed for dismiss/hover/persist \u2014 `scan <url> --runtime local --merge <audit.json>`.",
|
|
47464
|
+
// axe's own `target-size` rule can FAIL this on a live page, but no tier certifies it, so a
|
|
47465
|
+
// clean scan leaves it open rather than conforming. Say both halves.
|
|
47466
|
+
"2.5.8": "A live scan can fail this (axe `target-size`) but never certify it \u2014 `scan <url> --runtime local --merge <audit.json>`, then adjudicate what is left.",
|
|
47467
|
+
"4.1.3": "Needs a live browser WITH interaction: a status message is triggered and the live region observed \u2014 `scan <url> --runtime local --merge <audit.json>` (interactions are on by default).",
|
|
47468
|
+
// Rendering criteria no automated tier measures — say so, rather than pointing at `scan` and
|
|
47469
|
+
// letting the reader discover it changes nothing.
|
|
47470
|
+
"1.4.5": "No automated tier decides this: whether text is presented as an image is a reading of each image's content. Adjudicate it (`verify --manual`) against the images the audit lists.",
|
|
47471
|
+
"2.1.2": "No automated tier decides this: escaping a keyboard trap has to be attempted by hand, on each focusable region.",
|
|
47472
|
+
"2.3.1": "No automated tier decides this: flashing has to be observed over time on the rendered page.",
|
|
47473
|
+
"2.4.11": "No automated tier decides this: whether a focused element stays unobscured depends on the sticky headers and overlays in play on each screen."
|
|
47474
|
+
};
|
|
47475
|
+
function residualReason(automatability2, sc) {
|
|
47476
|
+
const trail = sc ? RESIDUAL_TRAIL[sc] : void 0;
|
|
47477
|
+
if (trail) return trail;
|
|
47394
47478
|
return automatability2 === "needs-rendering" ? "Needs a rendered DOM (contrast, focus visibility, zoom/reflow, target size) \u2014 decide via `scan`." : "Judgement criterion \u2014 adjudicated by the agent from source/context (`verify --manual`, gated).";
|
|
47395
47479
|
}
|
|
47480
|
+
function subjectMatterReason(sc, files) {
|
|
47481
|
+
const scope = `nothing in the ${files} file(s) audited is concerned`;
|
|
47482
|
+
if (sc.startsWith("1.2."))
|
|
47483
|
+
return `No time-based media in scope: ${scope} \u2014 no <audio>, <video>, <track>, <object>, <embed>, <iframe> or <canvas> element was found.`;
|
|
47484
|
+
if (sc === "2.5.4") return `No motion actuation in scope: ${scope} \u2014 no device-motion or device-orientation API is used.`;
|
|
47485
|
+
if (sc === "1.3.5" || sc.startsWith("3.3.")) return `No user input in scope: ${scope} \u2014 no form control (native, ARIA or contenteditable) was found.`;
|
|
47486
|
+
return `No element in scope is concerned by this success criterion (${scope}).`;
|
|
47487
|
+
}
|
|
47396
47488
|
var STATIC_PREDS = allSC().filter((c2) => c2.automatability === "static").map((c2) => [c2.sc, APPLICABLE[c2.sc] ?? isFullDocument]);
|
|
47489
|
+
var SUBJECT_PREDS = allSC().filter((c2) => c2.automatability !== "static" && SUBJECT_MATTER[c2.sc] !== void 0).map((c2) => [c2.sc, SUBJECT_MATTER[c2.sc]]);
|
|
47397
47490
|
function newAccum() {
|
|
47398
47491
|
return {
|
|
47399
47492
|
byCriterion: /* @__PURE__ */ new Map(),
|
|
@@ -47406,7 +47499,8 @@ function newAccum() {
|
|
|
47406
47499
|
sfcFiles: 0,
|
|
47407
47500
|
sfcExts: /* @__PURE__ */ new Set(),
|
|
47408
47501
|
captures: [],
|
|
47409
|
-
langCounts: /* @__PURE__ */ new Map()
|
|
47502
|
+
langCounts: /* @__PURE__ */ new Map(),
|
|
47503
|
+
renderedScs: /* @__PURE__ */ new Set()
|
|
47410
47504
|
};
|
|
47411
47505
|
}
|
|
47412
47506
|
function primarySubtag(lang) {
|
|
@@ -47439,6 +47533,10 @@ function foldDoc(acc, doc, graph) {
|
|
|
47439
47533
|
for (const [id, pred] of STATIC_PREDS) {
|
|
47440
47534
|
if (!acc.applicable.get(id) && pred(doc)) acc.applicable.set(id, true);
|
|
47441
47535
|
}
|
|
47536
|
+
for (const [id, pred] of SUBJECT_PREDS) {
|
|
47537
|
+
if (!acc.applicable.get(id) && pred(doc)) acc.applicable.set(id, true);
|
|
47538
|
+
}
|
|
47539
|
+
if (doc.signals) for (const sc of renderedTestedScs(doc.signals)) acc.renderedScs.add(sc);
|
|
47442
47540
|
if (doc.opaqueComponents?.length) {
|
|
47443
47541
|
for (const lib of doc.opaqueComponents) acc.opaqueLibs.add(lib);
|
|
47444
47542
|
acc.opaqueFiles++;
|
|
@@ -47475,9 +47573,12 @@ function finalize(acc, inputs, extra = {}) {
|
|
|
47475
47573
|
}
|
|
47476
47574
|
} else if (normativeFs.length > 0) {
|
|
47477
47575
|
status = "NC";
|
|
47576
|
+
} else if (SUBJECT_MATTER[c2.sc] !== void 0 && !(acc.applicable.get(c2.sc) ?? false)) {
|
|
47577
|
+
status = "NA";
|
|
47578
|
+
justification = subjectMatterReason(c2.sc, acc.fileCount);
|
|
47478
47579
|
} else {
|
|
47479
47580
|
status = "manual";
|
|
47480
|
-
residualRisks.push({ criteriaId: c2.sc, reason: residualReason(c2.automatability), automatability: c2.automatability });
|
|
47581
|
+
residualRisks.push({ criteriaId: c2.sc, reason: residualReason(c2.automatability, c2.sc), automatability: c2.automatability });
|
|
47481
47582
|
}
|
|
47482
47583
|
criteria.push({ id: c2.sc, guideline: c2.guideline, status, findings: fs2, ...justification ? { justification } : {} });
|
|
47483
47584
|
}
|
|
@@ -47515,6 +47616,11 @@ function finalize(acc, inputs, extra = {}) {
|
|
|
47515
47616
|
}
|
|
47516
47617
|
} : {},
|
|
47517
47618
|
...acc.langCounts.size ? { langs: [...acc.langCounts.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).map(([lang]) => lang) } : {},
|
|
47619
|
+
// Coverage the RENDERED tier earned from the snapshots this run ingested. Same field the
|
|
47620
|
+
// live-browser merge stamps (`mergeDynamic`), because it is the same claim: these criteria
|
|
47621
|
+
// were measured. Omitted when no snapshot carried usable signals, so a source-only audit
|
|
47622
|
+
// is byte-for-byte unchanged and still says the rendering criteria are untested.
|
|
47623
|
+
...acc.renderedScs.size ? { scan: { testedScs: [...acc.renderedScs].sort() } } : {},
|
|
47518
47624
|
// The pages this run genuinely read. Written UNCONDITIONALLY — `[]` is the whole point,
|
|
47519
47625
|
// because "this audit read no page" is exactly the claim a source-only run needs to make,
|
|
47520
47626
|
// and an omit-when-empty field would say nothing precisely then. See scope.pagesAudited.
|
|
@@ -47529,8 +47635,8 @@ function finalize(acc, inputs, extra = {}) {
|
|
|
47529
47635
|
};
|
|
47530
47636
|
}
|
|
47531
47637
|
function hashContent(content, mode) {
|
|
47532
|
-
const
|
|
47533
|
-
return createHash4("sha1").update(
|
|
47638
|
+
const norm5 = mode === "normalized" ? content.replace(/>\s+</g, "><").trim() : content;
|
|
47639
|
+
return createHash4("sha1").update(norm5).digest("hex");
|
|
47534
47640
|
}
|
|
47535
47641
|
function runAudit(opts) {
|
|
47536
47642
|
const acc = newAccum();
|
|
@@ -53367,11 +53473,15 @@ var L4 = {
|
|
|
53367
53473
|
}
|
|
53368
53474
|
};
|
|
53369
53475
|
var NEEDS_RENDERING = [
|
|
53476
|
+
{ sc: "1.3.4", label: { fr: "verrou d\u2019orientation", en: "orientation lock" } },
|
|
53477
|
+
{ sc: "1.4.1", label: { fr: "information par la couleur", en: "use of colour" } },
|
|
53478
|
+
{ sc: "1.4.3", label: { fr: "contraste du texte", en: "text contrast" } },
|
|
53370
53479
|
{ sc: "1.4.4", label: { fr: "zoom 200 %", en: "200% zoom" } },
|
|
53371
53480
|
{ sc: "1.4.10", label: { fr: "reflow 320 px", en: "320px reflow" } },
|
|
53481
|
+
{ sc: "1.4.11", label: { fr: "contraste des composants", en: "non-text contrast" } },
|
|
53372
53482
|
{ sc: "1.4.12", label: { fr: "espacement du texte", en: "text spacing" } },
|
|
53373
|
-
{ sc: "2.4.7", label: { fr: "visibilit\xE9 du focus", en: "focus visibility" } },
|
|
53374
53483
|
{ sc: "1.4.13", label: { fr: "contenu au survol", en: "content on hover" } },
|
|
53484
|
+
{ sc: "2.4.7", label: { fr: "visibilit\xE9 du focus", en: "focus visibility" } },
|
|
53375
53485
|
{ sc: "4.1.3", label: { fr: "r\xE9gions live", en: "live regions" } }
|
|
53376
53486
|
];
|
|
53377
53487
|
function untestedNeedsRendering(r) {
|
|
@@ -55475,7 +55585,7 @@ function sectionBody(md, n) {
|
|
|
55475
55585
|
|
|
55476
55586
|
// src/adjudicate.ts
|
|
55477
55587
|
import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync10 } from "fs";
|
|
55478
|
-
import { join as join35 } from "path";
|
|
55588
|
+
import { join as join35, resolve as resolve10 } from "path";
|
|
55479
55589
|
|
|
55480
55590
|
// src/data/adjudication.json
|
|
55481
55591
|
var adjudication_default = {
|
|
@@ -56422,7 +56532,7 @@ function docsForAudit(audit2, cwd) {
|
|
|
56422
56532
|
const docs = [];
|
|
56423
56533
|
for (const f of files) {
|
|
56424
56534
|
try {
|
|
56425
|
-
docs.push(parseSource(readText(cwd ?
|
|
56535
|
+
docs.push(parseSource(readText(cwd ? resolve10(cwd, f) : f), f));
|
|
56426
56536
|
} catch {
|
|
56427
56537
|
}
|
|
56428
56538
|
}
|
|
@@ -56523,6 +56633,17 @@ function normativeRefResolves(ref, standard, itemCriterionId) {
|
|
|
56523
56633
|
}
|
|
56524
56634
|
function applyAdjudication(audit2, adj, opts = {}) {
|
|
56525
56635
|
const issues = [];
|
|
56636
|
+
const expected = opts.residualReasons ?? {};
|
|
56637
|
+
const itemIssues = /* @__PURE__ */ new Map();
|
|
56638
|
+
const notFolded = /* @__PURE__ */ new Set();
|
|
56639
|
+
const blame = (criteriaId, issue) => {
|
|
56640
|
+
issues.push(issue);
|
|
56641
|
+
notFolded.add(criteriaId);
|
|
56642
|
+
const list = itemIssues.get(criteriaId);
|
|
56643
|
+
if (list) list.push(issue);
|
|
56644
|
+
else itemIssues.set(criteriaId, [issue]);
|
|
56645
|
+
};
|
|
56646
|
+
const uncovered = (criteriaId) => notFolded.add(criteriaId);
|
|
56526
56647
|
canonicalizeAdjudication(adj);
|
|
56527
56648
|
const byId2 = new Map(adj.items.map((it) => [it.criteriaId, it]));
|
|
56528
56649
|
const packMode = !isCore(adj.standard);
|
|
@@ -56531,71 +56652,101 @@ function applyAdjudication(audit2, adj, opts = {}) {
|
|
|
56531
56652
|
for (const pc of derivePackResults(audit2, adj.standard)) {
|
|
56532
56653
|
if (pc.status !== "manual") continue;
|
|
56533
56654
|
open.add(pc.id);
|
|
56534
|
-
if (
|
|
56655
|
+
if (byId2.has(pc.id)) continue;
|
|
56656
|
+
if (Object.hasOwn(expected, pc.id)) uncovered(pc.id);
|
|
56657
|
+
else blame(pc.id, `criterion ${pc.id}: missing from the adjudication (coverage gap)`);
|
|
56535
56658
|
}
|
|
56536
56659
|
} else {
|
|
56537
56660
|
for (const r of audit2.residualRisks) {
|
|
56538
56661
|
open.add(r.criteriaId);
|
|
56539
|
-
if (
|
|
56662
|
+
if (byId2.has(r.criteriaId)) continue;
|
|
56663
|
+
if (Object.hasOwn(expected, r.criteriaId)) uncovered(r.criteriaId);
|
|
56664
|
+
else blame(r.criteriaId, `criterion ${r.criteriaId}: missing from the adjudication (coverage gap)`);
|
|
56540
56665
|
}
|
|
56541
56666
|
}
|
|
56542
56667
|
for (const it of adj.items) {
|
|
56543
56668
|
if (!open.has(it.criteriaId)) {
|
|
56544
|
-
|
|
56669
|
+
blame(it.criteriaId, `criterion ${it.criteriaId}: not open for adjudication \u2014 the engine already decided it, or it is not part of ${adj.standard}`);
|
|
56545
56670
|
}
|
|
56546
56671
|
}
|
|
56547
|
-
const groundInputs =
|
|
56672
|
+
const groundInputs = /* @__PURE__ */ new Map();
|
|
56673
|
+
const toGround = (criteriaId, g) => {
|
|
56674
|
+
const list = groundInputs.get(criteriaId);
|
|
56675
|
+
if (list) list.push(g);
|
|
56676
|
+
else groundInputs.set(criteriaId, [g]);
|
|
56677
|
+
};
|
|
56548
56678
|
for (const it of adj.items) {
|
|
56549
56679
|
const v = it.verdict;
|
|
56550
56680
|
if (v === null) {
|
|
56551
|
-
|
|
56681
|
+
blame(it.criteriaId, `criterion ${it.criteriaId}: unadjudicated (verdict is null)`);
|
|
56552
56682
|
} else if (v === "C" || v === "NA") {
|
|
56553
|
-
if (!it.justification || !it.justification.trim())
|
|
56683
|
+
if (!it.justification || !it.justification.trim()) blame(it.criteriaId, `criterion ${it.criteriaId}: a ${v} verdict requires a justification`);
|
|
56554
56684
|
const cites = it.citations ?? [];
|
|
56555
56685
|
if (it.evidence.length === 0) {
|
|
56556
56686
|
if (v === "C") {
|
|
56557
|
-
|
|
56687
|
+
blame(
|
|
56688
|
+
it.criteriaId,
|
|
56558
56689
|
`criterion ${it.criteriaId}: a C verdict needs evidence to cite, and none was harvested for this criterion \u2014 record "manual" (reason "undecidable"), or "NA" if nothing in scope is concerned`
|
|
56559
56690
|
);
|
|
56560
56691
|
}
|
|
56561
56692
|
} else if (cites.length === 0) {
|
|
56562
|
-
|
|
56693
|
+
blame(
|
|
56694
|
+
it.criteriaId,
|
|
56563
56695
|
`criterion ${it.criteriaId}: a ${v} verdict must cite at least one of the ${it.evidence.length} evidence item(s) it was shown (citations: [{file, line, \u2026}])`
|
|
56564
56696
|
);
|
|
56565
56697
|
} else {
|
|
56566
56698
|
const anchors = new Set(it.evidence.map((e) => `${e.file}:${e.line}`));
|
|
56567
56699
|
for (const c2 of cites) {
|
|
56568
56700
|
if (!anchors.has(`${c2.file}:${c2.line}`)) {
|
|
56569
|
-
|
|
56701
|
+
blame(it.criteriaId, `criterion ${it.criteriaId}: citation ${c2.file}:${c2.line} is not among this criterion's harvested evidence (fabricated?)`);
|
|
56570
56702
|
}
|
|
56571
|
-
|
|
56703
|
+
toGround(it.criteriaId, { file: c2.file, line: c2.line, selector: c2.selector, snippet: c2.snippet });
|
|
56572
56704
|
}
|
|
56573
56705
|
}
|
|
56574
56706
|
} else if (v === "NC") {
|
|
56575
|
-
if (!it.findings || it.findings.length === 0)
|
|
56707
|
+
if (!it.findings || it.findings.length === 0) blame(it.criteriaId, `criterion ${it.criteriaId}: an NC verdict requires at least one groundable finding`);
|
|
56576
56708
|
for (const f of it.findings ?? []) {
|
|
56577
56709
|
if (!f.normativeRef || !f.normativeRef.trim()) {
|
|
56578
|
-
|
|
56710
|
+
blame(it.criteriaId, `criterion ${it.criteriaId}: an NC finding requires a normativeRef citing the failed test of the active standard`);
|
|
56579
56711
|
} else if (!normativeRefResolves(f.normativeRef, adj.standard, isCore(adj.standard) ? void 0 : it.criteriaId)) {
|
|
56580
|
-
|
|
56712
|
+
blame(
|
|
56713
|
+
it.criteriaId,
|
|
56581
56714
|
isCore(adj.standard) ? `criterion ${it.criteriaId}: normativeRef "${f.normativeRef}" does not resolve to a test of ${adj.standard} (fabricated?)` : `criterion ${it.criteriaId}: normativeRef "${f.normativeRef}" is not a test of ${adj.standard} ${it.criteriaId} \u2014 cite one of its own tests (e.g. "${it.criteriaId}.1"); a WCAG id looks alike but denotes an unrelated test`
|
|
56582
56715
|
);
|
|
56583
56716
|
}
|
|
56584
|
-
|
|
56717
|
+
toGround(it.criteriaId, { file: f.file, line: f.line, selector: f.selector, snippet: f.snippet });
|
|
56585
56718
|
}
|
|
56586
56719
|
} else if (v === "manual") {
|
|
56587
56720
|
if (!it.reason || !MANUAL_REASONS.has(it.reason))
|
|
56588
|
-
|
|
56721
|
+
blame(it.criteriaId, `criterion ${it.criteriaId}: a manual verdict requires reason \u2208 {${MANUAL_REASON_VALUES.join(", ")}}`);
|
|
56589
56722
|
} else {
|
|
56590
|
-
|
|
56723
|
+
blame(it.criteriaId, `criterion ${it.criteriaId}: unknown verdict "${String(v)}" \u2014 expected one of ${VERDICTS.join(" | ")}`);
|
|
56724
|
+
}
|
|
56725
|
+
for (const rec of it.recommendations ?? []) toGround(it.criteriaId, { file: rec.file, line: rec.line, selector: rec.selector, snippet: rec.snippet });
|
|
56726
|
+
}
|
|
56727
|
+
const grounding = { grounded: 0, moved: 0, failed: 0, issues: [] };
|
|
56728
|
+
for (const [criteriaId, inputs] of groundInputs) {
|
|
56729
|
+
for (const input of inputs) {
|
|
56730
|
+
const r = groundFinding(input, { cwd: opts.cwd });
|
|
56731
|
+
if (r.ok) {
|
|
56732
|
+
grounding.grounded++;
|
|
56733
|
+
if (r.moved) grounding.moved++;
|
|
56734
|
+
} else {
|
|
56735
|
+
grounding.failed++;
|
|
56736
|
+
if (r.issue) {
|
|
56737
|
+
grounding.issues.push(r.issue);
|
|
56738
|
+
blame(criteriaId, r.issue);
|
|
56739
|
+
}
|
|
56740
|
+
}
|
|
56591
56741
|
}
|
|
56592
|
-
for (const rec of it.recommendations ?? []) groundInputs.push({ file: rec.file, line: rec.line, selector: rec.selector, snippet: rec.snippet });
|
|
56593
56742
|
}
|
|
56594
|
-
|
|
56595
|
-
|
|
56596
|
-
if (issues.length) {
|
|
56597
|
-
return { ok: false, audit: audit2, issues, applied: 0, stillManual: 0, grounding };
|
|
56743
|
+
if (opts.strict && issues.length) {
|
|
56744
|
+
return { ok: false, audit: audit2, issues, applied: 0, stillManual: 0, rejected: 0, rejectedCriteria: [], grounding };
|
|
56598
56745
|
}
|
|
56746
|
+
const rejectedCriteria = adj.items.map((it) => it.criteriaId).filter((id) => itemIssues.has(id));
|
|
56747
|
+
for (const id of itemIssues.keys()) if (!rejectedCriteria.includes(id)) rejectedCriteria.push(id);
|
|
56748
|
+
const rejectedSet = notFolded;
|
|
56749
|
+
const rejectedWhy = (id) => expected[id] ?? residualRejectedReason(itemIssues.get(id)?.[0] ?? "refused by the gate");
|
|
56599
56750
|
const next = structuredClone(audit2);
|
|
56600
56751
|
const critById = new Map(next.criteria.map((c2) => [c2.id, c2]));
|
|
56601
56752
|
const newFindings = [];
|
|
@@ -56604,6 +56755,17 @@ function applyAdjudication(audit2, adj, opts = {}) {
|
|
|
56604
56755
|
if (packMode) {
|
|
56605
56756
|
const decided = [];
|
|
56606
56757
|
for (const it of adj.items) {
|
|
56758
|
+
if (rejectedSet.has(it.criteriaId)) {
|
|
56759
|
+
if (!open.has(it.criteriaId)) continue;
|
|
56760
|
+
decided.push({
|
|
56761
|
+
id: it.criteriaId,
|
|
56762
|
+
status: "manual",
|
|
56763
|
+
reason: "undecidable",
|
|
56764
|
+
justification: rejectedWhy(it.criteriaId),
|
|
56765
|
+
findings: []
|
|
56766
|
+
});
|
|
56767
|
+
continue;
|
|
56768
|
+
}
|
|
56607
56769
|
if (it.verdict === "manual") {
|
|
56608
56770
|
stillManual++;
|
|
56609
56771
|
decided.push({
|
|
@@ -56628,14 +56790,19 @@ function applyAdjudication(audit2, adj, opts = {}) {
|
|
|
56628
56790
|
decidedBy: "agent"
|
|
56629
56791
|
});
|
|
56630
56792
|
}
|
|
56793
|
+
for (const id of notFolded) {
|
|
56794
|
+
if (byId2.has(id) || !open.has(id)) continue;
|
|
56795
|
+
decided.push({ id, status: "manual", reason: "undecidable", justification: rejectedWhy(id), findings: [] });
|
|
56796
|
+
}
|
|
56631
56797
|
next.packAdjudication = { standard: adj.standard, criteria: decided };
|
|
56632
56798
|
next.findings = [...next.findings, ...newFindings];
|
|
56633
|
-
next.adjudicated = { date: adj.auditDate, applied, stillManual };
|
|
56634
|
-
return { ok:
|
|
56799
|
+
next.adjudicated = { date: adj.auditDate, applied, stillManual, ...rejectedCriteria.length ? { rejected: rejectedCriteria.length } : {} };
|
|
56800
|
+
return { ok: issues.length === 0, audit: next, issues, applied, stillManual, rejected: rejectedCriteria.length, rejectedCriteria, grounding };
|
|
56635
56801
|
}
|
|
56636
56802
|
for (const it of adj.items) {
|
|
56637
56803
|
const c2 = critById.get(it.criteriaId);
|
|
56638
56804
|
if (!c2) continue;
|
|
56805
|
+
if (rejectedSet.has(it.criteriaId)) continue;
|
|
56639
56806
|
if (it.verdict === "manual") {
|
|
56640
56807
|
c2.status = "manual";
|
|
56641
56808
|
c2.decidedBy = "agent";
|
|
@@ -56656,7 +56823,7 @@ function applyAdjudication(audit2, adj, opts = {}) {
|
|
|
56656
56823
|
}
|
|
56657
56824
|
for (const it of adj.items) {
|
|
56658
56825
|
const c2 = critById.get(it.criteriaId);
|
|
56659
|
-
if (!c2) continue;
|
|
56826
|
+
if (!c2 || rejectedSet.has(it.criteriaId)) continue;
|
|
56660
56827
|
for (const rec of it.recommendations ?? []) {
|
|
56661
56828
|
const f = agentFinding(it.criteriaId, rec, true);
|
|
56662
56829
|
c2.findings.push(f);
|
|
@@ -56664,10 +56831,10 @@ function applyAdjudication(audit2, adj, opts = {}) {
|
|
|
56664
56831
|
}
|
|
56665
56832
|
}
|
|
56666
56833
|
next.findings = [...next.findings, ...newFindings];
|
|
56667
|
-
next.residualRisks = next.residualRisks.filter((r) => byId2.get(r.criteriaId)?.verdict === "manual");
|
|
56834
|
+
next.residualRisks = next.residualRisks.filter((r) => byId2.get(r.criteriaId)?.verdict === "manual" || rejectedSet.has(r.criteriaId)).map((r) => rejectedSet.has(r.criteriaId) ? { ...r, reason: rejectedWhy(r.criteriaId) } : r);
|
|
56668
56835
|
recomputeTallies(next);
|
|
56669
|
-
next.adjudicated = { date: adj.auditDate, applied, stillManual };
|
|
56670
|
-
return { ok:
|
|
56836
|
+
next.adjudicated = { date: adj.auditDate, applied, stillManual, ...rejectedCriteria.length ? { rejected: rejectedCriteria.length } : {} };
|
|
56837
|
+
return { ok: issues.length === 0, audit: next, issues, applied, stillManual, rejected: rejectedCriteria.length, rejectedCriteria, grounding };
|
|
56671
56838
|
}
|
|
56672
56839
|
function agentFinding(criteriaId, f, advisory = false) {
|
|
56673
56840
|
return {
|
|
@@ -56698,6 +56865,7 @@ function recomputeTallies(a) {
|
|
|
56698
56865
|
}
|
|
56699
56866
|
var residualScanReason = () => "Rendering criterion \u2014 decide on the rendered DOM (`scan`).";
|
|
56700
56867
|
var residualUndecidableReason = () => "Left as an explicit residual risk (not decidable from the available evidence).";
|
|
56868
|
+
var residualRejectedReason = (why) => `Adjudication refused by the gate \u2014 ${why.replace(/^criterion \S+: /, "")}. Re-adjudicate this criterion.`;
|
|
56701
56869
|
var T2 = {
|
|
56702
56870
|
fr: {
|
|
56703
56871
|
title: "# Adjudication des crit\xE8res \xE0 \xE9valuer (ultra11y)",
|
|
@@ -56773,11 +56941,11 @@ function packGuidanceBlock(standard, criterionId, lang) {
|
|
|
56773
56941
|
function plainTest(s) {
|
|
56774
56942
|
return s.replace(/\[([^\]]+)\]\(#[^)]*\)/g, "$1");
|
|
56775
56943
|
}
|
|
56776
|
-
function formatAdjudication(items, lang = "en", standard = CORE2) {
|
|
56944
|
+
function formatAdjudication(items, lang = "en", standard = CORE2, opts = {}) {
|
|
56777
56945
|
const s = T2[lang];
|
|
56778
56946
|
const pack = isCore(standard) ? void 0 : loadPack(standard);
|
|
56779
|
-
const out2 = [s.title, "", s.intro, "", ...s.verdicts, "", s.rule, "", s.then, ""];
|
|
56780
|
-
if (pack) out2.push(`> ${s.packIntro(pack.name)}`, "");
|
|
56947
|
+
const out2 = opts.preamble === false ? [] : [s.title, "", s.intro, "", ...s.verdicts, "", s.rule, "", s.then, ""];
|
|
56948
|
+
if (pack && opts.preamble !== false) out2.push(`> ${s.packIntro(pack.name)}`, "");
|
|
56781
56949
|
for (const it of items) {
|
|
56782
56950
|
out2.push(`## ${pack ? `${pack.name} ` : ""}${it.criteriaId}${it.title ? ` \u2014 ${it.title}` : ""} _(${it.automatability})_`);
|
|
56783
56951
|
out2.push("", `> ${s.evidence} (${it.evidence.length}${it.evidenceTruncated ? ` / ${it.evidenceTruncated.total}` : ""}):`, "");
|
|
@@ -56838,7 +57006,149 @@ function writeAdjudication(items, outDir, opts) {
|
|
|
56838
57006
|
};
|
|
56839
57007
|
writeFileSync10(todoPath, JSON.stringify(file, null, 2) + "\n");
|
|
56840
57008
|
writeFileSync10(mdPath, formatAdjudication(items, opts.lang ?? "en", opts.standard));
|
|
56841
|
-
|
|
57009
|
+
const verdictsPath = join35(outDir, "ADJUDICATE.verdicts.json");
|
|
57010
|
+
writeFileSync10(verdictsPath, JSON.stringify({ ...file, items: slimAdjudicationItems(items) }, null, 2) + "\n");
|
|
57011
|
+
const itemsDir = join35(outDir, "adjudicate");
|
|
57012
|
+
mkdirSync9(itemsDir, { recursive: true });
|
|
57013
|
+
for (const it of items) {
|
|
57014
|
+
writeFileSync10(join35(itemsDir, `${it.criteriaId}.md`), formatAdjudication([it], opts.lang ?? "en", opts.standard, { preamble: false }));
|
|
57015
|
+
}
|
|
57016
|
+
return { todoPath, mdPath, verdictsPath, itemsDir, count: items.length };
|
|
57017
|
+
}
|
|
57018
|
+
function slimAdjudicationItems(items) {
|
|
57019
|
+
return items.map((it) => {
|
|
57020
|
+
const { evidence: _evidence, evidenceTruncated: _truncated, ...rest } = it;
|
|
57021
|
+
return { ...rest, evidence: [] };
|
|
57022
|
+
});
|
|
57023
|
+
}
|
|
57024
|
+
function hydrateAdjudication(adj, audit2, opts = {}) {
|
|
57025
|
+
const needs = adj.items.filter((it) => !it.evidence || it.evidence.length === 0);
|
|
57026
|
+
if (!needs.length) return;
|
|
57027
|
+
const derived = new Map(buildAdjudicationWorklist(audit2, { ...opts, standard: adj.standard }).map((it) => [it.criteriaId, it]));
|
|
57028
|
+
for (const it of needs) {
|
|
57029
|
+
const full = derived.get(it.criteriaId);
|
|
57030
|
+
if (!full) continue;
|
|
57031
|
+
it.evidence = full.evidence;
|
|
57032
|
+
if (full.evidenceTruncated) it.evidenceTruncated = full.evidenceTruncated;
|
|
57033
|
+
}
|
|
57034
|
+
}
|
|
57035
|
+
|
|
57036
|
+
// src/ledger.ts
|
|
57037
|
+
import { createHash as createHash6 } from "crypto";
|
|
57038
|
+
import { existsSync as existsSync21, mkdirSync as mkdirSync10, readFileSync as readFileSync19, writeFileSync as writeFileSync11 } from "fs";
|
|
57039
|
+
import { dirname as dirname10, join as join36 } from "path";
|
|
57040
|
+
var LEDGER_DIR = ".ultra11y/verdicts";
|
|
57041
|
+
function ledgerPath(standard, root = ".") {
|
|
57042
|
+
return join36(root, LEDGER_DIR, `${standard}.json`);
|
|
57043
|
+
}
|
|
57044
|
+
var norm3 = (s) => s.replace(/\s+/g, " ").trim();
|
|
57045
|
+
var anchorKey = (e) => `${e.file}|${e.selector ?? ""}|${norm3(e.snippet ?? "")}`;
|
|
57046
|
+
function evidenceFingerprint(evidence) {
|
|
57047
|
+
const keys = evidence.map(anchorKey).sort();
|
|
57048
|
+
return `sha256:${createHash6("sha256").update(`${keys.length}
|
|
57049
|
+
${keys.join("\n")}`).digest("hex").slice(0, 32)}`;
|
|
57050
|
+
}
|
|
57051
|
+
function reanchor(stored, today2) {
|
|
57052
|
+
if (!stored?.length) return stored;
|
|
57053
|
+
const byKey2 = new Map(today2.map((e) => [anchorKey(e), e]));
|
|
57054
|
+
return stored.map((s) => {
|
|
57055
|
+
const now = byKey2.get(anchorKey(s));
|
|
57056
|
+
return now && now.line !== s.line ? { ...s, line: now.line } : s;
|
|
57057
|
+
});
|
|
57058
|
+
}
|
|
57059
|
+
function emptyLedger(standard = CORE2) {
|
|
57060
|
+
return { tool: "ultra11y", kind: "verdict-ledger", schemaVersion: SCHEMA_VERSION, standard, entries: [] };
|
|
57061
|
+
}
|
|
57062
|
+
function isLedger(v) {
|
|
57063
|
+
const o = v;
|
|
57064
|
+
return !!o && typeof o === "object" && o.tool === "ultra11y" && o.kind === "verdict-ledger" && Array.isArray(o.entries);
|
|
57065
|
+
}
|
|
57066
|
+
function readLedger(path) {
|
|
57067
|
+
if (!existsSync21(path)) return void 0;
|
|
57068
|
+
try {
|
|
57069
|
+
const parsed = JSON.parse(readFileSync19(path, "utf8"));
|
|
57070
|
+
return isLedger(parsed) ? parsed : void 0;
|
|
57071
|
+
} catch {
|
|
57072
|
+
return void 0;
|
|
57073
|
+
}
|
|
57074
|
+
}
|
|
57075
|
+
function writeLedger(path, ledger) {
|
|
57076
|
+
mkdirSync10(dirname10(path), { recursive: true });
|
|
57077
|
+
const sorted = { ...ledger, entries: [...ledger.entries].sort((a, b) => a.criteriaId.localeCompare(b.criteriaId, "en", { numeric: true })) };
|
|
57078
|
+
writeFileSync11(path, `${JSON.stringify(sorted, null, 2)}
|
|
57079
|
+
`);
|
|
57080
|
+
}
|
|
57081
|
+
function entriesFrom(adj, accepted, date) {
|
|
57082
|
+
const out2 = [];
|
|
57083
|
+
for (const it of adj.items) {
|
|
57084
|
+
if (!accepted.has(it.criteriaId) || it.verdict === null) continue;
|
|
57085
|
+
out2.push({
|
|
57086
|
+
criteriaId: it.criteriaId,
|
|
57087
|
+
verdict: it.verdict,
|
|
57088
|
+
...it.justification?.trim() ? { justification: it.justification.trim() } : {},
|
|
57089
|
+
...it.reason ? { reason: it.reason } : {},
|
|
57090
|
+
...it.citations?.length ? { citations: it.citations } : {},
|
|
57091
|
+
...it.findings?.length ? { findings: it.findings } : {},
|
|
57092
|
+
...it.recommendations?.length ? { recommendations: it.recommendations } : {},
|
|
57093
|
+
evidenceFingerprint: evidenceFingerprint(it.evidence),
|
|
57094
|
+
evidenceCount: it.evidence.length,
|
|
57095
|
+
date,
|
|
57096
|
+
decidedBy: "agent"
|
|
57097
|
+
});
|
|
57098
|
+
}
|
|
57099
|
+
return out2;
|
|
57100
|
+
}
|
|
57101
|
+
function mergeLedger(existing, standard, fresh) {
|
|
57102
|
+
const base = existing && existing.standard === standard ? existing : emptyLedger(standard);
|
|
57103
|
+
const byId2 = new Map(base.entries.map((e) => [e.criteriaId, e]));
|
|
57104
|
+
for (const e of fresh) byId2.set(e.criteriaId, e);
|
|
57105
|
+
return { ...base, schemaVersion: SCHEMA_VERSION, standard, entries: [...byId2.values()] };
|
|
57106
|
+
}
|
|
57107
|
+
function replayLedger(audit2, ledger, opts = {}) {
|
|
57108
|
+
const standard = opts.standard ?? ledger.standard ?? CORE2;
|
|
57109
|
+
const worklist = buildAdjudicationWorklist(audit2, { cwd: opts.cwd, standard });
|
|
57110
|
+
const open = new Map(worklist.map((it) => [it.criteriaId, it]));
|
|
57111
|
+
const byId2 = new Map(ledger.entries.map((e) => [e.criteriaId, e]));
|
|
57112
|
+
const items = [];
|
|
57113
|
+
const fresh = [];
|
|
57114
|
+
const stale = [];
|
|
57115
|
+
const obsolete = [];
|
|
57116
|
+
const missing = [];
|
|
57117
|
+
const residualReasons = {};
|
|
57118
|
+
for (const e of ledger.entries) if (!open.has(e.criteriaId)) obsolete.push(e.criteriaId);
|
|
57119
|
+
for (const it of worklist) {
|
|
57120
|
+
const e = byId2.get(it.criteriaId);
|
|
57121
|
+
if (!e) {
|
|
57122
|
+
missing.push(it.criteriaId);
|
|
57123
|
+
residualReasons[it.criteriaId] = "No verdict in the ledger \u2014 this criterion has never been adjudicated. Run an adjudication pass to record one.";
|
|
57124
|
+
continue;
|
|
57125
|
+
}
|
|
57126
|
+
const now = evidenceFingerprint(it.evidence);
|
|
57127
|
+
if (now !== e.evidenceFingerprint) {
|
|
57128
|
+
stale.push(it.criteriaId);
|
|
57129
|
+
residualReasons[it.criteriaId] = `Ledger verdict is STALE \u2014 the evidence changed since it was recorded on ${e.date} (${e.evidenceCount} item(s) then, ${it.evidence.length} now). Re-adjudicate this criterion.`;
|
|
57130
|
+
continue;
|
|
57131
|
+
}
|
|
57132
|
+
fresh.push(it.criteriaId);
|
|
57133
|
+
items.push({
|
|
57134
|
+
...it,
|
|
57135
|
+
verdict: e.verdict,
|
|
57136
|
+
justification: e.justification ?? "",
|
|
57137
|
+
reason: e.reason ?? null,
|
|
57138
|
+
findings: reanchor(e.findings, it.evidence) ?? [],
|
|
57139
|
+
...e.citations ? { citations: reanchor(e.citations, it.evidence) ?? [] } : {},
|
|
57140
|
+
...e.recommendations ? { recommendations: reanchor(e.recommendations, it.evidence) ?? [] } : {},
|
|
57141
|
+
decidedBy: "agent"
|
|
57142
|
+
});
|
|
57143
|
+
}
|
|
57144
|
+
return {
|
|
57145
|
+
adj: { tool: "ultra11y", kind: "adjudication", schemaVersion: SCHEMA_VERSION, standard, auditDate: audit2.date, items },
|
|
57146
|
+
fresh,
|
|
57147
|
+
stale,
|
|
57148
|
+
obsolete,
|
|
57149
|
+
missing,
|
|
57150
|
+
residualReasons
|
|
57151
|
+
};
|
|
56842
57152
|
}
|
|
56843
57153
|
|
|
56844
57154
|
// src/llm.ts
|
|
@@ -57031,9 +57341,9 @@ function applyRawVerdicts(items, verdicts) {
|
|
|
57031
57341
|
|
|
57032
57342
|
// src/scan.ts
|
|
57033
57343
|
import { execFileSync as execFileSync6 } from "child_process";
|
|
57034
|
-
import { mkdtempSync as mkdtempSync2, writeFileSync as
|
|
57344
|
+
import { mkdtempSync as mkdtempSync2, writeFileSync as writeFileSync12, existsSync as existsSync23, statSync as statSync8, readdirSync as readdirSync5, rmSync as rmSync3, readFileSync as readFileSync20 } from "fs";
|
|
57035
57345
|
import { tmpdir as tmpdir2 } from "os";
|
|
57036
|
-
import { join as
|
|
57346
|
+
import { join as join37, resolve as resolve11 } from "path";
|
|
57037
57347
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
57038
57348
|
|
|
57039
57349
|
// src/axe-map.ts
|
|
@@ -57293,7 +57603,7 @@ async function crawlUrls(start2, opts) {
|
|
|
57293
57603
|
}
|
|
57294
57604
|
|
|
57295
57605
|
// src/sample.ts
|
|
57296
|
-
import { existsSync as
|
|
57606
|
+
import { existsSync as existsSync22 } from "fs";
|
|
57297
57607
|
function looksLikeTarget(u) {
|
|
57298
57608
|
return /^https?:\/\//i.test(u) || /^(\.\.?[/\\]|[/\\])/.test(u) || /\.x?html?$/i.test(u);
|
|
57299
57609
|
}
|
|
@@ -57329,7 +57639,7 @@ function validateSample(raw) {
|
|
|
57329
57639
|
if (p.auth !== void 0 && typeof p.auth !== "boolean") err2(`sample.pages[${i2}].auth`, "auth must be a boolean");
|
|
57330
57640
|
if (p.storageState !== void 0 && (typeof p.storageState !== "string" || p.storageState.trim() === "")) {
|
|
57331
57641
|
err2(`sample.pages[${i2}].storageState`, "storageState must be a non-empty file path string");
|
|
57332
|
-
} else if (typeof p.storageState === "string" && p.storageState.trim() !== "" && !
|
|
57642
|
+
} else if (typeof p.storageState === "string" && p.storageState.trim() !== "" && !existsSync22(p.storageState)) {
|
|
57333
57643
|
warn(
|
|
57334
57644
|
`sample.pages[${i2}].storageState`,
|
|
57335
57645
|
`storageState path not found: "${p.storageState}" (resolved from the current directory) \u2014 the scan will not be able to load this session`
|
|
@@ -57376,7 +57686,7 @@ function sampleScope(sample) {
|
|
|
57376
57686
|
...sample.transverse?.length ? { transverse: sample.transverse } : {}
|
|
57377
57687
|
};
|
|
57378
57688
|
}
|
|
57379
|
-
var
|
|
57689
|
+
var norm4 = (s) => s.normalize("NFD").replace(new RegExp("\\p{Diacritic}", "gu"), "").replace(/['’]/g, " ").toLowerCase();
|
|
57380
57690
|
var SHORT_KEYWORD = 5;
|
|
57381
57691
|
var escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
57382
57692
|
function keywordMatches(haystack, keyword) {
|
|
@@ -57384,12 +57694,12 @@ function keywordMatches(haystack, keyword) {
|
|
|
57384
57694
|
return new RegExp(`(^|[^a-z0-9])${escapeRe(keyword)}($|[^a-z0-9])`).test(haystack);
|
|
57385
57695
|
}
|
|
57386
57696
|
function lintSample(sample, methodology) {
|
|
57387
|
-
const haystacks = sample.pages.map((p) =>
|
|
57388
|
-
const transverseHay =
|
|
57697
|
+
const haystacks = sample.pages.map((p) => norm4([p.name, p.notes ?? "", p.url, p.id].join(" ")));
|
|
57698
|
+
const transverseHay = norm4((sample.transverse ?? []).join(" "));
|
|
57389
57699
|
const hasTransverse = (sample.transverse ?? []).length > 0;
|
|
57390
57700
|
const missing = [];
|
|
57391
57701
|
for (const kind of methodology.requiredKinds) {
|
|
57392
|
-
const kws = kind.keywords.map(
|
|
57702
|
+
const kws = kind.keywords.map(norm4).filter(Boolean);
|
|
57393
57703
|
const covered = haystacks.some((h) => kws.some((k) => keywordMatches(h, k))) || transverseHay !== "" && kws.some((k) => keywordMatches(transverseHay, k)) || hasTransverse && /transverse/.test(kind.id) || /representative|representatif/.test(kind.id) && sample.pages.length >= 2;
|
|
57394
57704
|
if (!covered) missing.push(kind);
|
|
57395
57705
|
}
|
|
@@ -57522,11 +57832,11 @@ function imageExists(tag) {
|
|
|
57522
57832
|
}
|
|
57523
57833
|
var CTX_PREFIX = "ultra11y-dyn-";
|
|
57524
57834
|
function buildImage(tag = IMAGE_TAG) {
|
|
57525
|
-
const ctx = mkdtempSync2(
|
|
57835
|
+
const ctx = mkdtempSync2(join37(tmpdir2(), CTX_PREFIX));
|
|
57526
57836
|
try {
|
|
57527
|
-
|
|
57528
|
-
|
|
57529
|
-
|
|
57837
|
+
writeFileSync12(join37(ctx, "runner.mjs"), RUNNER);
|
|
57838
|
+
writeFileSync12(join37(ctx, "package.json"), PKG);
|
|
57839
|
+
writeFileSync12(join37(ctx, "Dockerfile"), DOCKERFILE);
|
|
57530
57840
|
execFileSync6("docker", ["build", "-t", tag, ctx], { stdio: "inherit", timeout: 9e5 });
|
|
57531
57841
|
} finally {
|
|
57532
57842
|
rmSync3(ctx, { recursive: true, force: true });
|
|
@@ -57537,7 +57847,7 @@ function cleanTempContexts() {
|
|
|
57537
57847
|
const dir = tmpdir2();
|
|
57538
57848
|
for (const name2 of readdirSync5(dir)) {
|
|
57539
57849
|
if (!name2.startsWith(CTX_PREFIX)) continue;
|
|
57540
|
-
rmSync3(
|
|
57850
|
+
rmSync3(join37(dir, name2), { recursive: true, force: true });
|
|
57541
57851
|
removed++;
|
|
57542
57852
|
}
|
|
57543
57853
|
return removed;
|
|
@@ -57603,7 +57913,7 @@ var PROBE_FIELDS = [
|
|
|
57603
57913
|
function runRunner(target, isFile, tag, snapshot = true) {
|
|
57604
57914
|
const args2 = ["run", "--rm"];
|
|
57605
57915
|
if (!snapshot) args2.push("-e", "ULTRA11Y_SNAPSHOT=0");
|
|
57606
|
-
if (isFile) args2.push("-v", `${
|
|
57916
|
+
if (isFile) args2.push("-v", `${resolve11(target)}:${MOUNT}:ro`);
|
|
57607
57917
|
args2.push(tag, isFile ? MOUNT : target);
|
|
57608
57918
|
let stdout;
|
|
57609
57919
|
try {
|
|
@@ -57686,7 +57996,7 @@ function toDynamicResult(out2, target, lang = "en", engine = "axe-core@playwrigh
|
|
|
57686
57996
|
var DOCKER_TESTED_SCS = ["1.4.10"];
|
|
57687
57997
|
function runScan(opts) {
|
|
57688
57998
|
const isUrl3 = /^https?:\/\//i.test(opts.target);
|
|
57689
|
-
if (!isUrl3 && !
|
|
57999
|
+
if (!isUrl3 && !existsSync23(opts.target)) {
|
|
57690
58000
|
throw new Error(`File not found: ${opts.target}. Pass an http(s):// URL or an existing HTML file.`);
|
|
57691
58001
|
}
|
|
57692
58002
|
if (!dockerAvailable()) {
|
|
@@ -57694,7 +58004,7 @@ function runScan(opts) {
|
|
|
57694
58004
|
}
|
|
57695
58005
|
const tag = opts.tag ?? IMAGE_TAG;
|
|
57696
58006
|
if (!imageExists(tag)) buildImage(tag);
|
|
57697
|
-
const isFile = !isUrl3 &&
|
|
58007
|
+
const isFile = !isUrl3 && existsSync23(opts.target) && statSync8(opts.target).isFile();
|
|
57698
58008
|
const out2 = runRunner(opts.target, isFile, tag, Boolean(opts.snapshotRoot));
|
|
57699
58009
|
const id = opts.snapshotRoot ? writeRunnerSnapshot(opts.snapshotRoot, out2, opts.target) : void 0;
|
|
57700
58010
|
return { ...toDynamicResult(out2, opts.target), testedScs: [...DOCKER_TESTED_SCS], ...id ? { snapshots: [id] } : {} };
|
|
@@ -57781,10 +58091,10 @@ async function runCrawlScan(opts) {
|
|
|
57781
58091
|
var sevRank = { bloquant: 3, majeur: 2, mineur: 1 };
|
|
57782
58092
|
function resolveHostAnchor(file, snippet2) {
|
|
57783
58093
|
const s = snippet2?.trim();
|
|
57784
|
-
if (!s || !
|
|
58094
|
+
if (!s || !existsSync23(file)) return null;
|
|
57785
58095
|
let source;
|
|
57786
58096
|
try {
|
|
57787
|
-
source =
|
|
58097
|
+
source = readFileSync20(file, "utf8");
|
|
57788
58098
|
} catch {
|
|
57789
58099
|
return null;
|
|
57790
58100
|
}
|
|
@@ -57800,12 +58110,12 @@ function resolveHostAnchor(file, snippet2) {
|
|
|
57800
58110
|
idx = source.indexOf(openMatch[0]);
|
|
57801
58111
|
if (idx >= 0) return at(idx, openMatch[0].length);
|
|
57802
58112
|
}
|
|
57803
|
-
const
|
|
57804
|
-
const needle =
|
|
58113
|
+
const norm5 = (t2) => t2.replace(/\s+/g, " ").trim();
|
|
58114
|
+
const needle = norm5(openMatch ? openMatch[0] : s);
|
|
57805
58115
|
if (needle) {
|
|
57806
58116
|
const lines = source.split("\n");
|
|
57807
58117
|
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
57808
|
-
if (
|
|
58118
|
+
if (norm5(lines[i2]).includes(needle)) {
|
|
57809
58119
|
const start2 = starts[i2] ?? 0;
|
|
57810
58120
|
return { line: i2 + 1, col: 1, start: start2, end: start2 + lines[i2].length };
|
|
57811
58121
|
}
|
|
@@ -57919,9 +58229,9 @@ function mergeSnapshotAudit(base, snap) {
|
|
|
57919
58229
|
}
|
|
57920
58230
|
|
|
57921
58231
|
// src/scan-local.ts
|
|
57922
|
-
import { existsSync as
|
|
58232
|
+
import { existsSync as existsSync24, statSync as statSync9 } from "fs";
|
|
57923
58233
|
import { createRequire } from "module";
|
|
57924
|
-
import { resolve as
|
|
58234
|
+
import { resolve as resolve12 } from "path";
|
|
57925
58235
|
var LOCAL_ENGINE = "axe-core@playwright (local)";
|
|
57926
58236
|
var LOCAL_TESTED_SCS = ["1.4.4", "1.4.10", "1.4.12", "2.4.7", "1.4.13"];
|
|
57927
58237
|
function localTestedScs(interact) {
|
|
@@ -57931,7 +58241,7 @@ var PW_SPEC = "@playwright/test";
|
|
|
57931
58241
|
var AXE_SPEC = "@axe-core/playwright";
|
|
57932
58242
|
function localAvailable(cwd) {
|
|
57933
58243
|
try {
|
|
57934
|
-
const req = createRequire(
|
|
58244
|
+
const req = createRequire(resolve12(cwd, "package.json"));
|
|
57935
58245
|
req.resolve(PW_SPEC);
|
|
57936
58246
|
req.resolve(AXE_SPEC);
|
|
57937
58247
|
return true;
|
|
@@ -57943,7 +58253,7 @@ function resolveLocalDeps(cwd) {
|
|
|
57943
58253
|
let chromium;
|
|
57944
58254
|
let AxeBuilder;
|
|
57945
58255
|
try {
|
|
57946
|
-
const req = createRequire(
|
|
58256
|
+
const req = createRequire(resolve12(cwd, "package.json"));
|
|
57947
58257
|
const pw = req(PW_SPEC);
|
|
57948
58258
|
const axeMod = req(AXE_SPEC);
|
|
57949
58259
|
chromium = pw.chromium;
|
|
@@ -58442,7 +58752,7 @@ async function runOnPage(browser, AxeBuilder, target, isFile, opts) {
|
|
|
58442
58752
|
const page = await context.newPage();
|
|
58443
58753
|
const empty = [];
|
|
58444
58754
|
try {
|
|
58445
|
-
const url = isFile ? "file://" +
|
|
58755
|
+
const url = isFile ? "file://" + resolve12(target) : target;
|
|
58446
58756
|
const response = await page.goto(url, { waitUntil: "load", timeout: 45e3 });
|
|
58447
58757
|
await page.waitForLoadState("networkidle", { timeout: 8e3 }).catch(() => {
|
|
58448
58758
|
});
|
|
@@ -58514,7 +58824,7 @@ async function runOnPage(browser, AxeBuilder, target, isFile, opts) {
|
|
|
58514
58824
|
}
|
|
58515
58825
|
async function runScanLocal(opts) {
|
|
58516
58826
|
const isUrl3 = /^https?:\/\//i.test(opts.target);
|
|
58517
|
-
if (!isUrl3 && !
|
|
58827
|
+
if (!isUrl3 && !existsSync24(opts.target)) {
|
|
58518
58828
|
throw new Error(`File not found: ${opts.target}. Pass an http(s):// URL or an existing HTML file.`);
|
|
58519
58829
|
}
|
|
58520
58830
|
const isFile = !isUrl3 && statSync9(opts.target).isFile();
|
|
@@ -58588,7 +58898,7 @@ async function runSampleScanLocal(pages, opts) {
|
|
|
58588
58898
|
lang,
|
|
58589
58899
|
snapshot: Boolean(opts.snapshotRoot)
|
|
58590
58900
|
});
|
|
58591
|
-
const requestedUrl = isFile ? "file://" +
|
|
58901
|
+
const requestedUrl = isFile ? "file://" + resolve12(page.url) : page.url;
|
|
58592
58902
|
const landedUrl = out2.landedUrl ?? out2.url;
|
|
58593
58903
|
if (!landedOnRequestedPage(requestedUrl, landedUrl)) {
|
|
58594
58904
|
redirected.push({ id: page.id, name: page.name, requested: page.url, landed: landedUrl, reason: "redirect" });
|
|
@@ -58817,7 +59127,7 @@ function diffSides(left, right) {
|
|
|
58817
59127
|
}
|
|
58818
59128
|
|
|
58819
59129
|
// src/fix.ts
|
|
58820
|
-
import { writeFileSync as
|
|
59130
|
+
import { writeFileSync as writeFileSync13 } from "fs";
|
|
58821
59131
|
|
|
58822
59132
|
// src/fix/edits.ts
|
|
58823
59133
|
function openTag(source, start2) {
|
|
@@ -59031,7 +59341,7 @@ function fixOne(file, source, opts, canWrite = true) {
|
|
|
59031
59341
|
regression = after.length > findings.length || after.some((f) => !beforeKinds.has(f.ruleId));
|
|
59032
59342
|
if (opts.write && !regression && file !== "<stdin>") {
|
|
59033
59343
|
if (canWrite) {
|
|
59034
|
-
|
|
59344
|
+
writeFileSync13(file, output);
|
|
59035
59345
|
written = true;
|
|
59036
59346
|
} else {
|
|
59037
59347
|
skippedPartialStage = true;
|
|
@@ -59113,9 +59423,9 @@ function fixSummary(r, lang = "fr", write = false) {
|
|
|
59113
59423
|
}
|
|
59114
59424
|
|
|
59115
59425
|
// src/init.ts
|
|
59116
|
-
import { writeFileSync as
|
|
59426
|
+
import { writeFileSync as writeFileSync14, mkdirSync as mkdirSync11, chmodSync, realpathSync as realpathSync2 } from "fs";
|
|
59117
59427
|
import { execFileSync as execFileSync7 } from "child_process";
|
|
59118
|
-
import { join as
|
|
59428
|
+
import { join as join38, relative as relative4, sep as sep4 } from "path";
|
|
59119
59429
|
var EN_SEV = { bloquant: "blocking", majeur: "major", mineur: "minor" };
|
|
59120
59430
|
function repoRoot() {
|
|
59121
59431
|
try {
|
|
@@ -59233,24 +59543,24 @@ jobs:
|
|
|
59233
59543
|
`;
|
|
59234
59544
|
}
|
|
59235
59545
|
function writeHook(root, enginePath, failOn, mode = "staged") {
|
|
59236
|
-
const dir =
|
|
59237
|
-
|
|
59238
|
-
const path =
|
|
59239
|
-
|
|
59546
|
+
const dir = join38(root, ".git", "hooks");
|
|
59547
|
+
mkdirSync11(dir, { recursive: true });
|
|
59548
|
+
const path = join38(dir, "pre-commit");
|
|
59549
|
+
writeFileSync14(path, mode === "baseline" ? hookScript(enginePath, failOn) : stagedHookScript(enginePath, failOn));
|
|
59240
59550
|
chmodSync(path, 493);
|
|
59241
59551
|
return path;
|
|
59242
59552
|
}
|
|
59243
59553
|
function writeCi(root, enginePath, failOn) {
|
|
59244
|
-
const dir =
|
|
59245
|
-
|
|
59246
|
-
const path =
|
|
59247
|
-
|
|
59554
|
+
const dir = join38(root, ".github", "workflows");
|
|
59555
|
+
mkdirSync11(dir, { recursive: true });
|
|
59556
|
+
const path = join38(dir, "a11y.yml");
|
|
59557
|
+
writeFileSync14(path, ciWorkflow(enginePath, failOn));
|
|
59248
59558
|
return path;
|
|
59249
59559
|
}
|
|
59250
59560
|
|
|
59251
59561
|
// src/install/json-edit.ts
|
|
59252
|
-
import { copyFileSync as copyFileSync2, existsSync as
|
|
59253
|
-
import { dirname as
|
|
59562
|
+
import { copyFileSync as copyFileSync2, existsSync as existsSync25, mkdirSync as mkdirSync12, readFileSync as readFileSync21, renameSync as renameSync2, rmSync as rmSync4, writeFileSync as writeFileSync15 } from "fs";
|
|
59563
|
+
import { dirname as dirname11 } from "path";
|
|
59254
59564
|
var SettingsParseError = class extends Error {
|
|
59255
59565
|
constructor(path, cause) {
|
|
59256
59566
|
super(`${path} is not valid JSON (${cause}) \u2014 fix or move it, then run install again. It has NOT been modified.`);
|
|
@@ -59263,16 +59573,16 @@ function stamp() {
|
|
|
59263
59573
|
return (/* @__PURE__ */ new Date()).toISOString().replaceAll(/[:.]/g, "-");
|
|
59264
59574
|
}
|
|
59265
59575
|
function writeTextWithBackup(path, content, marker = "ultra11y") {
|
|
59266
|
-
if (
|
|
59267
|
-
|
|
59576
|
+
if (existsSync25(path) && readFileSync21(path, "utf8") === content) return { path, changed: false };
|
|
59577
|
+
mkdirSync12(dirname11(path), { recursive: true });
|
|
59268
59578
|
let backup;
|
|
59269
|
-
if (
|
|
59579
|
+
if (existsSync25(path)) {
|
|
59270
59580
|
backup = `${path}.${marker}-backup-${stamp()}`;
|
|
59271
59581
|
copyFileSync2(path, backup);
|
|
59272
59582
|
}
|
|
59273
59583
|
const tmp = `${path}.${process.pid}.tmp`;
|
|
59274
59584
|
try {
|
|
59275
|
-
|
|
59585
|
+
writeFileSync15(tmp, content, { mode: 420 });
|
|
59276
59586
|
renameSync2(tmp, path);
|
|
59277
59587
|
} catch (e) {
|
|
59278
59588
|
rmSync4(tmp, { force: true });
|
|
@@ -59282,8 +59592,8 @@ function writeTextWithBackup(path, content, marker = "ultra11y") {
|
|
|
59282
59592
|
}
|
|
59283
59593
|
function editJsonFile(path, mutate) {
|
|
59284
59594
|
let root = {};
|
|
59285
|
-
if (
|
|
59286
|
-
const raw =
|
|
59595
|
+
if (existsSync25(path)) {
|
|
59596
|
+
const raw = readFileSync21(path, "utf8");
|
|
59287
59597
|
if (raw.trim()) {
|
|
59288
59598
|
let parsed;
|
|
59289
59599
|
try {
|
|
@@ -59299,13 +59609,13 @@ function editJsonFile(path, mutate) {
|
|
|
59299
59609
|
}
|
|
59300
59610
|
const before = JSON.stringify(root);
|
|
59301
59611
|
mutate(root);
|
|
59302
|
-
if (JSON.stringify(root) === before &&
|
|
59612
|
+
if (JSON.stringify(root) === before && existsSync25(path)) return { path, changed: false };
|
|
59303
59613
|
return writeTextWithBackup(path, `${JSON.stringify(root, null, 2)}
|
|
59304
59614
|
`);
|
|
59305
59615
|
}
|
|
59306
59616
|
function readJsonSafe(path) {
|
|
59307
59617
|
try {
|
|
59308
|
-
const parsed = JSON.parse(
|
|
59618
|
+
const parsed = JSON.parse(readFileSync21(path, "utf8"));
|
|
59309
59619
|
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
59310
59620
|
} catch {
|
|
59311
59621
|
return null;
|
|
@@ -59346,7 +59656,7 @@ function claudeCodeWired(settingsPath) {
|
|
|
59346
59656
|
}
|
|
59347
59657
|
|
|
59348
59658
|
// src/install/text-edit.ts
|
|
59349
|
-
import { existsSync as
|
|
59659
|
+
import { existsSync as existsSync26, readFileSync as readFileSync23, rmSync as rmSync5 } from "fs";
|
|
59350
59660
|
var BLOCK_BEGIN = "<!-- BEGIN ultra11y (managed by `ultra11y install --agents-md`; edit outside this block) -->";
|
|
59351
59661
|
var BLOCK_END = "<!-- END ultra11y -->";
|
|
59352
59662
|
var blockRe = () => new RegExp(`${escapeRe2(BLOCK_BEGIN)}[\\s\\S]*?${escapeRe2(BLOCK_END)}\\n?`);
|
|
@@ -59358,15 +59668,15 @@ function upsertManagedBlock(path, body3) {
|
|
|
59358
59668
|
${body3.trim()}
|
|
59359
59669
|
${BLOCK_END}
|
|
59360
59670
|
`;
|
|
59361
|
-
if (!
|
|
59362
|
-
const current =
|
|
59671
|
+
if (!existsSync26(path)) return writeTextWithBackup(path, block);
|
|
59672
|
+
const current = readFileSync23(path, "utf8");
|
|
59363
59673
|
if (blockRe().test(current)) return writeTextWithBackup(path, current.replace(blockRe(), block));
|
|
59364
59674
|
const sep8 = current.endsWith("\n\n") ? "" : current.endsWith("\n") ? "\n" : "\n\n";
|
|
59365
59675
|
return writeTextWithBackup(path, `${current}${sep8}${block}`);
|
|
59366
59676
|
}
|
|
59367
59677
|
function removeManagedBlock(path) {
|
|
59368
|
-
if (!
|
|
59369
|
-
const current =
|
|
59678
|
+
if (!existsSync26(path)) return { path, changed: false };
|
|
59679
|
+
const current = readFileSync23(path, "utf8");
|
|
59370
59680
|
if (!blockRe().test(current)) return { path, changed: false };
|
|
59371
59681
|
const rest = current.replace(blockRe(), "");
|
|
59372
59682
|
if (rest.trim() === "") {
|
|
@@ -59376,13 +59686,13 @@ function removeManagedBlock(path) {
|
|
|
59376
59686
|
return writeTextWithBackup(path, rest.replace(/\n+$/, "\n"));
|
|
59377
59687
|
}
|
|
59378
59688
|
function hasManagedBlock(path) {
|
|
59379
|
-
return
|
|
59689
|
+
return existsSync26(path) && blockRe().test(readFileSync23(path, "utf8"));
|
|
59380
59690
|
}
|
|
59381
59691
|
|
|
59382
59692
|
// src/install/agents-md.ts
|
|
59383
|
-
import { join as
|
|
59693
|
+
import { join as join39 } from "path";
|
|
59384
59694
|
function agentsMdPath(root) {
|
|
59385
|
-
return
|
|
59695
|
+
return join39(root, "AGENTS.md");
|
|
59386
59696
|
}
|
|
59387
59697
|
function agentsMdBlock(root) {
|
|
59388
59698
|
const e = engineInvocation(root);
|
|
@@ -59437,25 +59747,25 @@ function agentsMdWired(root) {
|
|
|
59437
59747
|
}
|
|
59438
59748
|
|
|
59439
59749
|
// src/install/codex.ts
|
|
59440
|
-
import { existsSync as
|
|
59441
|
-
import { join as
|
|
59750
|
+
import { existsSync as existsSync28, readFileSync as readFileSync25 } from "fs";
|
|
59751
|
+
import { join as join41 } from "path";
|
|
59442
59752
|
|
|
59443
59753
|
// src/install/paths.ts
|
|
59444
|
-
import { copyFileSync as copyFileSync3, cpSync, existsSync as
|
|
59754
|
+
import { copyFileSync as copyFileSync3, cpSync, existsSync as existsSync27, mkdirSync as mkdirSync13, readFileSync as readFileSync24, realpathSync as realpathSync3, statSync as statSync10 } from "fs";
|
|
59445
59755
|
import { homedir as homedir2 } from "os";
|
|
59446
|
-
import { dirname as
|
|
59756
|
+
import { dirname as dirname12, join as join40 } from "path";
|
|
59447
59757
|
function codexHome() {
|
|
59448
|
-
return process.env.CODEX_HOME ||
|
|
59758
|
+
return process.env.CODEX_HOME || join40(homedir2(), ".codex");
|
|
59449
59759
|
}
|
|
59450
59760
|
function opencodeConfigDir() {
|
|
59451
59761
|
const xdg = process.env.XDG_CONFIG_HOME;
|
|
59452
|
-
return
|
|
59762
|
+
return join40(xdg && xdg !== "" ? xdg : join40(homedir2(), ".config"), "opencode");
|
|
59453
59763
|
}
|
|
59454
59764
|
function claudeSettingsPath(project, cwd = process.cwd()) {
|
|
59455
|
-
return project ?
|
|
59765
|
+
return project ? join40(cwd, ".claude", "settings.json") : join40(homedir2(), ".claude", "settings.json");
|
|
59456
59766
|
}
|
|
59457
59767
|
function pinnedEnginePath() {
|
|
59458
|
-
return
|
|
59768
|
+
return join40(homedir2(), ".ultra11y", "bin", "ultra11y.mjs");
|
|
59459
59769
|
}
|
|
59460
59770
|
function installedCliCommand() {
|
|
59461
59771
|
const override = process.env.ULTRA11Y_BIN;
|
|
@@ -59470,7 +59780,7 @@ function installedCliCommand() {
|
|
|
59470
59780
|
if (source && !ephemeral) return `node ${JSON.stringify(source)}`;
|
|
59471
59781
|
const pin = pinnedEnginePath();
|
|
59472
59782
|
if (source) {
|
|
59473
|
-
|
|
59783
|
+
mkdirSync13(join40(pin, ".."), { recursive: true });
|
|
59474
59784
|
copyFileSync3(source, pin);
|
|
59475
59785
|
}
|
|
59476
59786
|
return `node ${JSON.stringify(pin)}`;
|
|
@@ -59478,37 +59788,37 @@ function installedCliCommand() {
|
|
|
59478
59788
|
function packageRoot() {
|
|
59479
59789
|
let dir;
|
|
59480
59790
|
try {
|
|
59481
|
-
dir =
|
|
59791
|
+
dir = dirname12(realpathSync3(process.argv[1] ?? ""));
|
|
59482
59792
|
} catch {
|
|
59483
59793
|
return null;
|
|
59484
59794
|
}
|
|
59485
59795
|
for (let i2 = 0; i2 < 6 && dir && dir !== "/"; i2++) {
|
|
59486
|
-
if (
|
|
59487
|
-
dir =
|
|
59796
|
+
if (existsSync27(join40(dir, "skills", "review-a11y", "SKILL.md"))) return dir;
|
|
59797
|
+
dir = dirname12(dir);
|
|
59488
59798
|
}
|
|
59489
59799
|
return null;
|
|
59490
59800
|
}
|
|
59491
59801
|
function bundledSkillsDir() {
|
|
59492
59802
|
const root = packageRoot();
|
|
59493
|
-
const p = root ?
|
|
59494
|
-
return p &&
|
|
59803
|
+
const p = root ? join40(root, "skills") : null;
|
|
59804
|
+
return p && existsSync27(p) ? p : null;
|
|
59495
59805
|
}
|
|
59496
59806
|
function bundledOpencodePlugin() {
|
|
59497
59807
|
const root = packageRoot();
|
|
59498
|
-
const p = root ?
|
|
59499
|
-
return p &&
|
|
59808
|
+
const p = root ? join40(root, ".opencode", "plugins", "ultra11y.js") : null;
|
|
59809
|
+
return p && existsSync27(p) ? p : null;
|
|
59500
59810
|
}
|
|
59501
59811
|
function copySkillsInto(source, dest) {
|
|
59502
59812
|
const out2 = [];
|
|
59503
59813
|
for (const name2 of ["ultra11y", "review-a11y"]) {
|
|
59504
|
-
const from =
|
|
59505
|
-
if (!
|
|
59506
|
-
const to =
|
|
59507
|
-
const same = ["SKILL.md",
|
|
59508
|
-
const a =
|
|
59509
|
-
const b =
|
|
59814
|
+
const from = join40(source, name2);
|
|
59815
|
+
if (!existsSync27(from)) continue;
|
|
59816
|
+
const to = join40(dest, name2);
|
|
59817
|
+
const same = ["SKILL.md", join40("scripts", "ultra11y.mjs")].every((f) => {
|
|
59818
|
+
const a = join40(from, f);
|
|
59819
|
+
const b = join40(to, f);
|
|
59510
59820
|
try {
|
|
59511
|
-
return
|
|
59821
|
+
return existsSync27(b) && statSync10(a).size === statSync10(b).size && readFileSync24(a).equals(readFileSync24(b));
|
|
59512
59822
|
} catch {
|
|
59513
59823
|
return false;
|
|
59514
59824
|
}
|
|
@@ -59558,7 +59868,7 @@ function featuresBody(content) {
|
|
|
59558
59868
|
function installCodex({ codexDir, command, skillsSource }) {
|
|
59559
59869
|
const reports = [];
|
|
59560
59870
|
reports.push(
|
|
59561
|
-
editJsonFile(
|
|
59871
|
+
editJsonFile(join41(codexDir, "hooks.json"), (root) => {
|
|
59562
59872
|
const hooks = root.hooks ??= {};
|
|
59563
59873
|
hooks.PreToolUse = [
|
|
59564
59874
|
...withoutOurs2(hooks.PreToolUse),
|
|
@@ -59566,11 +59876,11 @@ function installCodex({ codexDir, command, skillsSource }) {
|
|
|
59566
59876
|
];
|
|
59567
59877
|
})
|
|
59568
59878
|
);
|
|
59569
|
-
const configPath =
|
|
59570
|
-
const current =
|
|
59879
|
+
const configPath = join41(codexDir, "config.toml");
|
|
59880
|
+
const current = existsSync28(configPath) ? readFileSync25(configPath, "utf8") : "";
|
|
59571
59881
|
const edit = enableHooksFeature(current);
|
|
59572
59882
|
if (edit.changed) reports.push(writeTextWithBackup(configPath, edit.content));
|
|
59573
|
-
if (skillsSource) reports.push(...copySkillsInto(skillsSource,
|
|
59883
|
+
if (skillsSource) reports.push(...copySkillsInto(skillsSource, join41(codexDir, "skills")));
|
|
59574
59884
|
return {
|
|
59575
59885
|
reports,
|
|
59576
59886
|
guidance: [
|
|
@@ -59585,8 +59895,8 @@ function installCodex({ codexDir, command, skillsSource }) {
|
|
|
59585
59895
|
}
|
|
59586
59896
|
function uninstallCodex({ codexDir }) {
|
|
59587
59897
|
const reports = [];
|
|
59588
|
-
const hooksPath =
|
|
59589
|
-
if (
|
|
59898
|
+
const hooksPath = join41(codexDir, "hooks.json");
|
|
59899
|
+
if (existsSync28(hooksPath)) {
|
|
59590
59900
|
reports.push(
|
|
59591
59901
|
editJsonFile(hooksPath, (root) => {
|
|
59592
59902
|
const hooks = root.hooks;
|
|
@@ -59601,33 +59911,33 @@ function uninstallCodex({ codexDir }) {
|
|
|
59601
59911
|
return { reports, guidance: ["codex: `[features] hooks = true` was left enabled \u2014 other hooks may depend on it."] };
|
|
59602
59912
|
}
|
|
59603
59913
|
function codexWired(codexDir) {
|
|
59604
|
-
const root = readJsonSafe(
|
|
59914
|
+
const root = readJsonSafe(join41(codexDir, "hooks.json"));
|
|
59605
59915
|
const groups = root?.hooks?.PreToolUse;
|
|
59606
59916
|
if (!Array.isArray(groups)) return 0;
|
|
59607
59917
|
return groups.reduce((n, g) => n + (g.hooks ?? []).filter((h) => String(h.command ?? "").includes(CODEX_MARKER)).length, 0);
|
|
59608
59918
|
}
|
|
59609
59919
|
function codexHooksEnabled(codexDir) {
|
|
59610
|
-
const p =
|
|
59611
|
-
return
|
|
59920
|
+
const p = join41(codexDir, "config.toml");
|
|
59921
|
+
return existsSync28(p) && HOOKS_ON.test(featuresBody(readFileSync25(p, "utf8")));
|
|
59612
59922
|
}
|
|
59613
59923
|
|
|
59614
59924
|
// src/install/opencode.ts
|
|
59615
|
-
import { existsSync as
|
|
59616
|
-
import { join as
|
|
59925
|
+
import { existsSync as existsSync29, readFileSync as readFileSync26, rmSync as rmSync6 } from "fs";
|
|
59926
|
+
import { join as join42 } from "path";
|
|
59617
59927
|
var OPENCODE_MARKER = "ULTRA11Y_OPENCODE_PLUGIN";
|
|
59618
59928
|
function pluginTarget(configDir) {
|
|
59619
|
-
return
|
|
59929
|
+
return join42(configDir, "plugin", "ultra11y.js");
|
|
59620
59930
|
}
|
|
59621
59931
|
function installOpencode({ configDir, pluginSource, skillsSource, enginePath }) {
|
|
59622
59932
|
const target = pluginTarget(configDir);
|
|
59623
|
-
if (
|
|
59933
|
+
if (existsSync29(target) && !readFileSync26(target, "utf8").includes(OPENCODE_MARKER)) {
|
|
59624
59934
|
throw new Error(`refusing to overwrite ${target}: it is not a file ultra11y wrote. Remove it manually if you want the plugin here.`);
|
|
59625
59935
|
}
|
|
59626
|
-
const reports = [writeTextWithBackup(target,
|
|
59627
|
-
if (enginePath &&
|
|
59628
|
-
reports.push(writeTextWithBackup(
|
|
59936
|
+
const reports = [writeTextWithBackup(target, readFileSync26(pluginSource, "utf8"))];
|
|
59937
|
+
if (enginePath && existsSync29(enginePath)) {
|
|
59938
|
+
reports.push(writeTextWithBackup(join42(configDir, "plugin", "ultra11y.mjs"), readFileSync26(enginePath, "utf8")));
|
|
59629
59939
|
}
|
|
59630
|
-
if (skillsSource) reports.push(...copySkillsInto(skillsSource,
|
|
59940
|
+
if (skillsSource) reports.push(...copySkillsInto(skillsSource, join42(configDir, "skills")));
|
|
59631
59941
|
return {
|
|
59632
59942
|
reports,
|
|
59633
59943
|
guidance: [
|
|
@@ -59640,13 +59950,13 @@ function installOpencode({ configDir, pluginSource, skillsSource, enginePath })
|
|
|
59640
59950
|
function uninstallOpencode({ configDir }) {
|
|
59641
59951
|
const reports = [];
|
|
59642
59952
|
const target = pluginTarget(configDir);
|
|
59643
|
-
if (
|
|
59953
|
+
if (existsSync29(target) && readFileSync26(target, "utf8").includes(OPENCODE_MARKER)) {
|
|
59644
59954
|
rmSync6(target, { force: true });
|
|
59645
|
-
rmSync6(
|
|
59955
|
+
rmSync6(join42(configDir, "plugin", "ultra11y.mjs"), { force: true });
|
|
59646
59956
|
reports.push({ path: target, changed: true });
|
|
59647
59957
|
}
|
|
59648
|
-
const configPath =
|
|
59649
|
-
if (
|
|
59958
|
+
const configPath = join42(configDir, "opencode.json");
|
|
59959
|
+
if (existsSync29(configPath)) {
|
|
59650
59960
|
reports.push(
|
|
59651
59961
|
editJsonFile(configPath, (root) => {
|
|
59652
59962
|
if (!Array.isArray(root.plugin)) return;
|
|
@@ -59660,9 +59970,9 @@ function uninstallOpencode({ configDir }) {
|
|
|
59660
59970
|
}
|
|
59661
59971
|
function opencodeWired(configDir) {
|
|
59662
59972
|
const target = pluginTarget(configDir);
|
|
59663
|
-
if (
|
|
59973
|
+
if (existsSync29(target) && readFileSync26(target, "utf8").includes(OPENCODE_MARKER)) return true;
|
|
59664
59974
|
try {
|
|
59665
|
-
const cfg = JSON.parse(
|
|
59975
|
+
const cfg = JSON.parse(readFileSync26(join42(configDir, "opencode.json"), "utf8"));
|
|
59666
59976
|
return Array.isArray(cfg.plugin) && cfg.plugin.some((p) => /^ultra11y(@|$)/.test(String(p)));
|
|
59667
59977
|
} catch {
|
|
59668
59978
|
return false;
|
|
@@ -59670,8 +59980,8 @@ function opencodeWired(configDir) {
|
|
|
59670
59980
|
}
|
|
59671
59981
|
|
|
59672
59982
|
// src/install/index.ts
|
|
59673
|
-
import { existsSync as
|
|
59674
|
-
import { join as
|
|
59983
|
+
import { existsSync as existsSync30 } from "fs";
|
|
59984
|
+
import { join as join43 } from "path";
|
|
59675
59985
|
var ALL_TARGETS = ["claude-code", "codex", "opencode"];
|
|
59676
59986
|
function parseTargets(flags2) {
|
|
59677
59987
|
const picked = /* @__PURE__ */ new Set();
|
|
@@ -59710,7 +60020,7 @@ function installForTargets(opts) {
|
|
|
59710
60020
|
const out3 = installCodex({ codexDir: codexHome(), command: cmd(), skillsSource: opts.skills === false ? null : bundledSkillsDir() });
|
|
59711
60021
|
r.reports.push(...out3.reports);
|
|
59712
60022
|
r.guidance.push(...out3.guidance);
|
|
59713
|
-
} else r.reports.push({ path:
|
|
60023
|
+
} else r.reports.push({ path: join43(codexHome(), "hooks.json"), changed: false });
|
|
59714
60024
|
break;
|
|
59715
60025
|
}
|
|
59716
60026
|
case "opencode": {
|
|
@@ -59719,18 +60029,18 @@ function installForTargets(opts) {
|
|
|
59719
60029
|
r.error = "opencode: this engine was not run from a package carrying .opencode/plugins/ultra11y.js \u2014 install ultra11y from npm and retry.";
|
|
59720
60030
|
break;
|
|
59721
60031
|
}
|
|
59722
|
-
const configDir = opts.project === true ?
|
|
60032
|
+
const configDir = opts.project === true ? join43(cwd, ".opencode") : opencodeConfigDir();
|
|
59723
60033
|
if (!opts.dryRun) {
|
|
59724
|
-
const engine =
|
|
60034
|
+
const engine = existsSync30(pinnedEnginePath()) ? pinnedEnginePath() : join43(packageRoot() ?? "", "scripts", "ultra11y.mjs");
|
|
59725
60035
|
const out3 = installOpencode({
|
|
59726
60036
|
configDir,
|
|
59727
60037
|
pluginSource: source,
|
|
59728
60038
|
skillsSource: opts.skills === false ? null : bundledSkillsDir(),
|
|
59729
|
-
enginePath:
|
|
60039
|
+
enginePath: existsSync30(engine) ? engine : null
|
|
59730
60040
|
});
|
|
59731
60041
|
r.reports.push(...out3.reports);
|
|
59732
60042
|
r.guidance.push(...out3.guidance);
|
|
59733
|
-
} else r.reports.push({ path:
|
|
60043
|
+
} else r.reports.push({ path: join43(configDir, "plugin", "ultra11y.js"), changed: false });
|
|
59734
60044
|
break;
|
|
59735
60045
|
}
|
|
59736
60046
|
case "agents-md": {
|
|
@@ -59768,7 +60078,7 @@ function uninstallForTargets(opts) {
|
|
|
59768
60078
|
break;
|
|
59769
60079
|
}
|
|
59770
60080
|
case "opencode": {
|
|
59771
|
-
const out3 = uninstallOpencode({ configDir: opts.project === true ?
|
|
60081
|
+
const out3 = uninstallOpencode({ configDir: opts.project === true ? join43(cwd, ".opencode") : opencodeConfigDir() });
|
|
59772
60082
|
r.reports.push(...out3.reports);
|
|
59773
60083
|
r.guidance.push(...out3.guidance);
|
|
59774
60084
|
break;
|
|
@@ -59789,18 +60099,18 @@ function statusReport(opts = {}) {
|
|
|
59789
60099
|
const claudePath = claudeSettingsPath(opts.project === true, cwd);
|
|
59790
60100
|
const root = projectRoot(cwd);
|
|
59791
60101
|
const codexDir = codexHome();
|
|
59792
|
-
const ocDir = opts.project === true ?
|
|
60102
|
+
const ocDir = opts.project === true ? join43(cwd, ".opencode") : opencodeConfigDir();
|
|
59793
60103
|
const codexOn = codexWired(codexDir) > 0;
|
|
59794
60104
|
return [
|
|
59795
60105
|
{ target: "claude-code", wired: claudeCodeWired(claudePath) > 0, path: claudePath, note: "the plugin route wires this separately and does not show here" },
|
|
59796
60106
|
{
|
|
59797
60107
|
target: "codex",
|
|
59798
60108
|
wired: codexOn,
|
|
59799
|
-
path:
|
|
60109
|
+
path: join43(codexDir, "hooks.json"),
|
|
59800
60110
|
// A wired hook behind a disabled feature flag never fires and looks fine. Say so.
|
|
59801
60111
|
note: codexOn && !codexHooksEnabled(codexDir) ? "WIRED BUT INERT: `[features] hooks = true` is not set in config.toml" : void 0
|
|
59802
60112
|
},
|
|
59803
|
-
{ target: "opencode", wired: opencodeWired(ocDir), path:
|
|
60113
|
+
{ target: "opencode", wired: opencodeWired(ocDir), path: join43(ocDir, "plugin", "ultra11y.js") },
|
|
59804
60114
|
{ target: "agents-md", wired: agentsMdWired(root), path: agentsMdPath(root) }
|
|
59805
60115
|
];
|
|
59806
60116
|
}
|
|
@@ -60335,9 +60645,9 @@ function pagesComment(result, opts = {}) {
|
|
|
60335
60645
|
}
|
|
60336
60646
|
|
|
60337
60647
|
// src/evidence.ts
|
|
60338
|
-
import { createHash as
|
|
60339
|
-
import { existsSync as
|
|
60340
|
-
import { join as
|
|
60648
|
+
import { createHash as createHash7 } from "crypto";
|
|
60649
|
+
import { existsSync as existsSync31, mkdirSync as mkdirSync14, readFileSync as readFileSync27, writeFileSync as writeFileSync16 } from "fs";
|
|
60650
|
+
import { join as join44 } from "path";
|
|
60341
60651
|
var DEFAULT_GEOMETRY = { pad: 24, minWidth: 320, minHeight: 120, maxWidth: 960, maxHeight: 540 };
|
|
60342
60652
|
var DEFAULT_CAPS = { perRule: 6, perPage: 12, total: 200 };
|
|
60343
60653
|
var HALO = { r: 255, g: 255, b: 255, a: 0.92 };
|
|
@@ -60354,7 +60664,7 @@ function pageIdOfSnapshot(file) {
|
|
|
60354
60664
|
}
|
|
60355
60665
|
function readJson3(file) {
|
|
60356
60666
|
try {
|
|
60357
|
-
return JSON.parse(
|
|
60667
|
+
return JSON.parse(readFileSync27(file, "utf8"));
|
|
60358
60668
|
} catch {
|
|
60359
60669
|
return void 0;
|
|
60360
60670
|
}
|
|
@@ -60370,21 +60680,21 @@ function resolveScale(img, viewport, boxes) {
|
|
|
60370
60680
|
}
|
|
60371
60681
|
function loadPageEvidence(root, pageId) {
|
|
60372
60682
|
const dir = snapshotDir(root, pageId);
|
|
60373
|
-
const domFile =
|
|
60374
|
-
const shotFile =
|
|
60375
|
-
if (!
|
|
60376
|
-
if (!
|
|
60683
|
+
const domFile = join44(dir, "dom.html");
|
|
60684
|
+
const shotFile = join44(dir, "screen.png");
|
|
60685
|
+
if (!existsSync31(domFile)) return { skip: "no-snapshot" };
|
|
60686
|
+
if (!existsSync31(shotFile)) return { skip: "no-screenshot" };
|
|
60377
60687
|
let dom;
|
|
60378
60688
|
let shot;
|
|
60379
60689
|
try {
|
|
60380
|
-
dom =
|
|
60381
|
-
shot =
|
|
60690
|
+
dom = readFileSync27(domFile, "utf8");
|
|
60691
|
+
shot = readFileSync27(shotFile);
|
|
60382
60692
|
} catch {
|
|
60383
60693
|
return { skip: "no-snapshot" };
|
|
60384
60694
|
}
|
|
60385
60695
|
const img = decodePng(shot);
|
|
60386
60696
|
if (!img) return { skip: "unreadable-image" };
|
|
60387
|
-
const digest = readJson3(
|
|
60697
|
+
const digest = readJson3(join44(dir, "boxes.json"));
|
|
60388
60698
|
if (!digest) return { skip: "no-boxes" };
|
|
60389
60699
|
const doc = parseHtml(dom, domFile);
|
|
60390
60700
|
const boxes = alignedBoxes(dom, digest, doc);
|
|
@@ -60400,7 +60710,7 @@ function loadPageEvidence(root, pageId) {
|
|
|
60400
60710
|
return { pageId, img, scale, viewport, ordinalOf, boxes, truncated: Boolean(digest.truncated) };
|
|
60401
60711
|
}
|
|
60402
60712
|
function metaOf(dir) {
|
|
60403
|
-
const raw = readJson3(
|
|
60713
|
+
const raw = readJson3(join44(dir, "meta.json"));
|
|
60404
60714
|
if (raw === void 0) return void 0;
|
|
60405
60715
|
const v = validateSnapshotMeta(raw);
|
|
60406
60716
|
return v.ok && v.meta ? v.meta : void 0;
|
|
@@ -60532,7 +60842,7 @@ function bump(t2, reason) {
|
|
|
60532
60842
|
t2.skipped[reason] = (t2.skipped[reason] ?? 0) + 1;
|
|
60533
60843
|
}
|
|
60534
60844
|
function cropName(f) {
|
|
60535
|
-
return `${
|
|
60845
|
+
return `${createHash7("sha1").update(findingId(f)).digest("hex").slice(0, 12)}.png`;
|
|
60536
60846
|
}
|
|
60537
60847
|
function writeEvidence(result, opts) {
|
|
60538
60848
|
const root = opts.root ?? process.cwd();
|
|
@@ -60565,7 +60875,7 @@ function writeEvidence(result, opts) {
|
|
|
60565
60875
|
}
|
|
60566
60876
|
continue;
|
|
60567
60877
|
}
|
|
60568
|
-
const dir =
|
|
60878
|
+
const dir = join44(opts.outDir, "assets", pageId);
|
|
60569
60879
|
let wrote = false;
|
|
60570
60880
|
const perRule = /* @__PURE__ */ new Map();
|
|
60571
60881
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -60590,13 +60900,13 @@ function writeEvidence(result, opts) {
|
|
|
60590
60900
|
continue;
|
|
60591
60901
|
}
|
|
60592
60902
|
const name2 = cropName(f);
|
|
60593
|
-
const path =
|
|
60903
|
+
const path = join44(dir, name2);
|
|
60594
60904
|
try {
|
|
60595
60905
|
if (!wrote) {
|
|
60596
|
-
|
|
60906
|
+
mkdirSync14(dir, { recursive: true });
|
|
60597
60907
|
wrote = true;
|
|
60598
60908
|
}
|
|
60599
|
-
|
|
60909
|
+
writeFileSync16(path, crop.png);
|
|
60600
60910
|
} catch {
|
|
60601
60911
|
manifest.skipped.set(id, "no-screenshot");
|
|
60602
60912
|
bump(tally, "no-screenshot");
|
|
@@ -60628,8 +60938,8 @@ function writeEvidence(result, opts) {
|
|
|
60628
60938
|
}
|
|
60629
60939
|
|
|
60630
60940
|
// src/html-emit.ts
|
|
60631
|
-
import { mkdirSync as
|
|
60632
|
-
import { join as
|
|
60941
|
+
import { mkdirSync as mkdirSync15, readFileSync as readFileSync28, statSync as statSync11, writeFileSync as writeFileSync17 } from "fs";
|
|
60942
|
+
import { join as join45 } from "path";
|
|
60633
60943
|
|
|
60634
60944
|
// src/html.ts
|
|
60635
60945
|
var MARK4 = { C: "C", NC: "NC", NA: "\u2014", manual: "?" };
|
|
@@ -61191,7 +61501,7 @@ function inlineSize(path) {
|
|
|
61191
61501
|
}
|
|
61192
61502
|
function dataUri(path) {
|
|
61193
61503
|
try {
|
|
61194
|
-
return `data:image/png;base64,${
|
|
61504
|
+
return `data:image/png;base64,${readFileSync28(path).toString("base64")}`;
|
|
61195
61505
|
} catch {
|
|
61196
61506
|
return void 0;
|
|
61197
61507
|
}
|
|
@@ -61230,7 +61540,7 @@ function writeHtml(result, opts) {
|
|
|
61230
61540
|
const t2 = T5[lang];
|
|
61231
61541
|
const budget = opts.inlineBudget ?? DEFAULT_INLINE_BUDGET;
|
|
61232
61542
|
const stdKey = isCore(standard) ? "wcag" : loadPack(standard).key;
|
|
61233
|
-
|
|
61543
|
+
mkdirSync15(opts.outDir, { recursive: true });
|
|
61234
61544
|
const crops = [...opts.evidence?.crops.values() ?? []];
|
|
61235
61545
|
const shots = [...opts.screenshots?.values() ?? []];
|
|
61236
61546
|
const rung = pickRung(
|
|
@@ -61262,7 +61572,7 @@ function writeHtml(result, opts) {
|
|
|
61262
61572
|
return drawn;
|
|
61263
61573
|
};
|
|
61264
61574
|
const flat = opts.layout === "pages";
|
|
61265
|
-
const sheetDir = flat ? opts.outDir :
|
|
61575
|
+
const sheetDir = flat ? opts.outDir : join45(opts.outDir, "pages");
|
|
61266
61576
|
const up = flat ? "./" : "../";
|
|
61267
61577
|
const compositeName = `ultra11y-${stdKey}-${result.date}.html`;
|
|
61268
61578
|
const nav = flat ? [{ href: "./index.html", text: t2.pagesTitle }] : [
|
|
@@ -61281,26 +61591,26 @@ function writeHtml(result, opts) {
|
|
|
61281
61591
|
nav: nav.map((n) => ({ ...n, current: n.href === `./${compositeName}` }))
|
|
61282
61592
|
});
|
|
61283
61593
|
if (notices.length) composite.blocks.unshift({ kind: "note", tone: "warn", runs: noticeRuns(notices) });
|
|
61284
|
-
compositePath =
|
|
61285
|
-
|
|
61594
|
+
compositePath = join45(opts.outDir, compositeName);
|
|
61595
|
+
writeFileSync17(compositePath, renderHtmlDocument(composite));
|
|
61286
61596
|
}
|
|
61287
61597
|
const fileCrops = cropLookup(opts.evidence, lang, (_p, href) => href.replace(/^\.\//, up));
|
|
61288
61598
|
const indexNav = nav.map((n) => ({ ...n, current: n.href === "./index.html" }));
|
|
61289
61599
|
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") });
|
|
61290
|
-
const indexPath =
|
|
61291
|
-
|
|
61600
|
+
const indexPath = join45(opts.outDir, "index.html");
|
|
61601
|
+
writeFileSync17(indexPath, renderHtmlDocument(index));
|
|
61292
61602
|
const sheets = [];
|
|
61293
61603
|
if (opts.pages) {
|
|
61294
61604
|
const derived = derivePages(result, pagesOf(result));
|
|
61295
|
-
|
|
61605
|
+
mkdirSync15(sheetDir, { recursive: true });
|
|
61296
61606
|
const sheetNav = flat ? [{ href: "./index.html", text: t2.pagesTitle }] : [
|
|
61297
61607
|
{ href: `${up}index.html`, text: t2.indexTitle },
|
|
61298
61608
|
{ href: `${up}${compositeName}`, text: t2.docTitle },
|
|
61299
61609
|
{ href: "./index.html", text: t2.pagesTitle }
|
|
61300
61610
|
];
|
|
61301
61611
|
if (!flat) {
|
|
61302
|
-
|
|
61303
|
-
|
|
61612
|
+
writeFileSync17(
|
|
61613
|
+
join45(sheetDir, "index.html"),
|
|
61304
61614
|
renderHtmlDocument(
|
|
61305
61615
|
pagesIndexDoc(result, {
|
|
61306
61616
|
standard,
|
|
@@ -61310,7 +61620,7 @@ function writeHtml(result, opts) {
|
|
|
61310
61620
|
})
|
|
61311
61621
|
)
|
|
61312
61622
|
);
|
|
61313
|
-
sheets.push(
|
|
61623
|
+
sheets.push(join45(sheetDir, "index.html"));
|
|
61314
61624
|
}
|
|
61315
61625
|
for (const p of derived) {
|
|
61316
61626
|
const doc = pageDoc(result, p, {
|
|
@@ -61323,8 +61633,8 @@ function writeHtml(result, opts) {
|
|
|
61323
61633
|
// beside the entry point, seen from wherever this sheet sits.
|
|
61324
61634
|
...opts.screenshots?.has(p.id) ? { screenshot: `${up}assets/${p.id}.png` } : {}
|
|
61325
61635
|
});
|
|
61326
|
-
const path =
|
|
61327
|
-
|
|
61636
|
+
const path = join45(sheetDir, `page-${p.id}.html`);
|
|
61637
|
+
writeFileSync17(path, renderHtmlDocument(doc));
|
|
61328
61638
|
sheets.push(path);
|
|
61329
61639
|
}
|
|
61330
61640
|
}
|
|
@@ -61344,8 +61654,8 @@ function noticeRuns(notices) {
|
|
|
61344
61654
|
|
|
61345
61655
|
// src/dev.ts
|
|
61346
61656
|
import { createServer } from "http";
|
|
61347
|
-
import { mkdirSync as
|
|
61348
|
-
import { dirname as
|
|
61657
|
+
import { mkdirSync as mkdirSync16, readFileSync as readFileSync29, writeFileSync as writeFileSync18 } from "fs";
|
|
61658
|
+
import { dirname as dirname13, join as join46 } from "path";
|
|
61349
61659
|
var DEV_DEFAULT_PORT = 4111;
|
|
61350
61660
|
function criterionLabel3(f, standard) {
|
|
61351
61661
|
if (isCore(standard)) return `WCAG ${f.criteriaId}`;
|
|
@@ -61567,13 +61877,13 @@ function auditCollected(root, payload) {
|
|
|
61567
61877
|
...payload.css ? { css: payload.css } : {},
|
|
61568
61878
|
...shot ? { screenshotBase64: shot } : {}
|
|
61569
61879
|
});
|
|
61570
|
-
return { ok: true, result: runAudit({ inputs: [
|
|
61880
|
+
return { ok: true, result: runAudit({ inputs: [join46(dir, "dom.html")] }) };
|
|
61571
61881
|
}
|
|
61572
61882
|
function projectPages(root) {
|
|
61573
61883
|
const snaps = readSnapshots(root);
|
|
61574
61884
|
if (!snaps.length) return { result: null, pages: [] };
|
|
61575
61885
|
const scope = pageScopesFrom(snaps);
|
|
61576
|
-
const result = runAudit({ inputs: [
|
|
61886
|
+
const result = runAudit({ inputs: [join46(root, ".ultra11y/pages")] });
|
|
61577
61887
|
result.scope.pages = scope;
|
|
61578
61888
|
attributePages(result, scope);
|
|
61579
61889
|
foldRecordedAdjudication(root, result);
|
|
@@ -61587,7 +61897,7 @@ function judgePages(result) {
|
|
|
61587
61897
|
function foldRecordedAdjudication(root, fresh) {
|
|
61588
61898
|
let prior;
|
|
61589
61899
|
try {
|
|
61590
|
-
prior = JSON.parse(
|
|
61900
|
+
prior = JSON.parse(readFileSync29(join46(root, "audits", "audit-latest.json"), "utf8"));
|
|
61591
61901
|
} catch {
|
|
61592
61902
|
return;
|
|
61593
61903
|
}
|
|
@@ -61702,9 +62012,9 @@ function startDevServer(opts) {
|
|
|
61702
62012
|
});
|
|
61703
62013
|
let auditPath;
|
|
61704
62014
|
if (applied.ok) {
|
|
61705
|
-
auditPath =
|
|
61706
|
-
|
|
61707
|
-
|
|
62015
|
+
auditPath = join46(opts.root, "audits", "audit-latest.json");
|
|
62016
|
+
mkdirSync16(dirname13(auditPath), { recursive: true });
|
|
62017
|
+
writeFileSync18(auditPath, `${JSON.stringify(applied.audit, null, 2)}
|
|
61708
62018
|
`);
|
|
61709
62019
|
}
|
|
61710
62020
|
res.writeHead(200, { "content-type": "application/json" }).end(
|
|
@@ -61731,10 +62041,10 @@ function startDevServer(opts) {
|
|
|
61731
62041
|
}
|
|
61732
62042
|
res.writeHead(404, { "content-type": "text/plain" }).end("not found");
|
|
61733
62043
|
});
|
|
61734
|
-
return new Promise((
|
|
62044
|
+
return new Promise((resolve17, reject) => {
|
|
61735
62045
|
server.once("error", reject);
|
|
61736
62046
|
server.listen(opts.port, "127.0.0.1", () => {
|
|
61737
|
-
|
|
62047
|
+
resolve17({
|
|
61738
62048
|
port: server.address().port,
|
|
61739
62049
|
close: () => new Promise((r) => server.close(() => r()))
|
|
61740
62050
|
});
|
|
@@ -62018,11 +62328,11 @@ function e2eSetupPlan(runners, paths, lang = "en") {
|
|
|
62018
62328
|
}
|
|
62019
62329
|
|
|
62020
62330
|
// src/orchestrate.ts
|
|
62021
|
-
import { existsSync as
|
|
62022
|
-
import { join as
|
|
62331
|
+
import { existsSync as existsSync32, mkdirSync as mkdirSync17, readFileSync as readFileSync30, rmSync as rmSync7, writeFileSync as writeFileSync19 } from "fs";
|
|
62332
|
+
import { join as join48, resolve as resolve13 } from "path";
|
|
62023
62333
|
|
|
62024
62334
|
// src/orchestrate-templates.ts
|
|
62025
|
-
import { join as
|
|
62335
|
+
import { join as join47 } from "path";
|
|
62026
62336
|
var ONE_WRITER_FOOTER = `
|
|
62027
62337
|
## Return, don't write
|
|
62028
62338
|
|
|
@@ -62108,7 +62418,7 @@ var PHASE_SPECS = {
|
|
|
62108
62418
|
title: "Adjudicate",
|
|
62109
62419
|
schema: ADJUDICATE_SCHEMA,
|
|
62110
62420
|
description: (n) => `Adjudicate the ${n} residual judgment criterion(ia) of an ultra11y audit (fan-out, fail-closed fold)`,
|
|
62111
|
-
applyHint: (engine, worklist, run2) => `node ${engine} verify --apply ${worklist} --in ${
|
|
62421
|
+
applyHint: (engine, worklist, run2) => `node ${engine} verify --apply ${worklist} --in ${join47(run2, "audit-latest.json")} --out ${run2}`
|
|
62112
62422
|
},
|
|
62113
62423
|
"verify-report": {
|
|
62114
62424
|
role: "refuter",
|
|
@@ -62130,7 +62440,7 @@ function toBatches(ids, batchSize) {
|
|
|
62130
62440
|
}
|
|
62131
62441
|
function phaseWorkflowScript(ph, runAbs, engineAbs, batchSize) {
|
|
62132
62442
|
const spec = phaseSpec(ph.name);
|
|
62133
|
-
const scriptPath =
|
|
62443
|
+
const scriptPath = join47(runAbs, "orchestration", `${ph.name}.workflow.mjs`);
|
|
62134
62444
|
const meta2 = { name: `ultra11y-${ph.name}`, description: spec.description(ph.items), phases: [{ title: spec.title }] };
|
|
62135
62445
|
return [
|
|
62136
62446
|
`export const meta = ${JSON.stringify(meta2)}`,
|
|
@@ -62174,7 +62484,7 @@ function agentContracts(runAbs, engineAbs) {
|
|
|
62174
62484
|
|
|
62175
62485
|
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.
|
|
62176
62486
|
|
|
62177
|
-
Worklist: \`${
|
|
62487
|
+
Worklist: \`${join47(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>\`).
|
|
62178
62488
|
|
|
62179
62489
|
For EACH of your criteria:
|
|
62180
62490
|
|
|
@@ -62186,13 +62496,13 @@ For EACH of your criteria:
|
|
|
62186
62496
|
- \`manual\` (still undecidable) \u2014 REQUIRES \`reason\`: \`needs-rendered-dom\` (only a rendered DOM can decide, e.g. computed contrast) or \`undecidable\` (the evidence cannot settle it either way).
|
|
62187
62497
|
3. Never guess. A criterion you cannot decide from real evidence stays \`manual\` with a reason \u2014 that is a valid, honest verdict; the scan tier or a human picks it up.
|
|
62188
62498
|
|
|
62189
|
-
Return (structured output): \`{ "verdicts": [{ "criteriaId", "verdict", "justification", "citations", "reason", "findings" }] }\` \u2014 your ITEMS only, every field grounded in what you actually read, every NC finding carrying its \`normativeRef\`, and every C (plus every evidenced NA) carrying its \`citations\`. A verdict that clears a criterion without naming what it cleared is refused by the fold
|
|
62499
|
+
Return (structured output): \`{ "verdicts": [{ "criteriaId", "verdict", "justification", "citations", "reason", "findings" }] }\` \u2014 your ITEMS only, every field grounded in what you actually read, every NC finding carrying its \`normativeRef\`, and every C (plus every evidenced NA) carrying its \`citations\`. A verdict that clears a criterion without naming what it cleared is refused by the fold; the refusal costs THAT criterion \u2014 which stays \xAB to assess \xBB carrying the reason \u2014 and leaves every other verdict standing. So never guess to fill a gap: an honest \`manual\` is worth more than a verdict the gate throws away.
|
|
62190
62500
|
${footer}`,
|
|
62191
62501
|
refuter: `# Contract: refuter
|
|
62192
62502
|
|
|
62193
62503
|
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.
|
|
62194
62504
|
|
|
62195
|
-
Worklist: \`${
|
|
62505
|
+
Worklist: \`${join47(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>\`).
|
|
62196
62506
|
|
|
62197
62507
|
For EACH of your entries:
|
|
62198
62508
|
|
|
@@ -62228,14 +62538,14 @@ ${status}
|
|
|
62228
62538
|
|
|
62229
62539
|
## The loop (play every role yourself, one item at a time)
|
|
62230
62540
|
|
|
62231
|
-
1. **Audit** (if not done): \`${engine} audit "<globs>" --graph --out ${runAbs}\` \u2192 \`${
|
|
62232
|
-
2. **Adjudicate the residual criteria** \u2014 \`${engine} verify --manual --in ${
|
|
62233
|
-
3. **Report**: \`${engine} report --in ${
|
|
62234
|
-
4. **Verify the report's claims** \u2014 \`${engine} verify --report <the report .md> --out ${runAbs}\` writes \`${
|
|
62541
|
+
1. **Audit** (if not done): \`${engine} audit "<globs>" --graph --out ${runAbs}\` \u2192 \`${join47(runAbs, "audit-latest.json")}\`.
|
|
62542
|
+
2. **Adjudicate the residual criteria** \u2014 \`${engine} verify --manual --in ${join47(runAbs, "audit-latest.json")} --out ${runAbs}\` writes \`${join47(runAbs, "ADJUDICATE.todo.json")}\`. For EVERY item, apply \`${join47(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 \u2014 gated per verdict, so a refusal costs its own criterion and no other: \`${engine} verify --apply ${join47(runAbs, "ADJUDICATE.todo.json")} --in ${join47(runAbs, "audit-latest.json")} --out ${runAbs}\`. Add \`--ledger .ultra11y/verdicts/<standard>.json\` to RECORD the verdicts that landed, so CI can replay them without a model; \`--strict\` restores the old all-or-nothing fold.
|
|
62543
|
+
3. **Report**: \`${engine} report --in ${join47(runAbs, "audit-latest.json")} --out ${runAbs}\`.
|
|
62544
|
+
4. **Verify the report's claims** \u2014 \`${engine} verify --report <the report .md> --out ${runAbs}\` writes \`${join47(runAbs, "VERIFY.todo.json")}\`. For EVERY entry, apply \`${join47(runAbs, "orchestration", "agents", "refuter.md")}\` yourself (open file:line, verdict supported/partial/refuted/unsupported + note IN the todo file). Then: \`${engine} verify --apply ${join47(runAbs, "VERIFY.todo.json")} --report <the report .md>\`.
|
|
62235
62545
|
5. **Gate**: \`${engine} check --report <the report .md> --semantic\` must exit 0 before presenting anything.
|
|
62236
62546
|
6. **Fix & re-audit**: \`${engine} fix <globs> --write --iterate\`, hand-apply the judgment fixes, then loop from step 1 until the gate stays green.
|
|
62237
62547
|
|
|
62238
|
-
With subagents available, prefer the emitted workflows instead: \`orchestrate --run ${runAbs} --phase <p>\` then \`Workflow({ scriptPath: "${
|
|
62548
|
+
With subagents available, prefer the emitted workflows instead: \`orchestrate --run ${runAbs} --phase <p>\` then \`Workflow({ scriptPath: "${join47(runAbs, "orchestration", "<p>.workflow.mjs")}" })\` \u2014 you stay the sole writer either way.
|
|
62239
62549
|
`;
|
|
62240
62550
|
}
|
|
62241
62551
|
|
|
@@ -62244,13 +62554,13 @@ var PHASES = ["adjudicate", "verify-report"];
|
|
|
62244
62554
|
var SMALL_WORKLIST = 3;
|
|
62245
62555
|
var BATCH_SIZE2 = 8;
|
|
62246
62556
|
function listPhases(runDir, engineAbs) {
|
|
62247
|
-
const run2 =
|
|
62248
|
-
const adjPath =
|
|
62557
|
+
const run2 = resolve13(runDir);
|
|
62558
|
+
const adjPath = join48(run2, "ADJUDICATE.todo.json");
|
|
62249
62559
|
let adjIds = [];
|
|
62250
62560
|
let adjReady = false;
|
|
62251
|
-
if (
|
|
62561
|
+
if (existsSync32(adjPath)) {
|
|
62252
62562
|
try {
|
|
62253
|
-
const f = JSON.parse(
|
|
62563
|
+
const f = JSON.parse(readFileSync30(adjPath, "utf8"));
|
|
62254
62564
|
if (f && f.kind === "adjudication" && Array.isArray(f.items)) {
|
|
62255
62565
|
adjReady = true;
|
|
62256
62566
|
adjIds = f.items.map((i2) => i2.criteriaId);
|
|
@@ -62258,12 +62568,12 @@ function listPhases(runDir, engineAbs) {
|
|
|
62258
62568
|
} catch {
|
|
62259
62569
|
}
|
|
62260
62570
|
}
|
|
62261
|
-
const verPath =
|
|
62571
|
+
const verPath = join48(run2, "VERIFY.todo.json");
|
|
62262
62572
|
let verIds = [];
|
|
62263
62573
|
let verReady = false;
|
|
62264
|
-
if (
|
|
62574
|
+
if (existsSync32(verPath)) {
|
|
62265
62575
|
try {
|
|
62266
|
-
const items = JSON.parse(
|
|
62576
|
+
const items = JSON.parse(readFileSync30(verPath, "utf8"));
|
|
62267
62577
|
if (Array.isArray(items)) {
|
|
62268
62578
|
verReady = true;
|
|
62269
62579
|
verIds = items.map((i2) => String(i2.n));
|
|
@@ -62278,7 +62588,7 @@ function listPhases(runDir, engineAbs) {
|
|
|
62278
62588
|
worklist: adjPath,
|
|
62279
62589
|
items: adjIds.length,
|
|
62280
62590
|
ids: adjIds,
|
|
62281
|
-
prerequisite: `node ${engineAbs} verify --manual --in ${
|
|
62591
|
+
prerequisite: `node ${engineAbs} verify --manual --in ${join48(run2, "audit-latest.json")} --out ${run2}`
|
|
62282
62592
|
},
|
|
62283
62593
|
{
|
|
62284
62594
|
name: "verify-report",
|
|
@@ -62291,8 +62601,8 @@ function listPhases(runDir, engineAbs) {
|
|
|
62291
62601
|
];
|
|
62292
62602
|
}
|
|
62293
62603
|
function orchestrateRun(runDir, engineAbs, opts = {}) {
|
|
62294
|
-
const run2 =
|
|
62295
|
-
if (!
|
|
62604
|
+
const run2 = resolve13(runDir);
|
|
62605
|
+
if (!existsSync32(run2)) {
|
|
62296
62606
|
return { exitCode: 2, written: [], notices: [], errors: [`run dir not found: ${run2}`], phases: [] };
|
|
62297
62607
|
}
|
|
62298
62608
|
const phases = listPhases(run2, engineAbs);
|
|
@@ -62319,22 +62629,22 @@ function orchestrateRun(runDir, engineAbs, opts = {}) {
|
|
|
62319
62629
|
}
|
|
62320
62630
|
selected = [ph];
|
|
62321
62631
|
}
|
|
62322
|
-
const orchDir =
|
|
62323
|
-
const agentsDir =
|
|
62324
|
-
|
|
62325
|
-
|
|
62632
|
+
const orchDir = join48(run2, "orchestration");
|
|
62633
|
+
const agentsDir = join48(orchDir, "agents");
|
|
62634
|
+
mkdirSync17(join48(orchDir, "out"), { recursive: true });
|
|
62635
|
+
mkdirSync17(agentsDir, { recursive: true });
|
|
62326
62636
|
const written = [];
|
|
62327
62637
|
const notices = [];
|
|
62328
62638
|
for (const [name2, content] of Object.entries(agentContracts(run2, engineAbs))) {
|
|
62329
|
-
const p =
|
|
62330
|
-
|
|
62639
|
+
const p = join48(agentsDir, `${name2}.md`);
|
|
62640
|
+
writeFileSync19(p, content);
|
|
62331
62641
|
written.push(p);
|
|
62332
62642
|
}
|
|
62333
62643
|
const emitted = new Set(opts.eco ? [] : selected.filter((p) => p.items > 0).map((p) => p.name));
|
|
62334
62644
|
for (const ph of phases) {
|
|
62335
62645
|
if (emitted.has(ph.name)) continue;
|
|
62336
|
-
const stale =
|
|
62337
|
-
if (
|
|
62646
|
+
const stale = join48(orchDir, `${ph.name}.workflow.mjs`);
|
|
62647
|
+
if (existsSync32(stale)) {
|
|
62338
62648
|
rmSync7(stale, { force: true });
|
|
62339
62649
|
notices.push(`phase "${ph.name}": stale workflow removed \u2014 its worklist ${ph.ready ? "is now empty" : "no longer exists"}.`);
|
|
62340
62650
|
}
|
|
@@ -62348,13 +62658,13 @@ function orchestrateRun(runDir, engineAbs, opts = {}) {
|
|
|
62348
62658
|
if (ph.items <= SMALL_WORKLIST) {
|
|
62349
62659
|
notices.push(`phase "${ph.name}": only ${ph.items} item(s) \u2014 the sequential --eco path is equivalent and cheaper.`);
|
|
62350
62660
|
}
|
|
62351
|
-
const p =
|
|
62352
|
-
|
|
62661
|
+
const p = join48(orchDir, `${ph.name}.workflow.mjs`);
|
|
62662
|
+
writeFileSync19(p, phaseWorkflowScript(ph, run2, engineAbs, BATCH_SIZE2));
|
|
62353
62663
|
written.push(p);
|
|
62354
62664
|
}
|
|
62355
62665
|
}
|
|
62356
|
-
const rb =
|
|
62357
|
-
|
|
62666
|
+
const rb = join48(orchDir, "RUNBOOK.md");
|
|
62667
|
+
writeFileSync19(rb, runbookMd(phases, run2, engineAbs));
|
|
62358
62668
|
written.push(rb);
|
|
62359
62669
|
return { exitCode: 0, written, notices, errors: [], phases };
|
|
62360
62670
|
}
|
|
@@ -62363,8 +62673,8 @@ function orchestrateRun(runDir, engineAbs, opts = {}) {
|
|
|
62363
62673
|
import { createInterface as createInterface2 } from "readline";
|
|
62364
62674
|
|
|
62365
62675
|
// src/mcp/handlers.ts
|
|
62366
|
-
import { existsSync as
|
|
62367
|
-
import { isAbsolute as isAbsolute3, join as
|
|
62676
|
+
import { existsSync as existsSync33, readFileSync as readFileSync31, realpathSync as realpathSync4, statSync as statSync12 } from "fs";
|
|
62677
|
+
import { isAbsolute as isAbsolute3, join as join49, resolve as resolve14, sep as sep5 } from "path";
|
|
62368
62678
|
|
|
62369
62679
|
// src/criteria-view.ts
|
|
62370
62680
|
var CriteriaLookupError = class extends Error {
|
|
@@ -62841,8 +63151,8 @@ function positive(v, key) {
|
|
|
62841
63151
|
function requiredCwd(args2, defaults) {
|
|
62842
63152
|
const cwd = str2(args2.cwd) ?? defaults.defaultCwd;
|
|
62843
63153
|
if (!cwd) throw new ToolError("`cwd` is required: an absolute path to the project root.");
|
|
62844
|
-
const abs =
|
|
62845
|
-
if (!
|
|
63154
|
+
const abs = resolve14(cwd);
|
|
63155
|
+
if (!existsSync33(abs)) throw new ToolError(`project root not found: ${abs}`);
|
|
62846
63156
|
if (!statSync12(abs).isDirectory()) throw new ToolError(`\`cwd\` is not a directory: ${abs}`);
|
|
62847
63157
|
return abs;
|
|
62848
63158
|
}
|
|
@@ -62953,7 +63263,7 @@ function audit(args2, cwd) {
|
|
|
62953
63263
|
process.chdir(cwd);
|
|
62954
63264
|
const inputs = strArray2(args2.globs) ?? DEFAULT_GLOBS;
|
|
62955
63265
|
const scopedToDiff = bool(args2.changed) || bool(args2.staged) || str2(args2.since) !== void 0;
|
|
62956
|
-
const extra = [CAPTURES_DIR, PAGES_DIR].filter((d) => !scopedToDiff &&
|
|
63266
|
+
const extra = [CAPTURES_DIR, PAGES_DIR].filter((d) => !scopedToDiff && existsSync33(d) && !inputs.includes(d));
|
|
62957
63267
|
const result = runAudit({
|
|
62958
63268
|
inputs: [...inputs, ...extra],
|
|
62959
63269
|
include: strArray2(args2.include),
|
|
@@ -63072,13 +63382,13 @@ function handlePackCheck(args2, cwd) {
|
|
|
63072
63382
|
return { cwd, ...res };
|
|
63073
63383
|
}
|
|
63074
63384
|
function handleSampleCheck(cwd, standard) {
|
|
63075
|
-
const file =
|
|
63076
|
-
if (!
|
|
63385
|
+
const file = join49(cwd, ".ultra11yrc.json");
|
|
63386
|
+
if (!existsSync33(file)) {
|
|
63077
63387
|
throw new ToolError(`no .ultra11yrc.json at ${cwd} \u2014 a page sample must be declared before it can be linted.`);
|
|
63078
63388
|
}
|
|
63079
63389
|
let raw;
|
|
63080
63390
|
try {
|
|
63081
|
-
raw = JSON.parse(
|
|
63391
|
+
raw = JSON.parse(readFileSync31(file, "utf8"));
|
|
63082
63392
|
} catch (e) {
|
|
63083
63393
|
throw new ToolError(`.ultra11yrc.json is not valid JSON: ${e.message}`);
|
|
63084
63394
|
}
|
|
@@ -63169,10 +63479,10 @@ function handleInit(args2, cwd) {
|
|
|
63169
63479
|
throw new ToolError(`\`fail_on\` must be one of: error, warning, notice (got "${failOnRaw}")`);
|
|
63170
63480
|
}
|
|
63171
63481
|
const failOn = failOnRaw;
|
|
63172
|
-
const enginePath =
|
|
63482
|
+
const enginePath = resolve14("scripts/ultra11y.mjs");
|
|
63173
63483
|
const written = [];
|
|
63174
63484
|
if (bool(args2.hook)) written.push(writeHook(cwd, enginePath, failOn));
|
|
63175
|
-
if (bool(args2.ci)) written.push(
|
|
63485
|
+
if (bool(args2.ci)) written.push(join49(cwd, ".github/workflows/a11y.yml"));
|
|
63176
63486
|
if (!written.length) {
|
|
63177
63487
|
throw new ToolError("nothing to install \u2014 pass hook:true and/or ci:true.");
|
|
63178
63488
|
}
|
|
@@ -63192,8 +63502,8 @@ function reportText(args2, tool) {
|
|
|
63192
63502
|
if (inline) return inline;
|
|
63193
63503
|
if (!file) throw new ToolError(`\`report_text\` is required \u2014 the report markdown for ultra11y_${tool} to work on.`);
|
|
63194
63504
|
if (!isAbsolute3(file)) throw new ToolError("`report_file` must be an absolute path.");
|
|
63195
|
-
if (!
|
|
63196
|
-
return
|
|
63505
|
+
if (!existsSync33(file)) throw new ToolError(`report file not found: ${file}`);
|
|
63506
|
+
return readFileSync31(file, "utf8");
|
|
63197
63507
|
}
|
|
63198
63508
|
function handleCriteria(args2) {
|
|
63199
63509
|
const lang = langOf(args2);
|
|
@@ -63260,7 +63570,7 @@ function handleMethod(args2) {
|
|
|
63260
63570
|
function handleRead(args2, cwd) {
|
|
63261
63571
|
const raw = str2(args2.path);
|
|
63262
63572
|
if (!raw) throw new ToolError("`path` is required \u2014 relative to the project root, or an absolute path inside it.");
|
|
63263
|
-
const target = isAbsolute3(raw) ? raw :
|
|
63573
|
+
const target = isAbsolute3(raw) ? raw : join49(cwd, raw);
|
|
63264
63574
|
let real;
|
|
63265
63575
|
try {
|
|
63266
63576
|
real = realpathSync4(target);
|
|
@@ -63274,7 +63584,7 @@ function handleRead(args2, cwd) {
|
|
|
63274
63584
|
const st = statSync12(real);
|
|
63275
63585
|
if (!st.isFile()) throw new ToolError(`not a file: ${raw}`);
|
|
63276
63586
|
if (st.size > MAX_READ_BYTES) throw new ToolError(`file is too large to read (${st.size} bytes): ${raw}`);
|
|
63277
|
-
const lines =
|
|
63587
|
+
const lines = readFileSync31(real, "utf8").split("\n");
|
|
63278
63588
|
const total = lines.length;
|
|
63279
63589
|
const start2 = Math.max(1, Math.floor(num2(args2.start_line) ?? 1));
|
|
63280
63590
|
if (start2 > total) throw new ToolError(`start_line ${start2} is past the end of the file (${total} lines).`);
|
|
@@ -63929,25 +64239,25 @@ function str3(v) {
|
|
|
63929
64239
|
var DECLARED = new Set([...TOOLS2, ...WRITE_TOOLS].map((t2) => t2.name));
|
|
63930
64240
|
|
|
63931
64241
|
// src/mcp/resources.ts
|
|
63932
|
-
import { existsSync as
|
|
63933
|
-
import { basename as basename3, dirname as
|
|
64242
|
+
import { existsSync as existsSync34, readdirSync as readdirSync6, readFileSync as readFileSync32, realpathSync as realpathSync5, statSync as statSync13 } from "fs";
|
|
64243
|
+
import { basename as basename3, dirname as dirname14, join as join50, resolve as resolve15, sep as sep6 } from "path";
|
|
63934
64244
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
63935
64245
|
var SKILL_NAME = "ultra11y";
|
|
63936
64246
|
var URI_SCHEME = "skill://";
|
|
63937
64247
|
function resolveSkillRoot(moduleDir) {
|
|
63938
|
-
const here = moduleDir ??
|
|
63939
|
-
const candidates2 = [
|
|
63940
|
-
return candidates2.find((dir) =>
|
|
64248
|
+
const here = moduleDir ?? dirname14(fileURLToPath4(import.meta.url));
|
|
64249
|
+
const candidates2 = [resolve15(here, ".."), resolve15(here, "..", "skills", SKILL_NAME), resolve15(here, "..", "..", "skills", SKILL_NAME)];
|
|
64250
|
+
return candidates2.find((dir) => existsSync34(join50(dir, "SKILL.md")));
|
|
63941
64251
|
}
|
|
63942
64252
|
function listResources(moduleDir) {
|
|
63943
64253
|
const root = resolveSkillRoot(moduleDir);
|
|
63944
64254
|
if (!root) return [];
|
|
63945
64255
|
const out2 = [describe(root, "SKILL.md", `${SKILL_NAME}: the skill`)];
|
|
63946
|
-
const refDir =
|
|
63947
|
-
if (!
|
|
64256
|
+
const refDir = join50(root, "references");
|
|
64257
|
+
if (!existsSync34(refDir)) return out2;
|
|
63948
64258
|
for (const file of readdirSync6(refDir).sort()) {
|
|
63949
64259
|
if (!file.endsWith(".md")) continue;
|
|
63950
|
-
out2.push(describe(root,
|
|
64260
|
+
out2.push(describe(root, join50("references", file), `${SKILL_NAME} reference: ${basename3(file, ".md")}`));
|
|
63951
64261
|
}
|
|
63952
64262
|
return out2;
|
|
63953
64263
|
}
|
|
@@ -63959,7 +64269,7 @@ function readResource(uri, moduleDir) {
|
|
|
63959
64269
|
if (!root) throw new ResourceError("no skill payload found next to this build \u2014 nothing to read");
|
|
63960
64270
|
const rel2 = uri.slice(URI_SCHEME.length);
|
|
63961
64271
|
if (!rel2) throw new ResourceError("empty resource path");
|
|
63962
|
-
const target =
|
|
64272
|
+
const target = resolve15(root, rel2);
|
|
63963
64273
|
const rootReal = realpathSync5(root);
|
|
63964
64274
|
let targetReal;
|
|
63965
64275
|
try {
|
|
@@ -63971,7 +64281,7 @@ function readResource(uri, moduleDir) {
|
|
|
63971
64281
|
throw new ResourceError(`resource path escapes the skill root: ${uri}`);
|
|
63972
64282
|
}
|
|
63973
64283
|
if (!statSync13(targetReal).isFile()) throw new ResourceError(`not a file: ${uri}`);
|
|
63974
|
-
return { uri, mimeType: "text/markdown", text:
|
|
64284
|
+
return { uri, mimeType: "text/markdown", text: readFileSync32(targetReal, "utf8") };
|
|
63975
64285
|
}
|
|
63976
64286
|
var ResourceError = class extends Error {
|
|
63977
64287
|
};
|
|
@@ -63982,14 +64292,14 @@ function describe(root, rel2, fallbackTitle) {
|
|
|
63982
64292
|
title: fallbackTitle,
|
|
63983
64293
|
mimeType: "text/markdown"
|
|
63984
64294
|
};
|
|
63985
|
-
const summary = firstProse(
|
|
64295
|
+
const summary = firstProse(join50(root, rel2));
|
|
63986
64296
|
if (summary) decl.description = summary;
|
|
63987
64297
|
return decl;
|
|
63988
64298
|
}
|
|
63989
64299
|
function firstProse(file) {
|
|
63990
64300
|
let text;
|
|
63991
64301
|
try {
|
|
63992
|
-
text =
|
|
64302
|
+
text = readFileSync32(file, "utf8");
|
|
63993
64303
|
} catch {
|
|
63994
64304
|
return void 0;
|
|
63995
64305
|
}
|
|
@@ -64371,14 +64681,14 @@ function startHttpServer(opts = {}) {
|
|
|
64371
64681
|
server.requestTimeout = 0;
|
|
64372
64682
|
server.headersTimeout = 6e4;
|
|
64373
64683
|
server.keepAliveTimeout = 12e4;
|
|
64374
|
-
return new Promise((
|
|
64684
|
+
return new Promise((resolve17, reject) => {
|
|
64375
64685
|
server.once("error", reject);
|
|
64376
64686
|
server.listen(opts.port ?? 0, bind, () => {
|
|
64377
64687
|
server.removeListener("error", reject);
|
|
64378
64688
|
const addr2 = server.address();
|
|
64379
64689
|
const port = typeof addr2 === "object" && addr2 ? addr2.port : opts.port ?? 0;
|
|
64380
64690
|
const host = bind.includes(":") ? `[${bind}]` : bind;
|
|
64381
|
-
|
|
64691
|
+
resolve17({
|
|
64382
64692
|
server,
|
|
64383
64693
|
port,
|
|
64384
64694
|
url: `http://${host}:${port}${MCP_PATH}`,
|
|
@@ -64487,7 +64797,7 @@ function sendJson(res, status, body3, origin, extra = {}) {
|
|
|
64487
64797
|
}
|
|
64488
64798
|
var DRAIN_LIMIT = MAX_BODY_BYTES * 8;
|
|
64489
64799
|
function readBody2(req) {
|
|
64490
|
-
return new Promise((
|
|
64800
|
+
return new Promise((resolve17, reject) => {
|
|
64491
64801
|
const chunks = [];
|
|
64492
64802
|
let size = 0;
|
|
64493
64803
|
let over = false;
|
|
@@ -64511,7 +64821,7 @@ function readBody2(req) {
|
|
|
64511
64821
|
});
|
|
64512
64822
|
req.on("end", () => {
|
|
64513
64823
|
if (over) reject(new Error("too large"));
|
|
64514
|
-
else
|
|
64824
|
+
else resolve17(Buffer.concat(chunks).toString("utf8"));
|
|
64515
64825
|
});
|
|
64516
64826
|
req.on("error", reject);
|
|
64517
64827
|
req.on("aborted", () => reject(new Error("client aborted the request")));
|
|
@@ -64813,6 +65123,13 @@ Options:
|
|
|
64813
65123
|
--generate criteria: emit the bundled references/criteria.md (WCAG 2.2 AA)
|
|
64814
65124
|
--apply <file> verify: reduce a filled verdicts file to a pass/fail gate
|
|
64815
65125
|
(requires --report \u2014 coverage is re-derived from the report, uncapped)
|
|
65126
|
+
Also accepts an ADJUDICATION (--in <audit.json>), or a VERDICT LEDGER,
|
|
65127
|
+
which it REPLAYS onto the audit with no model in the loop
|
|
65128
|
+
--ledger <file> verify --apply / judge --apply: record the verdicts the fold ACCEPTED,
|
|
65129
|
+
so a later run replays them for free (default:
|
|
65130
|
+
.ultra11y/verdicts/<standard>.json). Replay re-derives the evidence
|
|
65131
|
+
and re-runs the same gate; a verdict whose evidence changed is
|
|
65132
|
+
dropped as stale and its criterion says so
|
|
64816
65133
|
--max-verify <n> verify: cap the worklist size; 0 = no cap (default: 40)
|
|
64817
65134
|
--verdicts <file> check --semantic: the adjudicated verdicts artifact
|
|
64818
65135
|
(default: VERIFY.todo.json next to the report)
|
|
@@ -64875,6 +65192,10 @@ Options:
|
|
|
64875
65192
|
--apply judge: fold the verdicts straight into the audit, through the SAME
|
|
64876
65193
|
fail-closed gate an agent's verdicts pass (no unjustified C/NA, no
|
|
64877
65194
|
ungroundable NC, no unadjudicated criterion)
|
|
65195
|
+
--strict verify --apply / judge --apply: all-or-nothing fold \u2014 one refused
|
|
65196
|
+
verdict discards the whole adjudication. The default folds PER
|
|
65197
|
+
VERDICT: a refusal costs its own criterion, which stays to assess
|
|
65198
|
+
carrying the refusal, and every verdict that proved itself lands
|
|
64878
65199
|
--semantic verify: fold the support-check into one pass
|
|
64879
65200
|
check: engage the semantic gate \u2014 requires an adjudicated verdicts
|
|
64880
65201
|
artifact (fails closed when absent) and re-grounds every passing
|
|
@@ -64949,6 +65270,10 @@ var VALUE_FLAGS2 = /* @__PURE__ */ new Set([
|
|
|
64949
65270
|
"dedup",
|
|
64950
65271
|
"only",
|
|
64951
65272
|
"standard",
|
|
65273
|
+
// `verify --apply` / `judge --apply`: where to RECORD the verdicts the fold accepted, so a
|
|
65274
|
+
// later run can replay them without a model (src/ledger.ts). A path; empty falls back to the
|
|
65275
|
+
// standard's default location under .ultra11y/verdicts/.
|
|
65276
|
+
"ledger",
|
|
64952
65277
|
"baseline",
|
|
64953
65278
|
"fail-on",
|
|
64954
65279
|
"split",
|
|
@@ -65020,6 +65345,9 @@ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
65020
65345
|
"generate",
|
|
65021
65346
|
"semantic",
|
|
65022
65347
|
"manual",
|
|
65348
|
+
// `verify --apply` / `judge --apply`: restore the all-or-nothing fold, where one refused
|
|
65349
|
+
// verdict discards the whole adjudication. The default is per-verdict.
|
|
65350
|
+
"strict",
|
|
65023
65351
|
"no-technical",
|
|
65024
65352
|
"override",
|
|
65025
65353
|
"local",
|
|
@@ -65244,9 +65572,9 @@ async function cmdAudit(p) {
|
|
|
65244
65572
|
const capturesFlag = typeof p.flags.captures === "string" && p.flags.captures ? p.flags.captures : void 0;
|
|
65245
65573
|
const capturesDir = capturesFlag ?? CAPTURES_DIR;
|
|
65246
65574
|
const scopedToDiff = p.flags.changed === true || p.flags.staged === true || since !== void 0;
|
|
65247
|
-
const capturesWanted = p.flags["no-captures"] !== true && !inputs.includes("-") && (capturesFlag !== void 0 ||
|
|
65575
|
+
const capturesWanted = p.flags["no-captures"] !== true && !inputs.includes("-") && (capturesFlag !== void 0 || existsSync35(capturesDir));
|
|
65248
65576
|
const useCaptures = capturesWanted && !scopedToDiff && !inputs.includes(capturesDir);
|
|
65249
|
-
const pagesWanted = p.flags["no-captures"] !== true && !inputs.includes("-") &&
|
|
65577
|
+
const pagesWanted = p.flags["no-captures"] !== true && !inputs.includes("-") && existsSync35(PAGES_DIR);
|
|
65250
65578
|
const usePages = pagesWanted && !scopedToDiff && !inputs.includes(PAGES_DIR);
|
|
65251
65579
|
const pagesInScope = pagesWanted && !scopedToDiff;
|
|
65252
65580
|
const auditInputs = [...inputs, ...useCaptures ? [capturesDir] : [], ...usePages ? [PAGES_DIR] : []];
|
|
@@ -65288,10 +65616,10 @@ async function cmdAudit(p) {
|
|
|
65288
65616
|
if (typeof p.flags.out === "string") {
|
|
65289
65617
|
const out2 = p.flags.out;
|
|
65290
65618
|
const asFile = out2.toLowerCase().endsWith(".json");
|
|
65291
|
-
const target = asFile ? out2 :
|
|
65619
|
+
const target = asFile ? out2 : join51(out2, "audit-latest.json");
|
|
65292
65620
|
try {
|
|
65293
|
-
|
|
65294
|
-
|
|
65621
|
+
mkdirSync18(asFile ? dirname15(out2) : out2, { recursive: true });
|
|
65622
|
+
writeFileSync20(target, JSON.stringify(result, null, 2) + "\n");
|
|
65295
65623
|
console.error(lang === "fr" ? `\u2192 audit \xE9crit dans ${target}` : `\u2192 audit written to ${target}`);
|
|
65296
65624
|
} catch {
|
|
65297
65625
|
}
|
|
@@ -65310,7 +65638,7 @@ async function cmdAudit(p) {
|
|
|
65310
65638
|
const baselineFlag = p.flags.baseline;
|
|
65311
65639
|
if (typeof baselineFlag === "string" && baselineFlag) {
|
|
65312
65640
|
let baseline = null;
|
|
65313
|
-
if (
|
|
65641
|
+
if (existsSync35(baselineFlag)) {
|
|
65314
65642
|
try {
|
|
65315
65643
|
const parsed = JSON.parse(readText(baselineFlag));
|
|
65316
65644
|
if (isCurrentAudit(parsed)) baseline = parsed;
|
|
@@ -65364,11 +65692,11 @@ async function cmdDev(p) {
|
|
|
65364
65692
|
return 2;
|
|
65365
65693
|
}
|
|
65366
65694
|
if (p.flags.next === true) {
|
|
65367
|
-
const dir =
|
|
65695
|
+
const dir = join51(root, ".ultra11y", "next");
|
|
65368
65696
|
const rel2 = ".ultra11y/next/overlay.jsx";
|
|
65369
65697
|
try {
|
|
65370
|
-
|
|
65371
|
-
|
|
65698
|
+
mkdirSync18(dir, { recursive: true });
|
|
65699
|
+
writeFileSync20(join51(dir, "overlay.jsx"), nextOverlayComponent(port));
|
|
65372
65700
|
} catch (e) {
|
|
65373
65701
|
console.error(`ultra11y dev: could not write ${rel2}: ${e instanceof Error ? e.message : String(e)}`);
|
|
65374
65702
|
return 1;
|
|
@@ -65399,9 +65727,9 @@ async function cmdDev(p) {
|
|
|
65399
65727
|
}
|
|
65400
65728
|
console.error(fr ? `ultra11y dev : tableau de bord sur http://127.0.0.1:${server.port}` : `ultra11y dev: dashboard on http://127.0.0.1:${server.port}`);
|
|
65401
65729
|
console.error(fr ? "Boucle locale uniquement (l'outil \xE9crit des fichiers). Ctrl-C pour arr\xEAter." : "Loopback only (the tool writes files). Ctrl-C to stop.");
|
|
65402
|
-
await new Promise((
|
|
65730
|
+
await new Promise((resolve17) => {
|
|
65403
65731
|
const stop2 = () => {
|
|
65404
|
-
void server.close().then(
|
|
65732
|
+
void server.close().then(resolve17);
|
|
65405
65733
|
};
|
|
65406
65734
|
process.once("SIGINT", stop2);
|
|
65407
65735
|
process.once("SIGTERM", stop2);
|
|
@@ -65417,7 +65745,7 @@ async function cmdPagesDiscover(p) {
|
|
|
65417
65745
|
const snaps = sampleFromSnapshots(readSnapshots(root));
|
|
65418
65746
|
if (!snaps.length) {
|
|
65419
65747
|
console.error(
|
|
65420
|
-
lang === "fr" ? `ultra11y pages discover : aucun instantan\xE9 sous ${
|
|
65748
|
+
lang === "fr" ? `ultra11y pages discover : aucun instantan\xE9 sous ${join51(root, PAGES_DIR)} \u2014 lancez d'abord vos tests E2E avec checkA11y, ou \`scan --sample\`.` : `ultra11y pages discover: no snapshot under ${join51(root, PAGES_DIR)} \u2014 run your E2E tests with checkA11y first, or \`scan --sample\`.`
|
|
65421
65749
|
);
|
|
65422
65750
|
return 1;
|
|
65423
65751
|
}
|
|
@@ -65497,9 +65825,9 @@ ${found} page(s) discovered, ${merged.added.length} new. Nothing was written \u2
|
|
|
65497
65825
|
}
|
|
65498
65826
|
return 0;
|
|
65499
65827
|
}
|
|
65500
|
-
const file =
|
|
65828
|
+
const file = join51(process.cwd(), ".ultra11yrc.json");
|
|
65501
65829
|
let doc = {};
|
|
65502
|
-
if (
|
|
65830
|
+
if (existsSync35(file)) {
|
|
65503
65831
|
try {
|
|
65504
65832
|
doc = JSON.parse(readText(file));
|
|
65505
65833
|
} catch {
|
|
@@ -65508,7 +65836,7 @@ ${found} page(s) discovered, ${merged.added.length} new. Nothing was written \u2
|
|
|
65508
65836
|
}
|
|
65509
65837
|
}
|
|
65510
65838
|
doc.sample = v.sample;
|
|
65511
|
-
|
|
65839
|
+
writeFileSync20(file, `${JSON.stringify(doc, null, 2)}
|
|
65512
65840
|
`);
|
|
65513
65841
|
if (!p.flags.json)
|
|
65514
65842
|
console.log(
|
|
@@ -65568,27 +65896,27 @@ async function cmdPages(p) {
|
|
|
65568
65896
|
const derived = derivePages(result, scope);
|
|
65569
65897
|
const split = p.flags.split === "page";
|
|
65570
65898
|
const outDir = typeof p.flags.out === "string" && p.flags.out ? p.flags.out : void 0;
|
|
65571
|
-
const shotOf = (id) =>
|
|
65899
|
+
const shotOf = (id) => join51(PAGES_DIR, id, "screen.png");
|
|
65572
65900
|
const shotsRelative = (fileDir) => {
|
|
65573
65901
|
const m = /* @__PURE__ */ new Map();
|
|
65574
65902
|
for (const pg of derived) {
|
|
65575
|
-
if (
|
|
65903
|
+
if (existsSync35(shotOf(pg.id))) m.set(pg.id, relative5(fileDir, shotOf(pg.id)).split("\\").join("/"));
|
|
65576
65904
|
}
|
|
65577
65905
|
return m;
|
|
65578
65906
|
};
|
|
65579
65907
|
const shotPaths = (pages) => {
|
|
65580
65908
|
const m = /* @__PURE__ */ new Map();
|
|
65581
|
-
for (const pg of pages) if (
|
|
65909
|
+
for (const pg of pages) if (existsSync35(shotOf(pg.id))) m.set(pg.id, shotOf(pg.id));
|
|
65582
65910
|
return m;
|
|
65583
65911
|
};
|
|
65584
65912
|
const shotsCopiedInto = (dir) => {
|
|
65585
65913
|
const m = /* @__PURE__ */ new Map();
|
|
65586
|
-
const assets =
|
|
65914
|
+
const assets = join51(dir, "assets");
|
|
65587
65915
|
for (const pg of derived) {
|
|
65588
65916
|
const src = shotOf(pg.id);
|
|
65589
|
-
if (!
|
|
65590
|
-
|
|
65591
|
-
copyFileSync4(src,
|
|
65917
|
+
if (!existsSync35(src)) continue;
|
|
65918
|
+
mkdirSync18(assets, { recursive: true });
|
|
65919
|
+
copyFileSync4(src, join51(assets, `${pg.id}.png`));
|
|
65592
65920
|
m.set(pg.id, `./assets/${pg.id}.png`);
|
|
65593
65921
|
}
|
|
65594
65922
|
return m;
|
|
@@ -65605,7 +65933,7 @@ async function cmdPages(p) {
|
|
|
65605
65933
|
console.log(renderPagesDocument(result, derived, { standard, lang, screenshots: shotsRelative(".") }));
|
|
65606
65934
|
return 0;
|
|
65607
65935
|
}
|
|
65608
|
-
|
|
65936
|
+
mkdirSync18(outDir, { recursive: true });
|
|
65609
65937
|
const manifest = wantEvidence ? writeEvidence(result, { outDir, ...evidenceCapsOf(p) }) : void 0;
|
|
65610
65938
|
const cropFor = manifest ? (f) => {
|
|
65611
65939
|
const c2 = manifest.crops.get(findingId(f));
|
|
@@ -65614,7 +65942,7 @@ async function cmdPages(p) {
|
|
|
65614
65942
|
if (manifest) {
|
|
65615
65943
|
const total = evidenceNotice(manifest, null, lang);
|
|
65616
65944
|
console.error(
|
|
65617
|
-
lang === "fr" ? `ultra11y : ${manifest.totals.imaged} vignette(s) \xE9crite(s) dans ${
|
|
65945
|
+
lang === "fr" ? `ultra11y : ${manifest.totals.imaged} vignette(s) \xE9crite(s) dans ${join51(outDir, "assets")}.` : `ultra11y: ${manifest.totals.imaged} crop(s) written to ${join51(outDir, "assets")}.`
|
|
65618
65946
|
);
|
|
65619
65947
|
for (const line of total) console.error(line);
|
|
65620
65948
|
}
|
|
@@ -65623,8 +65951,8 @@ async function cmdPages(p) {
|
|
|
65623
65951
|
...manifest ? { evidenceNotice: (id) => evidenceNotice(manifest, id, lang) } : {}
|
|
65624
65952
|
};
|
|
65625
65953
|
if (!split) {
|
|
65626
|
-
const file =
|
|
65627
|
-
|
|
65954
|
+
const file = join51(outDir, `pages-${result.date}.md`);
|
|
65955
|
+
writeFileSync20(file, `${renderPagesDocument(result, derived, { standard, lang, screenshots: shotsCopiedInto(outDir), ...evidenceOpts })}
|
|
65628
65956
|
`);
|
|
65629
65957
|
const html2 = wantHtml ? emitHtml(result, {
|
|
65630
65958
|
outDir,
|
|
@@ -65643,11 +65971,11 @@ async function cmdPages(p) {
|
|
|
65643
65971
|
const sheet = (id) => `page-${id}.md`;
|
|
65644
65972
|
const hrefs = new Map(derived.map((pg) => [pg.id, `./${sheet(pg.id)}`]));
|
|
65645
65973
|
for (const pg of derived) {
|
|
65646
|
-
|
|
65974
|
+
writeFileSync20(join51(outDir, sheet(pg.id)), `${renderPageDocument(result, pg, { standard, lang, screenshots: shots, ...evidenceOpts })}
|
|
65647
65975
|
`);
|
|
65648
65976
|
}
|
|
65649
|
-
const index =
|
|
65650
|
-
|
|
65977
|
+
const index = join51(outDir, "index.md");
|
|
65978
|
+
writeFileSync20(index, `${renderPagesIndex(result, derived, { standard, lang, hrefs })}
|
|
65651
65979
|
`);
|
|
65652
65980
|
const html = wantHtml ? emitHtml(result, {
|
|
65653
65981
|
outDir,
|
|
@@ -65698,7 +66026,7 @@ async function cmdSnapshot(p) {
|
|
|
65698
66026
|
2
|
|
65699
66027
|
)
|
|
65700
66028
|
);
|
|
65701
|
-
else if (!snaps.length) console.log(lang === "fr" ? `Aucun instantan\xE9 dans ${
|
|
66029
|
+
else if (!snaps.length) console.log(lang === "fr" ? `Aucun instantan\xE9 dans ${join51(root, PAGES_DIR)}.` : `No snapshot in ${join51(root, PAGES_DIR)}.`);
|
|
65702
66030
|
else for (const s of snaps) console.log(`${s.meta.id} ${s.meta.name} ${s.meta.url}${s.meta.auth ? " [auth]" : ""}`);
|
|
65703
66031
|
return 0;
|
|
65704
66032
|
}
|
|
@@ -65745,7 +66073,7 @@ async function cmdSnapshot(p) {
|
|
|
65745
66073
|
console.error(`ultra11y snapshot write: could not write the snapshot: ${e instanceof Error ? e.message : String(e)}`);
|
|
65746
66074
|
return 1;
|
|
65747
66075
|
}
|
|
65748
|
-
const result = runAudit({ inputs: [
|
|
66076
|
+
const result = runAudit({ inputs: [join51(dir, "dom.html")], onWarn: (m) => console.error(m) });
|
|
65749
66077
|
const failOnRaw = p.flags["fail-on"];
|
|
65750
66078
|
const failOnParsed = parseFailOn(failOnRaw);
|
|
65751
66079
|
if (failOnRaw !== void 0 && failOnParsed === null) {
|
|
@@ -65777,9 +66105,9 @@ function cmdInit(p) {
|
|
|
65777
66105
|
if (want.baseline) {
|
|
65778
66106
|
const inputs = p.positionals.length ? p.positionals : ["."];
|
|
65779
66107
|
const result = runAudit({ inputs, onWarn: (m) => console.error(m) });
|
|
65780
|
-
|
|
65781
|
-
const bp =
|
|
65782
|
-
|
|
66108
|
+
mkdirSync18(join51(root, "audits"), { recursive: true });
|
|
66109
|
+
const bp = join51(root, "audits", "baseline.json");
|
|
66110
|
+
writeFileSync20(bp, JSON.stringify(result, null, 2) + "\n");
|
|
65783
66111
|
wrote.push(bp);
|
|
65784
66112
|
}
|
|
65785
66113
|
if (want.hook) wrote.push(writeHook(root, engineRel, failOn, legacy ? "baseline" : "staged"));
|
|
@@ -66078,8 +66406,8 @@ async function cmdTickets(p) {
|
|
|
66078
66406
|
const ticketsOut = typeof p.flags.out === "string" && p.flags.out ? p.flags.out : void 0;
|
|
66079
66407
|
let setPath;
|
|
66080
66408
|
if (ticketsOut) {
|
|
66081
|
-
|
|
66082
|
-
setPath =
|
|
66409
|
+
mkdirSync18(ticketsOut, { recursive: true });
|
|
66410
|
+
setPath = join51(ticketsOut, `issues-${result.date}.json`);
|
|
66083
66411
|
const payload = {
|
|
66084
66412
|
tool: "ultra11y",
|
|
66085
66413
|
kind: "issues",
|
|
@@ -66090,7 +66418,7 @@ async function cmdTickets(p) {
|
|
|
66090
66418
|
count: plan.tickets.length,
|
|
66091
66419
|
issues: plan.tickets
|
|
66092
66420
|
};
|
|
66093
|
-
|
|
66421
|
+
writeFileSync20(setPath, `${JSON.stringify(payload, null, 2)}
|
|
66094
66422
|
`);
|
|
66095
66423
|
if (!json) console.log(setPath);
|
|
66096
66424
|
}
|
|
@@ -66180,8 +66508,8 @@ async function cmdTickets(p) {
|
|
|
66180
66508
|
return pushed.failed > 0 ? 1 : 0;
|
|
66181
66509
|
}
|
|
66182
66510
|
function depsAt(root) {
|
|
66183
|
-
const pkgPath =
|
|
66184
|
-
if (!
|
|
66511
|
+
const pkgPath = join51(root, "package.json");
|
|
66512
|
+
if (!existsSync35(pkgPath)) return {};
|
|
66185
66513
|
try {
|
|
66186
66514
|
const pkg = JSON.parse(readText(pkgPath));
|
|
66187
66515
|
return { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
@@ -66207,24 +66535,24 @@ function cmdRender(p) {
|
|
|
66207
66535
|
console.error(`ultra11y render: --runner must be playwright|cypress|auto (got "${forced}").`);
|
|
66208
66536
|
return 2;
|
|
66209
66537
|
}
|
|
66210
|
-
const detected = detectE2eRunner(depsAt(root), (f) =>
|
|
66538
|
+
const detected = detectE2eRunner(depsAt(root), (f) => existsSync35(join51(root, f)));
|
|
66211
66539
|
const runners = forced && forced !== "auto" ? [forced] : detected;
|
|
66212
66540
|
if (!runners.length) {
|
|
66213
66541
|
console.error(e2eSetupPlan([], {}, lang));
|
|
66214
66542
|
return 1;
|
|
66215
66543
|
}
|
|
66216
66544
|
const engineRef = engineRefFor(root);
|
|
66217
|
-
const dir =
|
|
66545
|
+
const dir = join51(root, ".ultra11y", "e2e");
|
|
66218
66546
|
const paths = {};
|
|
66219
66547
|
try {
|
|
66220
|
-
|
|
66548
|
+
mkdirSync18(dir, { recursive: true });
|
|
66221
66549
|
if (runners.includes("playwright")) {
|
|
66222
|
-
|
|
66550
|
+
writeFileSync20(join51(dir, "playwright.mjs"), playwrightFixture(engineRef));
|
|
66223
66551
|
paths.playwright = ".ultra11y/e2e/playwright.mjs";
|
|
66224
66552
|
}
|
|
66225
66553
|
if (runners.includes("cypress")) {
|
|
66226
|
-
|
|
66227
|
-
|
|
66554
|
+
writeFileSync20(join51(dir, "cypress-plugin.mjs"), cypressPlugin(engineRef));
|
|
66555
|
+
writeFileSync20(join51(dir, "cypress-commands.mjs"), cypressCommands());
|
|
66228
66556
|
paths.cypressPlugin = ".ultra11y/e2e/cypress-plugin.mjs";
|
|
66229
66557
|
paths.cypressCommands = ".ultra11y/e2e/cypress-commands.mjs";
|
|
66230
66558
|
}
|
|
@@ -66239,7 +66567,7 @@ function cmdRender(p) {
|
|
|
66239
66567
|
if (p.flags.scaffold === true) {
|
|
66240
66568
|
const out2 = typeof p.flags.out === "string" && p.flags.out ? p.flags.out : "ultra11y-render.tsx";
|
|
66241
66569
|
try {
|
|
66242
|
-
|
|
66570
|
+
writeFileSync20(out2, ssrHarness());
|
|
66243
66571
|
} catch (e) {
|
|
66244
66572
|
console.error(`ultra11y render: could not write ${out2}: ${e instanceof Error ? e.message : String(e)}`);
|
|
66245
66573
|
return 1;
|
|
@@ -66253,29 +66581,29 @@ Fill in COMPONENTS, run it (e.g. npx tsx ${out2}), then: node scripts/ultra11y.m
|
|
|
66253
66581
|
}
|
|
66254
66582
|
if (p.flags.setup === true) {
|
|
66255
66583
|
const rel2 = ".ultra11y/capture-setup.mjs";
|
|
66256
|
-
const out2 =
|
|
66584
|
+
const out2 = join51(root, rel2);
|
|
66257
66585
|
try {
|
|
66258
|
-
|
|
66259
|
-
|
|
66586
|
+
mkdirSync18(dirname15(out2), { recursive: true });
|
|
66587
|
+
writeFileSync20(out2, captureSetup());
|
|
66260
66588
|
} catch (e) {
|
|
66261
66589
|
console.error(`ultra11y render: could not write ${out2}: ${e instanceof Error ? e.message : String(e)}`);
|
|
66262
66590
|
return 1;
|
|
66263
66591
|
}
|
|
66264
66592
|
let setupDeps = {};
|
|
66265
|
-
const setupPkg =
|
|
66266
|
-
if (
|
|
66593
|
+
const setupPkg = join51(root, "package.json");
|
|
66594
|
+
if (existsSync35(setupPkg)) {
|
|
66267
66595
|
try {
|
|
66268
66596
|
const pkg = JSON.parse(readText(setupPkg));
|
|
66269
66597
|
setupDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
66270
66598
|
} catch {
|
|
66271
66599
|
}
|
|
66272
66600
|
}
|
|
66273
|
-
const tr = detectTestRunner(setupDeps, (f) =>
|
|
66601
|
+
const tr = detectTestRunner(setupDeps, (f) => existsSync35(join51(root, f)));
|
|
66274
66602
|
console.log(captureSetupPlan(tr, rel2, lang));
|
|
66275
66603
|
const gaLine = ".ultra11y/captures/*.html text eol=lf linguist-generated=true";
|
|
66276
|
-
const gaPath =
|
|
66604
|
+
const gaPath = join51(root, ".gitattributes");
|
|
66277
66605
|
try {
|
|
66278
|
-
const existing =
|
|
66606
|
+
const existing = existsSync35(gaPath) ? readFileSync33(gaPath, "utf8") : "";
|
|
66279
66607
|
if (!existing.includes(".ultra11y/captures/")) {
|
|
66280
66608
|
appendFileSync(gaPath, (existing && !existing.endsWith("\n") ? "\n" : "") + gaLine + "\n");
|
|
66281
66609
|
console.log(lang === "fr" ? `.gitattributes : ajout\xE9 \xAB ${gaLine} \xBB` : `.gitattributes: added "${gaLine}"`);
|
|
@@ -66283,8 +66611,8 @@ Fill in COMPONENTS, run it (e.g. npx tsx ${out2}), then: node scripts/ultra11y.m
|
|
|
66283
66611
|
} catch {
|
|
66284
66612
|
}
|
|
66285
66613
|
try {
|
|
66286
|
-
const giPath =
|
|
66287
|
-
if (
|
|
66614
|
+
const giPath = join51(root, ".gitignore");
|
|
66615
|
+
if (existsSync35(giPath) && /^\s*\/?\.ultra11y(\/\**)?\/?\s*$/m.test(readFileSync33(giPath, "utf8")))
|
|
66288
66616
|
console.error(
|
|
66289
66617
|
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/").'
|
|
66290
66618
|
);
|
|
@@ -66294,8 +66622,8 @@ Fill in COMPONENTS, run it (e.g. npx tsx ${out2}), then: node scripts/ultra11y.m
|
|
|
66294
66622
|
}
|
|
66295
66623
|
if (p.flags.storybook === true || typeof p.flags.storybook === "string") {
|
|
66296
66624
|
const sbDir = p.positionals[0] ?? "storybook-static";
|
|
66297
|
-
const indexPath =
|
|
66298
|
-
if (!
|
|
66625
|
+
const indexPath = existsSync35(join51(sbDir, "index.json")) ? join51(sbDir, "index.json") : join51(sbDir, "stories.json");
|
|
66626
|
+
if (!existsSync35(indexPath)) {
|
|
66299
66627
|
console.error(
|
|
66300
66628
|
lang === "fr" ? `ultra11y render : aucun index Storybook (index.json/stories.json) dans ${sbDir}.` : `ultra11y render: no Storybook index (index.json/stories.json) in ${sbDir}.`
|
|
66301
66629
|
);
|
|
@@ -66305,7 +66633,7 @@ Fill in COMPONENTS, run it (e.g. npx tsx ${out2}), then: node scripts/ultra11y.m
|
|
|
66305
66633
|
const provById = new Map(stories.map((s) => [s.id, storyProvenance(s)]));
|
|
66306
66634
|
const capturesFlag = typeof p.flags.captures === "string" && p.flags.captures ? p.flags.captures : void 0;
|
|
66307
66635
|
const htmlDir = capturesFlag ?? sbDir;
|
|
66308
|
-
const htmlFiles =
|
|
66636
|
+
const htmlFiles = existsSync35(htmlDir) ? discover([htmlDir]).files.filter((f) => /\.html?$/i.test(f)) : [];
|
|
66309
66637
|
const outDir = ".ultra11y/captures";
|
|
66310
66638
|
let attributed = 0;
|
|
66311
66639
|
let skipped = 0;
|
|
@@ -66327,8 +66655,8 @@ Fill in COMPONENTS, run it (e.g. npx tsx ${out2}), then: node scripts/ultra11y.m
|
|
|
66327
66655
|
continue;
|
|
66328
66656
|
}
|
|
66329
66657
|
try {
|
|
66330
|
-
|
|
66331
|
-
|
|
66658
|
+
mkdirSync18(outDir, { recursive: true });
|
|
66659
|
+
writeFileSync20(join51(outDir, `${hitId}.html`), `${formatCaptureComment(prov)}
|
|
66332
66660
|
${raw}${raw.endsWith("\n") ? "" : "\n"}`);
|
|
66333
66661
|
attributed++;
|
|
66334
66662
|
} catch {
|
|
@@ -66350,11 +66678,11 @@ ${raw}${raw.endsWith("\n") ? "" : "\n"}`);
|
|
|
66350
66678
|
}
|
|
66351
66679
|
if (p.flags.coverage === true) {
|
|
66352
66680
|
const capturesFlag = typeof p.flags.captures === "string" && p.flags.captures ? p.flags.captures : void 0;
|
|
66353
|
-
const capturesDir = capturesFlag ??
|
|
66681
|
+
const capturesDir = capturesFlag ?? join51(root, ".ultra11y/captures");
|
|
66354
66682
|
const graphExt = [...GRAPH_ONLY_EXT, ...asList(p.flags.ext) ?? []];
|
|
66355
66683
|
const sourceFiles = discover([root], { include: asList(p.flags.include), exclude: asList(p.flags.exclude), ext: graphExt }).files;
|
|
66356
66684
|
const graph = buildGraphStreaming(sourceFiles);
|
|
66357
|
-
const capFiles =
|
|
66685
|
+
const capFiles = existsSync35(capturesDir) ? discover([capturesDir]).files : [];
|
|
66358
66686
|
const entries = capFiles.map((f) => ({ file: toPosix(f), provenance: parseCaptureProvenance(readText(f)) }));
|
|
66359
66687
|
const cov = computeCaptureCoverage(graph, entries);
|
|
66360
66688
|
if (p.flags.json) console.log(JSON.stringify(cov, null, 2));
|
|
@@ -66362,15 +66690,15 @@ ${raw}${raw.endsWith("\n") ? "" : "\n"}`);
|
|
|
66362
66690
|
return 0;
|
|
66363
66691
|
}
|
|
66364
66692
|
let deps = {};
|
|
66365
|
-
const pkgPath =
|
|
66366
|
-
if (
|
|
66693
|
+
const pkgPath = join51(root, "package.json");
|
|
66694
|
+
if (existsSync35(pkgPath)) {
|
|
66367
66695
|
try {
|
|
66368
66696
|
const pkg = JSON.parse(readText(pkgPath));
|
|
66369
66697
|
deps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
66370
66698
|
} catch {
|
|
66371
66699
|
}
|
|
66372
66700
|
}
|
|
66373
|
-
const detection = detectFrameworks(deps, (f) =>
|
|
66701
|
+
const detection = detectFrameworks(deps, (f) => existsSync35(join51(root, f)));
|
|
66374
66702
|
if (p.flags.json) console.log(JSON.stringify(detection, null, 2));
|
|
66375
66703
|
else console.log(renderPlan(detection, lang));
|
|
66376
66704
|
return 0;
|
|
@@ -66443,6 +66771,9 @@ function cmdVerify(p) {
|
|
|
66443
66771
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed) && parsed.kind === "adjudication") {
|
|
66444
66772
|
return applyAdjudicationFile(p, parsed, lang);
|
|
66445
66773
|
}
|
|
66774
|
+
if (isLedger(parsed)) {
|
|
66775
|
+
return replayLedgerFile(p, parsed, lang);
|
|
66776
|
+
}
|
|
66446
66777
|
if (!Array.isArray(parsed)) {
|
|
66447
66778
|
console.error("ultra11y verify: --apply must be a JSON array of verdicts, or an adjudication object.");
|
|
66448
66779
|
return 2;
|
|
@@ -66544,6 +66875,84 @@ function cmdVerify(p) {
|
|
|
66544
66875
|
);
|
|
66545
66876
|
return 0;
|
|
66546
66877
|
}
|
|
66878
|
+
function ledgerTarget(p, standard) {
|
|
66879
|
+
const flag = p.flags.ledger;
|
|
66880
|
+
if (flag === void 0 || flag === false) return void 0;
|
|
66881
|
+
return typeof flag === "string" && flag ? flag : ledgerPath(standard);
|
|
66882
|
+
}
|
|
66883
|
+
function replayLedgerFile(p, ledger, lang) {
|
|
66884
|
+
const inFlag = p.flags.in;
|
|
66885
|
+
if (typeof inFlag !== "string" || !inFlag) {
|
|
66886
|
+
console.error(
|
|
66887
|
+
lang === "fr" ? "ultra11y verify : --apply <registre> exige --in <audit.json> (l'audit \xE0 mettre \xE0 jour)." : "ultra11y verify: --apply <ledger> requires --in <audit.json> (the audit to update)."
|
|
66888
|
+
);
|
|
66889
|
+
return 2;
|
|
66890
|
+
}
|
|
66891
|
+
let audit2;
|
|
66892
|
+
try {
|
|
66893
|
+
audit2 = JSON.parse(readText(inFlag));
|
|
66894
|
+
} catch {
|
|
66895
|
+
console.error(`ultra11y verify: --in file not found or not valid JSON: ${inFlag}.`);
|
|
66896
|
+
return 2;
|
|
66897
|
+
}
|
|
66898
|
+
const cwd = typeof p.flags.cwd === "string" ? p.flags.cwd : void 0;
|
|
66899
|
+
const standard = resolveStandard(p.flags.standard) ?? ledger.standard;
|
|
66900
|
+
const rp = replayLedger(audit2, ledger, { cwd, standard });
|
|
66901
|
+
const r = applyAdjudication(audit2, rp.adj, { cwd, strict: p.flags.strict === true, residualReasons: rp.residualReasons });
|
|
66902
|
+
if (!r.ok && (p.flags.strict === true || r.applied + r.stillManual === 0)) {
|
|
66903
|
+
if (p.flags.json) console.log(JSON.stringify({ ...r, replay: rp }, null, 2));
|
|
66904
|
+
else {
|
|
66905
|
+
console.error(lang === "fr" ? `\u2717 Registre rejet\xE9 (${r.issues.length} probl\xE8me(s)) :` : `\u2717 Ledger rejected (${r.issues.length} issue(s)):`);
|
|
66906
|
+
for (const i2 of r.issues) console.error(` \u2717 ${i2}`);
|
|
66907
|
+
}
|
|
66908
|
+
return 1;
|
|
66909
|
+
}
|
|
66910
|
+
const out2 = typeof p.flags.out === "string" ? p.flags.out : ".";
|
|
66911
|
+
mkdirSync18(out2, { recursive: true });
|
|
66912
|
+
const auditPath = join51(out2, "audit-latest.json");
|
|
66913
|
+
writeFileSync20(auditPath, `${JSON.stringify(r.audit, null, 2)}
|
|
66914
|
+
`);
|
|
66915
|
+
if (p.flags.json)
|
|
66916
|
+
console.log(
|
|
66917
|
+
JSON.stringify(
|
|
66918
|
+
{
|
|
66919
|
+
ok: true,
|
|
66920
|
+
auditPath,
|
|
66921
|
+
applied: r.applied,
|
|
66922
|
+
stillManual: r.stillManual,
|
|
66923
|
+
rejected: r.rejected,
|
|
66924
|
+
rejectedCriteria: r.rejectedCriteria,
|
|
66925
|
+
replayed: rp.fresh.length,
|
|
66926
|
+
stale: rp.stale,
|
|
66927
|
+
obsolete: rp.obsolete,
|
|
66928
|
+
missing: rp.missing,
|
|
66929
|
+
issues: r.issues,
|
|
66930
|
+
conformancePct: r.audit.conformancePct,
|
|
66931
|
+
grounding: r.grounding
|
|
66932
|
+
},
|
|
66933
|
+
null,
|
|
66934
|
+
2
|
|
66935
|
+
)
|
|
66936
|
+
);
|
|
66937
|
+
else {
|
|
66938
|
+
console.log(
|
|
66939
|
+
lang === "fr" ? `\u2713 ${r.applied} verdict(s) rejou\xE9(s) depuis le registre, ${r.stillManual} laiss\xE9(s) en r\xE9siduel${r.rejected ? `, ${r.rejected} refus\xE9(s)` : ""} \u2192 ${auditPath}` : `\u2713 ${r.applied} verdict(s) replayed from the ledger, ${r.stillManual} left residual${r.rejected ? `, ${r.rejected} refused` : ""} \u2192 ${auditPath}`
|
|
66940
|
+
);
|
|
66941
|
+
if (rp.stale.length)
|
|
66942
|
+
console.error(
|
|
66943
|
+
lang === "fr" ? `\u26A0 ${rp.stale.length} verdict(s) p\xE9rim\xE9(s) (l'\xE9vidence a chang\xE9) : ${rp.stale.join(", ")}` : `\u26A0 ${rp.stale.length} stale verdict(s) (the evidence changed): ${rp.stale.join(", ")}`
|
|
66944
|
+
);
|
|
66945
|
+
if (rp.missing.length)
|
|
66946
|
+
console.error(
|
|
66947
|
+
lang === "fr" ? `\u26A0 ${rp.missing.length} crit\xE8re(s) absent(s) du registre : ${rp.missing.join(", ")}` : `\u26A0 ${rp.missing.length} criterion(ia) absent from the ledger: ${rp.missing.join(", ")}`
|
|
66948
|
+
);
|
|
66949
|
+
if (rp.obsolete.length)
|
|
66950
|
+
console.error(
|
|
66951
|
+
lang === "fr" ? `\u2139 ${rp.obsolete.length} entr\xE9e(s) obsol\xE8te(s) (le moteur les d\xE9cide d\xE9sormais) : ${rp.obsolete.join(", ")}` : `\u2139 ${rp.obsolete.length} obsolete entry(ies) (the engine now decides them): ${rp.obsolete.join(", ")}`
|
|
66952
|
+
);
|
|
66953
|
+
}
|
|
66954
|
+
return 0;
|
|
66955
|
+
}
|
|
66547
66956
|
function applyAdjudicationFile(p, adj, lang) {
|
|
66548
66957
|
const inFlag = p.flags.in;
|
|
66549
66958
|
if (typeof inFlag !== "string" || !inFlag) {
|
|
@@ -66559,8 +66968,11 @@ function applyAdjudicationFile(p, adj, lang) {
|
|
|
66559
66968
|
console.error(`ultra11y verify: --in file not found or not valid JSON: ${inFlag}.`);
|
|
66560
66969
|
return 2;
|
|
66561
66970
|
}
|
|
66562
|
-
const
|
|
66563
|
-
|
|
66971
|
+
const cwd = typeof p.flags.cwd === "string" ? p.flags.cwd : void 0;
|
|
66972
|
+
hydrateAdjudication(adj, audit2, { cwd });
|
|
66973
|
+
const strict = p.flags.strict === true;
|
|
66974
|
+
const r = applyAdjudication(audit2, adj, { cwd, strict });
|
|
66975
|
+
if (!r.ok && (strict || r.applied + r.stillManual === 0)) {
|
|
66564
66976
|
if (p.flags.json) console.log(JSON.stringify(r, null, 2));
|
|
66565
66977
|
else {
|
|
66566
66978
|
console.error(lang === "fr" ? `\u2717 Adjudication rejet\xE9e (${r.issues.length} probl\xE8me(s)) :` : `\u2717 Adjudication rejected (${r.issues.length} issue(s)):`);
|
|
@@ -66568,10 +66980,27 @@ function applyAdjudicationFile(p, adj, lang) {
|
|
|
66568
66980
|
}
|
|
66569
66981
|
return 1;
|
|
66570
66982
|
}
|
|
66983
|
+
if (r.rejected > 0 && !p.flags.json) {
|
|
66984
|
+
console.error(
|
|
66985
|
+
lang === "fr" ? `\u26A0 ${r.rejected} crit\xE8re(s) refus\xE9(s) par la porte \u2014 ils restent \xE0 \xE9valuer, avec le motif du refus :` : `\u26A0 ${r.rejected} criterion(ia) refused by the gate \u2014 they stay to assess, carrying the refusal:`
|
|
66986
|
+
);
|
|
66987
|
+
for (const i2 of r.issues) console.error(` \u2717 ${i2}`);
|
|
66988
|
+
}
|
|
66571
66989
|
const out2 = typeof p.flags.out === "string" ? p.flags.out : ".";
|
|
66572
|
-
|
|
66573
|
-
const auditPath =
|
|
66574
|
-
|
|
66990
|
+
mkdirSync18(out2, { recursive: true });
|
|
66991
|
+
const auditPath = join51(out2, "audit-latest.json");
|
|
66992
|
+
writeFileSync20(auditPath, JSON.stringify(r.audit, null, 2) + "\n");
|
|
66993
|
+
const ledgerOut = ledgerTarget(p, adj.standard);
|
|
66994
|
+
if (ledgerOut) {
|
|
66995
|
+
const refused = new Set(r.rejectedCriteria);
|
|
66996
|
+
const accepted = new Set(adj.items.map((it) => it.criteriaId).filter((id) => !refused.has(id)));
|
|
66997
|
+
const fresh = entriesFrom(adj, accepted, r.audit.date);
|
|
66998
|
+
writeLedger(ledgerOut, mergeLedger(readLedger(ledgerOut), adj.standard, fresh));
|
|
66999
|
+
if (!p.flags.json)
|
|
67000
|
+
console.log(
|
|
67001
|
+
lang === "fr" ? `\u2713 ${fresh.length} verdict(s) enregistr\xE9(s) au registre \u2192 ${ledgerOut}` : `\u2713 ${fresh.length} verdict(s) recorded in the ledger \u2192 ${ledgerOut}`
|
|
67002
|
+
);
|
|
67003
|
+
}
|
|
66575
67004
|
if (p.flags.json)
|
|
66576
67005
|
console.log(
|
|
66577
67006
|
JSON.stringify(
|
|
@@ -66580,6 +67009,9 @@ function applyAdjudicationFile(p, adj, lang) {
|
|
|
66580
67009
|
auditPath,
|
|
66581
67010
|
applied: r.applied,
|
|
66582
67011
|
stillManual: r.stillManual,
|
|
67012
|
+
rejected: r.rejected,
|
|
67013
|
+
rejectedCriteria: r.rejectedCriteria,
|
|
67014
|
+
issues: r.issues,
|
|
66583
67015
|
conformancePct: r.audit.conformancePct,
|
|
66584
67016
|
findings: r.audit.findings.length,
|
|
66585
67017
|
grounding: r.grounding
|
|
@@ -66590,7 +67022,7 @@ function applyAdjudicationFile(p, adj, lang) {
|
|
|
66590
67022
|
);
|
|
66591
67023
|
else
|
|
66592
67024
|
console.log(
|
|
66593
|
-
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}`
|
|
67025
|
+
lang === "fr" ? `\u2713 ${r.applied} crit\xE8re(s) adjug\xE9(s), ${r.stillManual} laiss\xE9(s) en r\xE9siduel${r.rejected ? `, ${r.rejected} refus\xE9(s)` : ""} \u2192 ${auditPath}` : `\u2713 ${r.applied} criterion(ia) adjudicated, ${r.stillManual} left residual${r.rejected ? `, ${r.rejected} refused` : ""} \u2192 ${auditPath}`
|
|
66594
67026
|
);
|
|
66595
67027
|
return 0;
|
|
66596
67028
|
}
|
|
@@ -66629,11 +67061,11 @@ async function cmdJudge(p) {
|
|
|
66629
67061
|
}
|
|
66630
67062
|
const max = typeof p.flags.max === "string" ? Number(p.flags.max) : void 0;
|
|
66631
67063
|
const truncated = max !== void 0 && Number.isFinite(max) && max > 0 && items.length > max;
|
|
66632
|
-
if (truncated && p.flags.apply === true) {
|
|
67064
|
+
if (truncated && p.flags.apply === true && p.flags.strict === true) {
|
|
66633
67065
|
console.error(
|
|
66634
|
-
lang === "fr" ? `ultra11y judge : --max ${max} ne couvre que ${max} des ${items.length} crit\xE8res, et --apply exige une adjudication COMPL\xC8TE (le
|
|
66635
|
-
Retirez --max pour tout adjuger, ou retirez --apply pour produire la worklist et l'appliquer plus tard.` : `ultra11y judge: --max ${max} covers only ${max} of ${items.length} criteria, and --apply requires a COMPLETE adjudication (the
|
|
66636
|
-
Drop --max to adjudicate everything, or drop --apply to produce the worklist and apply it later.`
|
|
67066
|
+
lang === "fr" ? `ultra11y judge : --max ${max} ne couvre que ${max} des ${items.length} crit\xE8res, et --apply --strict exige une adjudication COMPL\xC8TE (le fold tout-ou-rien refuse une couverture partielle).
|
|
67067
|
+
Retirez --strict pour appliquer ce qui est couvert, retirez --max pour tout adjuger, ou retirez --apply pour produire la worklist et l'appliquer plus tard.` : `ultra11y judge: --max ${max} covers only ${max} of ${items.length} criteria, and --apply --strict requires a COMPLETE adjudication (the all-or-nothing fold refuses partial coverage).
|
|
67068
|
+
Drop --strict to apply what is covered, drop --max to adjudicate everything, or drop --apply to produce the worklist and apply it later.`
|
|
66637
67069
|
);
|
|
66638
67070
|
return 2;
|
|
66639
67071
|
}
|
|
@@ -66672,19 +67104,36 @@ async function cmdJudge(p) {
|
|
|
66672
67104
|
return 0;
|
|
66673
67105
|
}
|
|
66674
67106
|
const adj = JSON.parse(readText(w.todoPath));
|
|
66675
|
-
const r = applyAdjudication(audit2, adj);
|
|
66676
|
-
if (!r.ok) {
|
|
67107
|
+
const r = applyAdjudication(audit2, adj, { strict: p.flags.strict === true });
|
|
67108
|
+
if (!r.ok && (p.flags.strict === true || r.applied + r.stillManual === 0)) {
|
|
66677
67109
|
console.error(lang === "fr" ? `\u2717 Adjudication rejet\xE9e (${r.issues.length} probl\xE8me(s)) :` : `\u2717 Adjudication rejected (${r.issues.length} issue(s)):`);
|
|
66678
67110
|
for (const i2 of r.issues.slice(0, 40)) console.error(` \u2717 ${i2}`);
|
|
66679
67111
|
if (r.issues.length > 40) console.error(` \u2026 +${r.issues.length - 40}`);
|
|
66680
67112
|
return 1;
|
|
66681
67113
|
}
|
|
66682
|
-
|
|
66683
|
-
|
|
66684
|
-
|
|
67114
|
+
if (r.rejected > 0) {
|
|
67115
|
+
console.error(
|
|
67116
|
+
lang === "fr" ? `\u26A0 ${r.rejected} crit\xE8re(s) refus\xE9(s) par la porte \u2014 ils restent \xE0 \xE9valuer, avec le motif du refus :` : `\u26A0 ${r.rejected} criterion(ia) refused by the gate \u2014 they stay to assess, carrying the refusal:`
|
|
67117
|
+
);
|
|
67118
|
+
for (const i2 of r.issues.slice(0, 40)) console.error(` \u2717 ${i2}`);
|
|
67119
|
+
if (r.issues.length > 40) console.error(` \u2026 +${r.issues.length - 40}`);
|
|
67120
|
+
}
|
|
67121
|
+
mkdirSync18(out2, { recursive: true });
|
|
67122
|
+
const auditPath = join51(out2, "audit-latest.json");
|
|
67123
|
+
writeFileSync20(auditPath, `${JSON.stringify(r.audit, null, 2)}
|
|
66685
67124
|
`);
|
|
67125
|
+
const ledgerOut = ledgerTarget(p, adj.standard);
|
|
67126
|
+
if (ledgerOut) {
|
|
67127
|
+
const refused = new Set(r.rejectedCriteria);
|
|
67128
|
+
const accepted = new Set(adj.items.map((it) => it.criteriaId).filter((id) => !refused.has(id)));
|
|
67129
|
+
const fresh = entriesFrom(adj, accepted, r.audit.date);
|
|
67130
|
+
writeLedger(ledgerOut, mergeLedger(readLedger(ledgerOut), adj.standard, fresh));
|
|
67131
|
+
console.log(
|
|
67132
|
+
lang === "fr" ? `\u2713 ${fresh.length} verdict(s) enregistr\xE9(s) au registre \u2192 ${ledgerOut}` : `\u2713 ${fresh.length} verdict(s) recorded in the ledger \u2192 ${ledgerOut}`
|
|
67133
|
+
);
|
|
67134
|
+
}
|
|
66686
67135
|
console.log(
|
|
66687
|
-
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}`
|
|
67136
|
+
lang === "fr" ? `\u2713 ${r.applied} crit\xE8re(s) adjug\xE9(s), ${r.stillManual} laiss\xE9(s) en r\xE9siduel${r.rejected ? `, ${r.rejected} refus\xE9(s)` : ""} \u2192 ${auditPath}` : `\u2713 ${r.applied} criterion(ia) adjudicated, ${r.stillManual} left residual${r.rejected ? `, ${r.rejected} refused` : ""} \u2192 ${auditPath}`
|
|
66688
67137
|
);
|
|
66689
67138
|
return 0;
|
|
66690
67139
|
}
|
|
@@ -66750,7 +67199,7 @@ async function cmdScan(p) {
|
|
|
66750
67199
|
return 0;
|
|
66751
67200
|
}
|
|
66752
67201
|
for (const target of p.positionals.filter((a) => a !== "-")) {
|
|
66753
|
-
if (/^https?:\/\//i.test(target) ||
|
|
67202
|
+
if (/^https?:\/\//i.test(target) || existsSync35(target)) continue;
|
|
66754
67203
|
console.error(
|
|
66755
67204
|
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.`
|
|
66756
67205
|
);
|
|
@@ -66892,7 +67341,7 @@ async function cmdScan(p) {
|
|
|
66892
67341
|
}
|
|
66893
67342
|
let merged = mergeDynamic(audit2, dynamic, lang);
|
|
66894
67343
|
if (dynamic.snapshots?.length) {
|
|
66895
|
-
const doms = dynamic.snapshots.map((id) =>
|
|
67344
|
+
const doms = dynamic.snapshots.map((id) => join51(snapshotRoot ?? ".", PAGES_DIR, id, "dom.html")).filter((f) => existsSync35(f));
|
|
66896
67345
|
if (doms.length) {
|
|
66897
67346
|
const snapAudit = runAudit({ inputs: doms, onWarn: (m) => console.error(m) });
|
|
66898
67347
|
merged = mergeSnapshotAudit(merged, snapAudit);
|
|
@@ -66903,12 +67352,12 @@ async function cmdScan(p) {
|
|
|
66903
67352
|
attributePages(merged, scope);
|
|
66904
67353
|
}
|
|
66905
67354
|
}
|
|
66906
|
-
|
|
66907
|
-
|
|
67355
|
+
mkdirSync18(out2, { recursive: true });
|
|
67356
|
+
writeFileSync20(join51(out2, "audit-latest.json"), JSON.stringify(merged, null, 2) + "\n");
|
|
66908
67357
|
if (p.flags.json) console.log(JSON.stringify(merged, null, 2));
|
|
66909
67358
|
else {
|
|
66910
67359
|
console.log(
|
|
66911
|
-
lang === "fr" ? `Audit statique + dynamique fusionn\xE9 \u2192 ${
|
|
67360
|
+
lang === "fr" ? `Audit statique + dynamique fusionn\xE9 \u2192 ${join51(out2, "audit-latest.json")} (${merged.conformancePct}% r\xE9ussite, ${merged.findings.length} findings).` : `Static + dynamic audit merged \u2192 ${join51(out2, "audit-latest.json")} (${merged.conformancePct}% pass rate, ${merged.findings.length} findings).`
|
|
66912
67361
|
);
|
|
66913
67362
|
if (dynamic.snapshots?.length)
|
|
66914
67363
|
console.log(
|
|
@@ -67150,9 +67599,9 @@ async function cmdImport(p) {
|
|
|
67150
67599
|
return 1;
|
|
67151
67600
|
}
|
|
67152
67601
|
if (outDir) {
|
|
67153
|
-
|
|
67154
|
-
const rawFile =
|
|
67155
|
-
|
|
67602
|
+
mkdirSync18(outDir, { recursive: true });
|
|
67603
|
+
const rawFile = join51(outDir, `external-${adapterId}-${arg}.raw.json`);
|
|
67604
|
+
writeFileSync20(rawFile, rawText.endsWith("\n") ? rawText : `${rawText}
|
|
67156
67605
|
`);
|
|
67157
67606
|
if (!p.flags.json) console.log(rawFile);
|
|
67158
67607
|
}
|
|
@@ -67186,9 +67635,9 @@ async function cmdImport(p) {
|
|
|
67186
67635
|
return 0;
|
|
67187
67636
|
}
|
|
67188
67637
|
if (outDir) {
|
|
67189
|
-
|
|
67190
|
-
const file =
|
|
67191
|
-
|
|
67638
|
+
mkdirSync18(outDir, { recursive: true });
|
|
67639
|
+
const file = join51(outDir, "external-latest.json");
|
|
67640
|
+
writeFileSync20(file, `${JSON.stringify(audit2, null, 2)}
|
|
67192
67641
|
`);
|
|
67193
67642
|
console.log(file);
|
|
67194
67643
|
} else {
|
|
@@ -67293,7 +67742,7 @@ function cmdOrchestrate(p) {
|
|
|
67293
67742
|
}
|
|
67294
67743
|
const engineAbs = realpathSync6(fileURLToPath5(import.meta.url));
|
|
67295
67744
|
if (p.flags.list === true) {
|
|
67296
|
-
if (!
|
|
67745
|
+
if (!existsSync35(runFlag)) {
|
|
67297
67746
|
console.error(`ultra11y orchestrate: run dir not found: ${runFlag}.`);
|
|
67298
67747
|
return 2;
|
|
67299
67748
|
}
|
|
@@ -67320,7 +67769,7 @@ function cmdOrchestrate(p) {
|
|
|
67320
67769
|
);
|
|
67321
67770
|
} else {
|
|
67322
67771
|
console.log(
|
|
67323
|
-
lang === "fr" ? `Suivez ${
|
|
67772
|
+
lang === "fr" ? `Suivez ${join51(runFlag, "orchestration", "RUNBOOK.md")} s\xE9quentiellement (chemin \xE9co).` : `Follow ${join51(runFlag, "orchestration", "RUNBOOK.md")} sequentially (the eco path).`
|
|
67324
67773
|
);
|
|
67325
67774
|
}
|
|
67326
67775
|
if (p.flags.phase === void 0 && workflows.length === 0 && p.flags.eco !== true) {
|
|
@@ -67375,7 +67824,7 @@ async function main(argv) {
|
|
|
67375
67824
|
if (typeof v === "string" && v && !allowed.includes(v)) console.error(`ultra11y: --${flag} "${v}" is not one of ${allowed.join("|")} \u2014 using the default.`);
|
|
67376
67825
|
}
|
|
67377
67826
|
const packList = typeof p.flags.pack === "string" ? p.flags.pack.split(",").map((s) => s.trim()).filter(Boolean) : [];
|
|
67378
|
-
const configRoot = p.command === "mcp" && typeof p.flags.cwd === "string" && p.flags.cwd ?
|
|
67827
|
+
const configRoot = p.command === "mcp" && typeof p.flags.cwd === "string" && p.flags.cwd ? resolve16(p.flags.cwd) : process.cwd();
|
|
67379
67828
|
const loaded2 = loadRuntimeStandards(configRoot, packList, (m) => console.error(m), p.flags.override === true);
|
|
67380
67829
|
if (loaded2.errors.length) {
|
|
67381
67830
|
for (const e of loaded2.errors) console.error(`ultra11y: ${e}`);
|