ultra11y 5.3.4 → 5.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.opencode/plugins/ultra11y.js +1 -1
- package/package.json +1 -1
- package/scripts/ultra11y.mjs +1706 -1271
- package/skills/review-a11y/SKILL.md +1 -1
- package/skills/review-a11y/scripts/ultra11y.mjs +1706 -1271
- package/skills/ultra11y/SKILL.md +2 -2
- package/skills/ultra11y/references/ci.md +46 -4
- package/skills/ultra11y/references/judgment.md +25 -12
- package/skills/ultra11y/references/pages.md +13 -4
- package/skills/ultra11y/scripts/ultra11y.mjs +1706 -1271
- package/src/adjudicate.ts +159 -37
- package/src/audit.ts +203 -2
- package/src/cli.ts +204 -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
package/scripts/ultra11y.mjs
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
-
import { realpathSync as realpathSync6, writeFileSync as
|
|
5
|
-
import { join as
|
|
4
|
+
import { realpathSync as realpathSync6, writeFileSync as 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.1";
|
|
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 }
|
|
@@ -40894,833 +41513,233 @@ var navLandmarkMissing = {
|
|
|
40894
41513
|
var navLandmarkUnnamed = {
|
|
40895
41514
|
id: "nav-landmark-unnamed",
|
|
40896
41515
|
criteria: ["1.3.1"],
|
|
40897
|
-
severity: "mineur",
|
|
40898
|
-
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 };
|
|
41516
|
+
severity: "mineur",
|
|
41517
|
+
scope: "page",
|
|
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,95 @@ 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
|
+
var MEDIA_HINT = /(youtube|youtu\.be|vimeo|dailymotion|soundcloud|spotify|twitch|wistia|brightcove|jwplayer|videopress|podcast|player|vid[eé]o|audio|baladodiffusion)/i;
|
|
47439
|
+
var MEDIA_EXT = /\.(mp4|webm|ogv|ogg|mov|m4v|avi|mpe?g|mp3|wav|flac|m4a|aac|opus|m3u8|mpd)(?:[?#]|$)/i;
|
|
47440
|
+
function hasMedia(d) {
|
|
47441
|
+
if (has(d, "audio", "video", "track", "source")) return true;
|
|
47442
|
+
return d.elements.some((el) => {
|
|
47443
|
+
if (el.tag === "object" || el.tag === "embed") {
|
|
47444
|
+
const type = (el.attribs.type ?? "").toLowerCase();
|
|
47445
|
+
return !type || type.startsWith("audio/") || type.startsWith("video/") || type.startsWith("application/") ? true : false;
|
|
47446
|
+
}
|
|
47447
|
+
if (el.tag !== "iframe") return false;
|
|
47448
|
+
const hay = `${el.attribs.src ?? ""} ${el.attribs.title ?? ""} ${el.attribs.allow ?? ""}`;
|
|
47449
|
+
return MEDIA_HINT.test(hay) || MEDIA_EXT.test(el.attribs.src ?? "");
|
|
47450
|
+
});
|
|
47451
|
+
}
|
|
47452
|
+
function hasMotionApi(d) {
|
|
47453
|
+
return /\b(?:devicemotion|deviceorientation|DeviceMotionEvent|DeviceOrientationEvent|Accelerometer|Gyroscope|requestPermission)\b/i.test(d.source);
|
|
47454
|
+
}
|
|
47455
|
+
function hasFormControl(d) {
|
|
47456
|
+
if (has(d, "input", "select", "textarea", "form", "fieldset", "output", "datalist")) return true;
|
|
47457
|
+
return d.elements.some((e) => {
|
|
47458
|
+
const role = (e.attribs.role ?? "").toLowerCase();
|
|
47459
|
+
if (role && /^(textbox|combobox|listbox|checkbox|radio|radiogroup|slider|spinbutton|searchbox|switch|form)$/.test(role)) return true;
|
|
47460
|
+
return e.attribs.contenteditable !== void 0;
|
|
47461
|
+
});
|
|
47462
|
+
}
|
|
47463
|
+
var RESIDUAL_TRAIL = {
|
|
47464
|
+
// Rendering criteria the SNAPSHOT tier decides offline, from computed styles + stylesheets.
|
|
47465
|
+
"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.",
|
|
47466
|
+
"1.4.1": "Decided on computed colours (a link distinguished by colour alone) \u2014 record a snapshot with `scan --sample`, then re-audit.",
|
|
47467
|
+
"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.",
|
|
47468
|
+
"1.4.11": "Decided on computed colours of UI components and graphics \u2014 record a snapshot with `scan --sample`, then re-audit.",
|
|
47469
|
+
"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.",
|
|
47470
|
+
// Rendering criteria that need a LIVE browser: they are measured by acting on the page.
|
|
47471
|
+
"1.4.4": "Needs a live browser: the page is re-measured at 200% zoom \u2014 `scan <url> --runtime local --merge <audit.json>`.",
|
|
47472
|
+
"1.4.10": "Needs a live browser: the page is re-measured in a 320px viewport (reflow) \u2014 `scan <url> --merge <audit.json>`.",
|
|
47473
|
+
"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>`.",
|
|
47474
|
+
"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>`.",
|
|
47475
|
+
// axe's own `target-size` rule can FAIL this on a live page, but no tier certifies it, so a
|
|
47476
|
+
// clean scan leaves it open rather than conforming. Say both halves.
|
|
47477
|
+
"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.",
|
|
47478
|
+
"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).",
|
|
47479
|
+
// Rendering criteria no automated tier measures — say so, rather than pointing at `scan` and
|
|
47480
|
+
// letting the reader discover it changes nothing.
|
|
47481
|
+
"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.",
|
|
47482
|
+
"2.1.2": "No automated tier decides this: escaping a keyboard trap has to be attempted by hand, on each focusable region.",
|
|
47483
|
+
"2.3.1": "No automated tier decides this: flashing has to be observed over time on the rendered page.",
|
|
47484
|
+
"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."
|
|
47485
|
+
};
|
|
47486
|
+
function residualReason(automatability2, sc) {
|
|
47487
|
+
const trail = sc ? RESIDUAL_TRAIL[sc] : void 0;
|
|
47488
|
+
if (trail) return trail;
|
|
47394
47489
|
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
47490
|
}
|
|
47491
|
+
function subjectMatterReason(sc, files) {
|
|
47492
|
+
const scope = `nothing in the ${files} file(s) audited is concerned`;
|
|
47493
|
+
if (sc.startsWith("1.2."))
|
|
47494
|
+
return `No time-based media in scope: ${scope} \u2014 no <audio>, <video>, <track> or <source> element, no <object>/<embed>, and no <iframe> pointing at media.`;
|
|
47495
|
+
if (sc === "2.5.4") return `No motion actuation in scope: ${scope} \u2014 no device-motion or device-orientation API is used.`;
|
|
47496
|
+
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.`;
|
|
47497
|
+
return `No element in scope is concerned by this success criterion (${scope}).`;
|
|
47498
|
+
}
|
|
47396
47499
|
var STATIC_PREDS = allSC().filter((c2) => c2.automatability === "static").map((c2) => [c2.sc, APPLICABLE[c2.sc] ?? isFullDocument]);
|
|
47500
|
+
var SUBJECT_PREDS = allSC().filter((c2) => c2.automatability !== "static" && SUBJECT_MATTER[c2.sc] !== void 0).map((c2) => [c2.sc, SUBJECT_MATTER[c2.sc]]);
|
|
47397
47501
|
function newAccum() {
|
|
47398
47502
|
return {
|
|
47399
47503
|
byCriterion: /* @__PURE__ */ new Map(),
|
|
@@ -47406,7 +47510,8 @@ function newAccum() {
|
|
|
47406
47510
|
sfcFiles: 0,
|
|
47407
47511
|
sfcExts: /* @__PURE__ */ new Set(),
|
|
47408
47512
|
captures: [],
|
|
47409
|
-
langCounts: /* @__PURE__ */ new Map()
|
|
47513
|
+
langCounts: /* @__PURE__ */ new Map(),
|
|
47514
|
+
renderedScs: /* @__PURE__ */ new Set()
|
|
47410
47515
|
};
|
|
47411
47516
|
}
|
|
47412
47517
|
function primarySubtag(lang) {
|
|
@@ -47439,6 +47544,10 @@ function foldDoc(acc, doc, graph) {
|
|
|
47439
47544
|
for (const [id, pred] of STATIC_PREDS) {
|
|
47440
47545
|
if (!acc.applicable.get(id) && pred(doc)) acc.applicable.set(id, true);
|
|
47441
47546
|
}
|
|
47547
|
+
for (const [id, pred] of SUBJECT_PREDS) {
|
|
47548
|
+
if (!acc.applicable.get(id) && pred(doc)) acc.applicable.set(id, true);
|
|
47549
|
+
}
|
|
47550
|
+
if (doc.signals) for (const sc of renderedTestedScs(doc.signals)) acc.renderedScs.add(sc);
|
|
47442
47551
|
if (doc.opaqueComponents?.length) {
|
|
47443
47552
|
for (const lib of doc.opaqueComponents) acc.opaqueLibs.add(lib);
|
|
47444
47553
|
acc.opaqueFiles++;
|
|
@@ -47475,9 +47584,12 @@ function finalize(acc, inputs, extra = {}) {
|
|
|
47475
47584
|
}
|
|
47476
47585
|
} else if (normativeFs.length > 0) {
|
|
47477
47586
|
status = "NC";
|
|
47587
|
+
} else if (SUBJECT_MATTER[c2.sc] !== void 0 && !(acc.applicable.get(c2.sc) ?? false)) {
|
|
47588
|
+
status = "NA";
|
|
47589
|
+
justification = subjectMatterReason(c2.sc, acc.fileCount);
|
|
47478
47590
|
} else {
|
|
47479
47591
|
status = "manual";
|
|
47480
|
-
residualRisks.push({ criteriaId: c2.sc, reason: residualReason(c2.automatability), automatability: c2.automatability });
|
|
47592
|
+
residualRisks.push({ criteriaId: c2.sc, reason: residualReason(c2.automatability, c2.sc), automatability: c2.automatability });
|
|
47481
47593
|
}
|
|
47482
47594
|
criteria.push({ id: c2.sc, guideline: c2.guideline, status, findings: fs2, ...justification ? { justification } : {} });
|
|
47483
47595
|
}
|
|
@@ -47515,6 +47627,11 @@ function finalize(acc, inputs, extra = {}) {
|
|
|
47515
47627
|
}
|
|
47516
47628
|
} : {},
|
|
47517
47629
|
...acc.langCounts.size ? { langs: [...acc.langCounts.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).map(([lang]) => lang) } : {},
|
|
47630
|
+
// Coverage the RENDERED tier earned from the snapshots this run ingested. Same field the
|
|
47631
|
+
// live-browser merge stamps (`mergeDynamic`), because it is the same claim: these criteria
|
|
47632
|
+
// were measured. Omitted when no snapshot carried usable signals, so a source-only audit
|
|
47633
|
+
// is byte-for-byte unchanged and still says the rendering criteria are untested.
|
|
47634
|
+
...acc.renderedScs.size ? { scan: { testedScs: [...acc.renderedScs].sort() } } : {},
|
|
47518
47635
|
// The pages this run genuinely read. Written UNCONDITIONALLY — `[]` is the whole point,
|
|
47519
47636
|
// because "this audit read no page" is exactly the claim a source-only run needs to make,
|
|
47520
47637
|
// and an omit-when-empty field would say nothing precisely then. See scope.pagesAudited.
|
|
@@ -47529,8 +47646,8 @@ function finalize(acc, inputs, extra = {}) {
|
|
|
47529
47646
|
};
|
|
47530
47647
|
}
|
|
47531
47648
|
function hashContent(content, mode) {
|
|
47532
|
-
const
|
|
47533
|
-
return createHash4("sha1").update(
|
|
47649
|
+
const norm5 = mode === "normalized" ? content.replace(/>\s+</g, "><").trim() : content;
|
|
47650
|
+
return createHash4("sha1").update(norm5).digest("hex");
|
|
47534
47651
|
}
|
|
47535
47652
|
function runAudit(opts) {
|
|
47536
47653
|
const acc = newAccum();
|
|
@@ -53367,11 +53484,15 @@ var L4 = {
|
|
|
53367
53484
|
}
|
|
53368
53485
|
};
|
|
53369
53486
|
var NEEDS_RENDERING = [
|
|
53487
|
+
{ sc: "1.3.4", label: { fr: "verrou d\u2019orientation", en: "orientation lock" } },
|
|
53488
|
+
{ sc: "1.4.1", label: { fr: "information par la couleur", en: "use of colour" } },
|
|
53489
|
+
{ sc: "1.4.3", label: { fr: "contraste du texte", en: "text contrast" } },
|
|
53370
53490
|
{ sc: "1.4.4", label: { fr: "zoom 200 %", en: "200% zoom" } },
|
|
53371
53491
|
{ sc: "1.4.10", label: { fr: "reflow 320 px", en: "320px reflow" } },
|
|
53492
|
+
{ sc: "1.4.11", label: { fr: "contraste des composants", en: "non-text contrast" } },
|
|
53372
53493
|
{ 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
53494
|
{ sc: "1.4.13", label: { fr: "contenu au survol", en: "content on hover" } },
|
|
53495
|
+
{ sc: "2.4.7", label: { fr: "visibilit\xE9 du focus", en: "focus visibility" } },
|
|
53375
53496
|
{ sc: "4.1.3", label: { fr: "r\xE9gions live", en: "live regions" } }
|
|
53376
53497
|
];
|
|
53377
53498
|
function untestedNeedsRendering(r) {
|
|
@@ -55475,7 +55596,7 @@ function sectionBody(md, n) {
|
|
|
55475
55596
|
|
|
55476
55597
|
// src/adjudicate.ts
|
|
55477
55598
|
import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync10 } from "fs";
|
|
55478
|
-
import { join as join35 } from "path";
|
|
55599
|
+
import { join as join35, resolve as resolve10 } from "path";
|
|
55479
55600
|
|
|
55480
55601
|
// src/data/adjudication.json
|
|
55481
55602
|
var adjudication_default = {
|
|
@@ -56422,7 +56543,7 @@ function docsForAudit(audit2, cwd) {
|
|
|
56422
56543
|
const docs = [];
|
|
56423
56544
|
for (const f of files) {
|
|
56424
56545
|
try {
|
|
56425
|
-
docs.push(parseSource(readText(cwd ?
|
|
56546
|
+
docs.push(parseSource(readText(cwd ? resolve10(cwd, f) : f), f));
|
|
56426
56547
|
} catch {
|
|
56427
56548
|
}
|
|
56428
56549
|
}
|
|
@@ -56523,6 +56644,17 @@ function normativeRefResolves(ref, standard, itemCriterionId) {
|
|
|
56523
56644
|
}
|
|
56524
56645
|
function applyAdjudication(audit2, adj, opts = {}) {
|
|
56525
56646
|
const issues = [];
|
|
56647
|
+
const expected = opts.residualReasons ?? {};
|
|
56648
|
+
const itemIssues = /* @__PURE__ */ new Map();
|
|
56649
|
+
const notFolded = /* @__PURE__ */ new Set();
|
|
56650
|
+
const blame = (criteriaId, issue) => {
|
|
56651
|
+
issues.push(issue);
|
|
56652
|
+
notFolded.add(criteriaId);
|
|
56653
|
+
const list = itemIssues.get(criteriaId);
|
|
56654
|
+
if (list) list.push(issue);
|
|
56655
|
+
else itemIssues.set(criteriaId, [issue]);
|
|
56656
|
+
};
|
|
56657
|
+
const uncovered = (criteriaId) => notFolded.add(criteriaId);
|
|
56526
56658
|
canonicalizeAdjudication(adj);
|
|
56527
56659
|
const byId2 = new Map(adj.items.map((it) => [it.criteriaId, it]));
|
|
56528
56660
|
const packMode = !isCore(adj.standard);
|
|
@@ -56531,71 +56663,101 @@ function applyAdjudication(audit2, adj, opts = {}) {
|
|
|
56531
56663
|
for (const pc of derivePackResults(audit2, adj.standard)) {
|
|
56532
56664
|
if (pc.status !== "manual") continue;
|
|
56533
56665
|
open.add(pc.id);
|
|
56534
|
-
if (
|
|
56666
|
+
if (byId2.has(pc.id)) continue;
|
|
56667
|
+
if (Object.hasOwn(expected, pc.id)) uncovered(pc.id);
|
|
56668
|
+
else blame(pc.id, `criterion ${pc.id}: missing from the adjudication (coverage gap)`);
|
|
56535
56669
|
}
|
|
56536
56670
|
} else {
|
|
56537
56671
|
for (const r of audit2.residualRisks) {
|
|
56538
56672
|
open.add(r.criteriaId);
|
|
56539
|
-
if (
|
|
56673
|
+
if (byId2.has(r.criteriaId)) continue;
|
|
56674
|
+
if (Object.hasOwn(expected, r.criteriaId)) uncovered(r.criteriaId);
|
|
56675
|
+
else blame(r.criteriaId, `criterion ${r.criteriaId}: missing from the adjudication (coverage gap)`);
|
|
56540
56676
|
}
|
|
56541
56677
|
}
|
|
56542
56678
|
for (const it of adj.items) {
|
|
56543
56679
|
if (!open.has(it.criteriaId)) {
|
|
56544
|
-
|
|
56680
|
+
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
56681
|
}
|
|
56546
56682
|
}
|
|
56547
|
-
const groundInputs =
|
|
56683
|
+
const groundInputs = /* @__PURE__ */ new Map();
|
|
56684
|
+
const toGround = (criteriaId, g) => {
|
|
56685
|
+
const list = groundInputs.get(criteriaId);
|
|
56686
|
+
if (list) list.push(g);
|
|
56687
|
+
else groundInputs.set(criteriaId, [g]);
|
|
56688
|
+
};
|
|
56548
56689
|
for (const it of adj.items) {
|
|
56549
56690
|
const v = it.verdict;
|
|
56550
56691
|
if (v === null) {
|
|
56551
|
-
|
|
56692
|
+
blame(it.criteriaId, `criterion ${it.criteriaId}: unadjudicated (verdict is null)`);
|
|
56552
56693
|
} else if (v === "C" || v === "NA") {
|
|
56553
|
-
if (!it.justification || !it.justification.trim())
|
|
56694
|
+
if (!it.justification || !it.justification.trim()) blame(it.criteriaId, `criterion ${it.criteriaId}: a ${v} verdict requires a justification`);
|
|
56554
56695
|
const cites = it.citations ?? [];
|
|
56555
56696
|
if (it.evidence.length === 0) {
|
|
56556
56697
|
if (v === "C") {
|
|
56557
|
-
|
|
56698
|
+
blame(
|
|
56699
|
+
it.criteriaId,
|
|
56558
56700
|
`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
56701
|
);
|
|
56560
56702
|
}
|
|
56561
56703
|
} else if (cites.length === 0) {
|
|
56562
|
-
|
|
56704
|
+
blame(
|
|
56705
|
+
it.criteriaId,
|
|
56563
56706
|
`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
56707
|
);
|
|
56565
56708
|
} else {
|
|
56566
56709
|
const anchors = new Set(it.evidence.map((e) => `${e.file}:${e.line}`));
|
|
56567
56710
|
for (const c2 of cites) {
|
|
56568
56711
|
if (!anchors.has(`${c2.file}:${c2.line}`)) {
|
|
56569
|
-
|
|
56712
|
+
blame(it.criteriaId, `criterion ${it.criteriaId}: citation ${c2.file}:${c2.line} is not among this criterion's harvested evidence (fabricated?)`);
|
|
56570
56713
|
}
|
|
56571
|
-
|
|
56714
|
+
toGround(it.criteriaId, { file: c2.file, line: c2.line, selector: c2.selector, snippet: c2.snippet });
|
|
56572
56715
|
}
|
|
56573
56716
|
}
|
|
56574
56717
|
} else if (v === "NC") {
|
|
56575
|
-
if (!it.findings || it.findings.length === 0)
|
|
56718
|
+
if (!it.findings || it.findings.length === 0) blame(it.criteriaId, `criterion ${it.criteriaId}: an NC verdict requires at least one groundable finding`);
|
|
56576
56719
|
for (const f of it.findings ?? []) {
|
|
56577
56720
|
if (!f.normativeRef || !f.normativeRef.trim()) {
|
|
56578
|
-
|
|
56721
|
+
blame(it.criteriaId, `criterion ${it.criteriaId}: an NC finding requires a normativeRef citing the failed test of the active standard`);
|
|
56579
56722
|
} else if (!normativeRefResolves(f.normativeRef, adj.standard, isCore(adj.standard) ? void 0 : it.criteriaId)) {
|
|
56580
|
-
|
|
56723
|
+
blame(
|
|
56724
|
+
it.criteriaId,
|
|
56581
56725
|
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
56726
|
);
|
|
56583
56727
|
}
|
|
56584
|
-
|
|
56728
|
+
toGround(it.criteriaId, { file: f.file, line: f.line, selector: f.selector, snippet: f.snippet });
|
|
56585
56729
|
}
|
|
56586
56730
|
} else if (v === "manual") {
|
|
56587
56731
|
if (!it.reason || !MANUAL_REASONS.has(it.reason))
|
|
56588
|
-
|
|
56732
|
+
blame(it.criteriaId, `criterion ${it.criteriaId}: a manual verdict requires reason \u2208 {${MANUAL_REASON_VALUES.join(", ")}}`);
|
|
56589
56733
|
} else {
|
|
56590
|
-
|
|
56734
|
+
blame(it.criteriaId, `criterion ${it.criteriaId}: unknown verdict "${String(v)}" \u2014 expected one of ${VERDICTS.join(" | ")}`);
|
|
56735
|
+
}
|
|
56736
|
+
for (const rec of it.recommendations ?? []) toGround(it.criteriaId, { file: rec.file, line: rec.line, selector: rec.selector, snippet: rec.snippet });
|
|
56737
|
+
}
|
|
56738
|
+
const grounding = { grounded: 0, moved: 0, failed: 0, issues: [] };
|
|
56739
|
+
for (const [criteriaId, inputs] of groundInputs) {
|
|
56740
|
+
for (const input of inputs) {
|
|
56741
|
+
const r = groundFinding(input, { cwd: opts.cwd });
|
|
56742
|
+
if (r.ok) {
|
|
56743
|
+
grounding.grounded++;
|
|
56744
|
+
if (r.moved) grounding.moved++;
|
|
56745
|
+
} else {
|
|
56746
|
+
grounding.failed++;
|
|
56747
|
+
if (r.issue) {
|
|
56748
|
+
grounding.issues.push(r.issue);
|
|
56749
|
+
blame(criteriaId, r.issue);
|
|
56750
|
+
}
|
|
56751
|
+
}
|
|
56591
56752
|
}
|
|
56592
|
-
for (const rec of it.recommendations ?? []) groundInputs.push({ file: rec.file, line: rec.line, selector: rec.selector, snippet: rec.snippet });
|
|
56593
56753
|
}
|
|
56594
|
-
|
|
56595
|
-
|
|
56596
|
-
if (issues.length) {
|
|
56597
|
-
return { ok: false, audit: audit2, issues, applied: 0, stillManual: 0, grounding };
|
|
56754
|
+
if (opts.strict && issues.length) {
|
|
56755
|
+
return { ok: false, audit: audit2, issues, applied: 0, stillManual: 0, rejected: 0, rejectedCriteria: [], grounding };
|
|
56598
56756
|
}
|
|
56757
|
+
const rejectedCriteria = adj.items.map((it) => it.criteriaId).filter((id) => itemIssues.has(id));
|
|
56758
|
+
for (const id of itemIssues.keys()) if (!rejectedCriteria.includes(id)) rejectedCriteria.push(id);
|
|
56759
|
+
const rejectedSet = notFolded;
|
|
56760
|
+
const rejectedWhy = (id) => expected[id] ?? residualRejectedReason(itemIssues.get(id)?.[0] ?? "refused by the gate");
|
|
56599
56761
|
const next = structuredClone(audit2);
|
|
56600
56762
|
const critById = new Map(next.criteria.map((c2) => [c2.id, c2]));
|
|
56601
56763
|
const newFindings = [];
|
|
@@ -56604,6 +56766,17 @@ function applyAdjudication(audit2, adj, opts = {}) {
|
|
|
56604
56766
|
if (packMode) {
|
|
56605
56767
|
const decided = [];
|
|
56606
56768
|
for (const it of adj.items) {
|
|
56769
|
+
if (rejectedSet.has(it.criteriaId)) {
|
|
56770
|
+
if (!open.has(it.criteriaId)) continue;
|
|
56771
|
+
decided.push({
|
|
56772
|
+
id: it.criteriaId,
|
|
56773
|
+
status: "manual",
|
|
56774
|
+
reason: "undecidable",
|
|
56775
|
+
justification: rejectedWhy(it.criteriaId),
|
|
56776
|
+
findings: []
|
|
56777
|
+
});
|
|
56778
|
+
continue;
|
|
56779
|
+
}
|
|
56607
56780
|
if (it.verdict === "manual") {
|
|
56608
56781
|
stillManual++;
|
|
56609
56782
|
decided.push({
|
|
@@ -56628,14 +56801,19 @@ function applyAdjudication(audit2, adj, opts = {}) {
|
|
|
56628
56801
|
decidedBy: "agent"
|
|
56629
56802
|
});
|
|
56630
56803
|
}
|
|
56804
|
+
for (const id of notFolded) {
|
|
56805
|
+
if (byId2.has(id) || !open.has(id)) continue;
|
|
56806
|
+
decided.push({ id, status: "manual", reason: "undecidable", justification: rejectedWhy(id), findings: [] });
|
|
56807
|
+
}
|
|
56631
56808
|
next.packAdjudication = { standard: adj.standard, criteria: decided };
|
|
56632
56809
|
next.findings = [...next.findings, ...newFindings];
|
|
56633
|
-
next.adjudicated = { date: adj.auditDate, applied, stillManual };
|
|
56634
|
-
return { ok:
|
|
56810
|
+
next.adjudicated = { date: adj.auditDate, applied, stillManual, ...rejectedCriteria.length ? { rejected: rejectedCriteria.length } : {} };
|
|
56811
|
+
return { ok: issues.length === 0, audit: next, issues, applied, stillManual, rejected: rejectedCriteria.length, rejectedCriteria, grounding };
|
|
56635
56812
|
}
|
|
56636
56813
|
for (const it of adj.items) {
|
|
56637
56814
|
const c2 = critById.get(it.criteriaId);
|
|
56638
56815
|
if (!c2) continue;
|
|
56816
|
+
if (rejectedSet.has(it.criteriaId)) continue;
|
|
56639
56817
|
if (it.verdict === "manual") {
|
|
56640
56818
|
c2.status = "manual";
|
|
56641
56819
|
c2.decidedBy = "agent";
|
|
@@ -56656,7 +56834,7 @@ function applyAdjudication(audit2, adj, opts = {}) {
|
|
|
56656
56834
|
}
|
|
56657
56835
|
for (const it of adj.items) {
|
|
56658
56836
|
const c2 = critById.get(it.criteriaId);
|
|
56659
|
-
if (!c2) continue;
|
|
56837
|
+
if (!c2 || rejectedSet.has(it.criteriaId)) continue;
|
|
56660
56838
|
for (const rec of it.recommendations ?? []) {
|
|
56661
56839
|
const f = agentFinding(it.criteriaId, rec, true);
|
|
56662
56840
|
c2.findings.push(f);
|
|
@@ -56664,10 +56842,10 @@ function applyAdjudication(audit2, adj, opts = {}) {
|
|
|
56664
56842
|
}
|
|
56665
56843
|
}
|
|
56666
56844
|
next.findings = [...next.findings, ...newFindings];
|
|
56667
|
-
next.residualRisks = next.residualRisks.filter((r) => byId2.get(r.criteriaId)?.verdict === "manual");
|
|
56845
|
+
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
56846
|
recomputeTallies(next);
|
|
56669
|
-
next.adjudicated = { date: adj.auditDate, applied, stillManual };
|
|
56670
|
-
return { ok:
|
|
56847
|
+
next.adjudicated = { date: adj.auditDate, applied, stillManual, ...rejectedCriteria.length ? { rejected: rejectedCriteria.length } : {} };
|
|
56848
|
+
return { ok: issues.length === 0, audit: next, issues, applied, stillManual, rejected: rejectedCriteria.length, rejectedCriteria, grounding };
|
|
56671
56849
|
}
|
|
56672
56850
|
function agentFinding(criteriaId, f, advisory = false) {
|
|
56673
56851
|
return {
|
|
@@ -56698,6 +56876,7 @@ function recomputeTallies(a) {
|
|
|
56698
56876
|
}
|
|
56699
56877
|
var residualScanReason = () => "Rendering criterion \u2014 decide on the rendered DOM (`scan`).";
|
|
56700
56878
|
var residualUndecidableReason = () => "Left as an explicit residual risk (not decidable from the available evidence).";
|
|
56879
|
+
var residualRejectedReason = (why) => `Adjudication refused by the gate \u2014 ${why.replace(/^criterion \S+: /, "")}. Re-adjudicate this criterion.`;
|
|
56701
56880
|
var T2 = {
|
|
56702
56881
|
fr: {
|
|
56703
56882
|
title: "# Adjudication des crit\xE8res \xE0 \xE9valuer (ultra11y)",
|
|
@@ -56865,6 +57044,124 @@ function hydrateAdjudication(adj, audit2, opts = {}) {
|
|
|
56865
57044
|
}
|
|
56866
57045
|
}
|
|
56867
57046
|
|
|
57047
|
+
// src/ledger.ts
|
|
57048
|
+
import { createHash as createHash6 } from "crypto";
|
|
57049
|
+
import { existsSync as existsSync21, mkdirSync as mkdirSync10, readFileSync as readFileSync19, writeFileSync as writeFileSync11 } from "fs";
|
|
57050
|
+
import { dirname as dirname10, join as join36 } from "path";
|
|
57051
|
+
var LEDGER_DIR = ".ultra11y/verdicts";
|
|
57052
|
+
function ledgerPath(standard, root = ".") {
|
|
57053
|
+
return join36(root, LEDGER_DIR, `${standard}.json`);
|
|
57054
|
+
}
|
|
57055
|
+
var norm3 = (s) => s.replace(/\s+/g, " ").trim();
|
|
57056
|
+
var anchorKey = (e) => `${e.file}|${e.selector ?? ""}|${norm3(e.snippet ?? "")}`;
|
|
57057
|
+
function evidenceFingerprint(evidence) {
|
|
57058
|
+
const keys = evidence.map(anchorKey).sort();
|
|
57059
|
+
return `sha256:${createHash6("sha256").update(`${keys.length}
|
|
57060
|
+
${keys.join("\n")}`).digest("hex").slice(0, 32)}`;
|
|
57061
|
+
}
|
|
57062
|
+
function reanchor(stored, today2) {
|
|
57063
|
+
if (!stored?.length) return stored;
|
|
57064
|
+
const byKey2 = new Map(today2.map((e) => [anchorKey(e), e]));
|
|
57065
|
+
return stored.map((s) => {
|
|
57066
|
+
const now = byKey2.get(anchorKey(s));
|
|
57067
|
+
return now && now.line !== s.line ? { ...s, line: now.line } : s;
|
|
57068
|
+
});
|
|
57069
|
+
}
|
|
57070
|
+
function emptyLedger(standard = CORE2) {
|
|
57071
|
+
return { tool: "ultra11y", kind: "verdict-ledger", schemaVersion: SCHEMA_VERSION, standard, entries: [] };
|
|
57072
|
+
}
|
|
57073
|
+
function isLedger(v) {
|
|
57074
|
+
const o = v;
|
|
57075
|
+
return !!o && typeof o === "object" && o.tool === "ultra11y" && o.kind === "verdict-ledger" && Array.isArray(o.entries);
|
|
57076
|
+
}
|
|
57077
|
+
function readLedger(path) {
|
|
57078
|
+
if (!existsSync21(path)) return void 0;
|
|
57079
|
+
try {
|
|
57080
|
+
const parsed = JSON.parse(readFileSync19(path, "utf8"));
|
|
57081
|
+
return isLedger(parsed) ? parsed : void 0;
|
|
57082
|
+
} catch {
|
|
57083
|
+
return void 0;
|
|
57084
|
+
}
|
|
57085
|
+
}
|
|
57086
|
+
function writeLedger(path, ledger) {
|
|
57087
|
+
mkdirSync10(dirname10(path), { recursive: true });
|
|
57088
|
+
const sorted = { ...ledger, entries: [...ledger.entries].sort((a, b) => a.criteriaId.localeCompare(b.criteriaId, "en", { numeric: true })) };
|
|
57089
|
+
writeFileSync11(path, `${JSON.stringify(sorted, null, 2)}
|
|
57090
|
+
`);
|
|
57091
|
+
}
|
|
57092
|
+
function entriesFrom(adj, accepted, date) {
|
|
57093
|
+
const out2 = [];
|
|
57094
|
+
for (const it of adj.items) {
|
|
57095
|
+
if (!accepted.has(it.criteriaId) || it.verdict === null) continue;
|
|
57096
|
+
out2.push({
|
|
57097
|
+
criteriaId: it.criteriaId,
|
|
57098
|
+
verdict: it.verdict,
|
|
57099
|
+
...it.justification?.trim() ? { justification: it.justification.trim() } : {},
|
|
57100
|
+
...it.reason ? { reason: it.reason } : {},
|
|
57101
|
+
...it.citations?.length ? { citations: it.citations } : {},
|
|
57102
|
+
...it.findings?.length ? { findings: it.findings } : {},
|
|
57103
|
+
...it.recommendations?.length ? { recommendations: it.recommendations } : {},
|
|
57104
|
+
evidenceFingerprint: evidenceFingerprint(it.evidence),
|
|
57105
|
+
evidenceCount: it.evidence.length,
|
|
57106
|
+
date,
|
|
57107
|
+
decidedBy: "agent"
|
|
57108
|
+
});
|
|
57109
|
+
}
|
|
57110
|
+
return out2;
|
|
57111
|
+
}
|
|
57112
|
+
function mergeLedger(existing, standard, fresh) {
|
|
57113
|
+
const base = existing && existing.standard === standard ? existing : emptyLedger(standard);
|
|
57114
|
+
const byId2 = new Map(base.entries.map((e) => [e.criteriaId, e]));
|
|
57115
|
+
for (const e of fresh) byId2.set(e.criteriaId, e);
|
|
57116
|
+
return { ...base, schemaVersion: SCHEMA_VERSION, standard, entries: [...byId2.values()] };
|
|
57117
|
+
}
|
|
57118
|
+
function replayLedger(audit2, ledger, opts = {}) {
|
|
57119
|
+
const standard = opts.standard ?? ledger.standard ?? CORE2;
|
|
57120
|
+
const worklist = buildAdjudicationWorklist(audit2, { cwd: opts.cwd, standard });
|
|
57121
|
+
const open = new Map(worklist.map((it) => [it.criteriaId, it]));
|
|
57122
|
+
const byId2 = new Map(ledger.entries.map((e) => [e.criteriaId, e]));
|
|
57123
|
+
const items = [];
|
|
57124
|
+
const fresh = [];
|
|
57125
|
+
const stale = [];
|
|
57126
|
+
const obsolete = [];
|
|
57127
|
+
const missing = [];
|
|
57128
|
+
const residualReasons = {};
|
|
57129
|
+
for (const e of ledger.entries) if (!open.has(e.criteriaId)) obsolete.push(e.criteriaId);
|
|
57130
|
+
for (const it of worklist) {
|
|
57131
|
+
const e = byId2.get(it.criteriaId);
|
|
57132
|
+
if (!e) {
|
|
57133
|
+
missing.push(it.criteriaId);
|
|
57134
|
+
residualReasons[it.criteriaId] = "No verdict in the ledger \u2014 this criterion has never been adjudicated. Run an adjudication pass to record one.";
|
|
57135
|
+
continue;
|
|
57136
|
+
}
|
|
57137
|
+
const now = evidenceFingerprint(it.evidence);
|
|
57138
|
+
if (now !== e.evidenceFingerprint) {
|
|
57139
|
+
stale.push(it.criteriaId);
|
|
57140
|
+
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.`;
|
|
57141
|
+
continue;
|
|
57142
|
+
}
|
|
57143
|
+
fresh.push(it.criteriaId);
|
|
57144
|
+
items.push({
|
|
57145
|
+
...it,
|
|
57146
|
+
verdict: e.verdict,
|
|
57147
|
+
justification: e.justification ?? "",
|
|
57148
|
+
reason: e.reason ?? null,
|
|
57149
|
+
findings: reanchor(e.findings, it.evidence) ?? [],
|
|
57150
|
+
...e.citations ? { citations: reanchor(e.citations, it.evidence) ?? [] } : {},
|
|
57151
|
+
...e.recommendations ? { recommendations: reanchor(e.recommendations, it.evidence) ?? [] } : {},
|
|
57152
|
+
decidedBy: "agent"
|
|
57153
|
+
});
|
|
57154
|
+
}
|
|
57155
|
+
return {
|
|
57156
|
+
adj: { tool: "ultra11y", kind: "adjudication", schemaVersion: SCHEMA_VERSION, standard, auditDate: audit2.date, items },
|
|
57157
|
+
fresh,
|
|
57158
|
+
stale,
|
|
57159
|
+
obsolete,
|
|
57160
|
+
missing,
|
|
57161
|
+
residualReasons
|
|
57162
|
+
};
|
|
57163
|
+
}
|
|
57164
|
+
|
|
56868
57165
|
// src/llm.ts
|
|
56869
57166
|
var DEFAULT_MODEL = "claude-sonnet-5";
|
|
56870
57167
|
var API_VERSION = "2023-06-01";
|
|
@@ -57055,9 +57352,9 @@ function applyRawVerdicts(items, verdicts) {
|
|
|
57055
57352
|
|
|
57056
57353
|
// src/scan.ts
|
|
57057
57354
|
import { execFileSync as execFileSync6 } from "child_process";
|
|
57058
|
-
import { mkdtempSync as mkdtempSync2, writeFileSync as
|
|
57355
|
+
import { mkdtempSync as mkdtempSync2, writeFileSync as writeFileSync12, existsSync as existsSync23, statSync as statSync8, readdirSync as readdirSync5, rmSync as rmSync3, readFileSync as readFileSync20 } from "fs";
|
|
57059
57356
|
import { tmpdir as tmpdir2 } from "os";
|
|
57060
|
-
import { join as
|
|
57357
|
+
import { join as join37, resolve as resolve11 } from "path";
|
|
57061
57358
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
57062
57359
|
|
|
57063
57360
|
// src/axe-map.ts
|
|
@@ -57317,7 +57614,7 @@ async function crawlUrls(start2, opts) {
|
|
|
57317
57614
|
}
|
|
57318
57615
|
|
|
57319
57616
|
// src/sample.ts
|
|
57320
|
-
import { existsSync as
|
|
57617
|
+
import { existsSync as existsSync22 } from "fs";
|
|
57321
57618
|
function looksLikeTarget(u) {
|
|
57322
57619
|
return /^https?:\/\//i.test(u) || /^(\.\.?[/\\]|[/\\])/.test(u) || /\.x?html?$/i.test(u);
|
|
57323
57620
|
}
|
|
@@ -57353,7 +57650,7 @@ function validateSample(raw) {
|
|
|
57353
57650
|
if (p.auth !== void 0 && typeof p.auth !== "boolean") err2(`sample.pages[${i2}].auth`, "auth must be a boolean");
|
|
57354
57651
|
if (p.storageState !== void 0 && (typeof p.storageState !== "string" || p.storageState.trim() === "")) {
|
|
57355
57652
|
err2(`sample.pages[${i2}].storageState`, "storageState must be a non-empty file path string");
|
|
57356
|
-
} else if (typeof p.storageState === "string" && p.storageState.trim() !== "" && !
|
|
57653
|
+
} else if (typeof p.storageState === "string" && p.storageState.trim() !== "" && !existsSync22(p.storageState)) {
|
|
57357
57654
|
warn(
|
|
57358
57655
|
`sample.pages[${i2}].storageState`,
|
|
57359
57656
|
`storageState path not found: "${p.storageState}" (resolved from the current directory) \u2014 the scan will not be able to load this session`
|
|
@@ -57400,7 +57697,7 @@ function sampleScope(sample) {
|
|
|
57400
57697
|
...sample.transverse?.length ? { transverse: sample.transverse } : {}
|
|
57401
57698
|
};
|
|
57402
57699
|
}
|
|
57403
|
-
var
|
|
57700
|
+
var norm4 = (s) => s.normalize("NFD").replace(new RegExp("\\p{Diacritic}", "gu"), "").replace(/['’]/g, " ").toLowerCase();
|
|
57404
57701
|
var SHORT_KEYWORD = 5;
|
|
57405
57702
|
var escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
57406
57703
|
function keywordMatches(haystack, keyword) {
|
|
@@ -57408,12 +57705,12 @@ function keywordMatches(haystack, keyword) {
|
|
|
57408
57705
|
return new RegExp(`(^|[^a-z0-9])${escapeRe(keyword)}($|[^a-z0-9])`).test(haystack);
|
|
57409
57706
|
}
|
|
57410
57707
|
function lintSample(sample, methodology) {
|
|
57411
|
-
const haystacks = sample.pages.map((p) =>
|
|
57412
|
-
const transverseHay =
|
|
57708
|
+
const haystacks = sample.pages.map((p) => norm4([p.name, p.notes ?? "", p.url, p.id].join(" ")));
|
|
57709
|
+
const transverseHay = norm4((sample.transverse ?? []).join(" "));
|
|
57413
57710
|
const hasTransverse = (sample.transverse ?? []).length > 0;
|
|
57414
57711
|
const missing = [];
|
|
57415
57712
|
for (const kind of methodology.requiredKinds) {
|
|
57416
|
-
const kws = kind.keywords.map(
|
|
57713
|
+
const kws = kind.keywords.map(norm4).filter(Boolean);
|
|
57417
57714
|
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;
|
|
57418
57715
|
if (!covered) missing.push(kind);
|
|
57419
57716
|
}
|
|
@@ -57546,11 +57843,11 @@ function imageExists(tag) {
|
|
|
57546
57843
|
}
|
|
57547
57844
|
var CTX_PREFIX = "ultra11y-dyn-";
|
|
57548
57845
|
function buildImage(tag = IMAGE_TAG) {
|
|
57549
|
-
const ctx = mkdtempSync2(
|
|
57846
|
+
const ctx = mkdtempSync2(join37(tmpdir2(), CTX_PREFIX));
|
|
57550
57847
|
try {
|
|
57551
|
-
|
|
57552
|
-
|
|
57553
|
-
|
|
57848
|
+
writeFileSync12(join37(ctx, "runner.mjs"), RUNNER);
|
|
57849
|
+
writeFileSync12(join37(ctx, "package.json"), PKG);
|
|
57850
|
+
writeFileSync12(join37(ctx, "Dockerfile"), DOCKERFILE);
|
|
57554
57851
|
execFileSync6("docker", ["build", "-t", tag, ctx], { stdio: "inherit", timeout: 9e5 });
|
|
57555
57852
|
} finally {
|
|
57556
57853
|
rmSync3(ctx, { recursive: true, force: true });
|
|
@@ -57561,7 +57858,7 @@ function cleanTempContexts() {
|
|
|
57561
57858
|
const dir = tmpdir2();
|
|
57562
57859
|
for (const name2 of readdirSync5(dir)) {
|
|
57563
57860
|
if (!name2.startsWith(CTX_PREFIX)) continue;
|
|
57564
|
-
rmSync3(
|
|
57861
|
+
rmSync3(join37(dir, name2), { recursive: true, force: true });
|
|
57565
57862
|
removed++;
|
|
57566
57863
|
}
|
|
57567
57864
|
return removed;
|
|
@@ -57627,7 +57924,7 @@ var PROBE_FIELDS = [
|
|
|
57627
57924
|
function runRunner(target, isFile, tag, snapshot = true) {
|
|
57628
57925
|
const args2 = ["run", "--rm"];
|
|
57629
57926
|
if (!snapshot) args2.push("-e", "ULTRA11Y_SNAPSHOT=0");
|
|
57630
|
-
if (isFile) args2.push("-v", `${
|
|
57927
|
+
if (isFile) args2.push("-v", `${resolve11(target)}:${MOUNT}:ro`);
|
|
57631
57928
|
args2.push(tag, isFile ? MOUNT : target);
|
|
57632
57929
|
let stdout;
|
|
57633
57930
|
try {
|
|
@@ -57710,7 +58007,7 @@ function toDynamicResult(out2, target, lang = "en", engine = "axe-core@playwrigh
|
|
|
57710
58007
|
var DOCKER_TESTED_SCS = ["1.4.10"];
|
|
57711
58008
|
function runScan(opts) {
|
|
57712
58009
|
const isUrl3 = /^https?:\/\//i.test(opts.target);
|
|
57713
|
-
if (!isUrl3 && !
|
|
58010
|
+
if (!isUrl3 && !existsSync23(opts.target)) {
|
|
57714
58011
|
throw new Error(`File not found: ${opts.target}. Pass an http(s):// URL or an existing HTML file.`);
|
|
57715
58012
|
}
|
|
57716
58013
|
if (!dockerAvailable()) {
|
|
@@ -57718,7 +58015,7 @@ function runScan(opts) {
|
|
|
57718
58015
|
}
|
|
57719
58016
|
const tag = opts.tag ?? IMAGE_TAG;
|
|
57720
58017
|
if (!imageExists(tag)) buildImage(tag);
|
|
57721
|
-
const isFile = !isUrl3 &&
|
|
58018
|
+
const isFile = !isUrl3 && existsSync23(opts.target) && statSync8(opts.target).isFile();
|
|
57722
58019
|
const out2 = runRunner(opts.target, isFile, tag, Boolean(opts.snapshotRoot));
|
|
57723
58020
|
const id = opts.snapshotRoot ? writeRunnerSnapshot(opts.snapshotRoot, out2, opts.target) : void 0;
|
|
57724
58021
|
return { ...toDynamicResult(out2, opts.target), testedScs: [...DOCKER_TESTED_SCS], ...id ? { snapshots: [id] } : {} };
|
|
@@ -57805,10 +58102,10 @@ async function runCrawlScan(opts) {
|
|
|
57805
58102
|
var sevRank = { bloquant: 3, majeur: 2, mineur: 1 };
|
|
57806
58103
|
function resolveHostAnchor(file, snippet2) {
|
|
57807
58104
|
const s = snippet2?.trim();
|
|
57808
|
-
if (!s || !
|
|
58105
|
+
if (!s || !existsSync23(file)) return null;
|
|
57809
58106
|
let source;
|
|
57810
58107
|
try {
|
|
57811
|
-
source =
|
|
58108
|
+
source = readFileSync20(file, "utf8");
|
|
57812
58109
|
} catch {
|
|
57813
58110
|
return null;
|
|
57814
58111
|
}
|
|
@@ -57824,12 +58121,12 @@ function resolveHostAnchor(file, snippet2) {
|
|
|
57824
58121
|
idx = source.indexOf(openMatch[0]);
|
|
57825
58122
|
if (idx >= 0) return at(idx, openMatch[0].length);
|
|
57826
58123
|
}
|
|
57827
|
-
const
|
|
57828
|
-
const needle =
|
|
58124
|
+
const norm5 = (t2) => t2.replace(/\s+/g, " ").trim();
|
|
58125
|
+
const needle = norm5(openMatch ? openMatch[0] : s);
|
|
57829
58126
|
if (needle) {
|
|
57830
58127
|
const lines = source.split("\n");
|
|
57831
58128
|
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
57832
|
-
if (
|
|
58129
|
+
if (norm5(lines[i2]).includes(needle)) {
|
|
57833
58130
|
const start2 = starts[i2] ?? 0;
|
|
57834
58131
|
return { line: i2 + 1, col: 1, start: start2, end: start2 + lines[i2].length };
|
|
57835
58132
|
}
|
|
@@ -57943,9 +58240,9 @@ function mergeSnapshotAudit(base, snap) {
|
|
|
57943
58240
|
}
|
|
57944
58241
|
|
|
57945
58242
|
// src/scan-local.ts
|
|
57946
|
-
import { existsSync as
|
|
58243
|
+
import { existsSync as existsSync24, statSync as statSync9 } from "fs";
|
|
57947
58244
|
import { createRequire } from "module";
|
|
57948
|
-
import { resolve as
|
|
58245
|
+
import { resolve as resolve12 } from "path";
|
|
57949
58246
|
var LOCAL_ENGINE = "axe-core@playwright (local)";
|
|
57950
58247
|
var LOCAL_TESTED_SCS = ["1.4.4", "1.4.10", "1.4.12", "2.4.7", "1.4.13"];
|
|
57951
58248
|
function localTestedScs(interact) {
|
|
@@ -57955,7 +58252,7 @@ var PW_SPEC = "@playwright/test";
|
|
|
57955
58252
|
var AXE_SPEC = "@axe-core/playwright";
|
|
57956
58253
|
function localAvailable(cwd) {
|
|
57957
58254
|
try {
|
|
57958
|
-
const req = createRequire(
|
|
58255
|
+
const req = createRequire(resolve12(cwd, "package.json"));
|
|
57959
58256
|
req.resolve(PW_SPEC);
|
|
57960
58257
|
req.resolve(AXE_SPEC);
|
|
57961
58258
|
return true;
|
|
@@ -57967,7 +58264,7 @@ function resolveLocalDeps(cwd) {
|
|
|
57967
58264
|
let chromium;
|
|
57968
58265
|
let AxeBuilder;
|
|
57969
58266
|
try {
|
|
57970
|
-
const req = createRequire(
|
|
58267
|
+
const req = createRequire(resolve12(cwd, "package.json"));
|
|
57971
58268
|
const pw = req(PW_SPEC);
|
|
57972
58269
|
const axeMod = req(AXE_SPEC);
|
|
57973
58270
|
chromium = pw.chromium;
|
|
@@ -58466,7 +58763,7 @@ async function runOnPage(browser, AxeBuilder, target, isFile, opts) {
|
|
|
58466
58763
|
const page = await context.newPage();
|
|
58467
58764
|
const empty = [];
|
|
58468
58765
|
try {
|
|
58469
|
-
const url = isFile ? "file://" +
|
|
58766
|
+
const url = isFile ? "file://" + resolve12(target) : target;
|
|
58470
58767
|
const response = await page.goto(url, { waitUntil: "load", timeout: 45e3 });
|
|
58471
58768
|
await page.waitForLoadState("networkidle", { timeout: 8e3 }).catch(() => {
|
|
58472
58769
|
});
|
|
@@ -58538,7 +58835,7 @@ async function runOnPage(browser, AxeBuilder, target, isFile, opts) {
|
|
|
58538
58835
|
}
|
|
58539
58836
|
async function runScanLocal(opts) {
|
|
58540
58837
|
const isUrl3 = /^https?:\/\//i.test(opts.target);
|
|
58541
|
-
if (!isUrl3 && !
|
|
58838
|
+
if (!isUrl3 && !existsSync24(opts.target)) {
|
|
58542
58839
|
throw new Error(`File not found: ${opts.target}. Pass an http(s):// URL or an existing HTML file.`);
|
|
58543
58840
|
}
|
|
58544
58841
|
const isFile = !isUrl3 && statSync9(opts.target).isFile();
|
|
@@ -58612,7 +58909,7 @@ async function runSampleScanLocal(pages, opts) {
|
|
|
58612
58909
|
lang,
|
|
58613
58910
|
snapshot: Boolean(opts.snapshotRoot)
|
|
58614
58911
|
});
|
|
58615
|
-
const requestedUrl = isFile ? "file://" +
|
|
58912
|
+
const requestedUrl = isFile ? "file://" + resolve12(page.url) : page.url;
|
|
58616
58913
|
const landedUrl = out2.landedUrl ?? out2.url;
|
|
58617
58914
|
if (!landedOnRequestedPage(requestedUrl, landedUrl)) {
|
|
58618
58915
|
redirected.push({ id: page.id, name: page.name, requested: page.url, landed: landedUrl, reason: "redirect" });
|
|
@@ -58841,7 +59138,7 @@ function diffSides(left, right) {
|
|
|
58841
59138
|
}
|
|
58842
59139
|
|
|
58843
59140
|
// src/fix.ts
|
|
58844
|
-
import { writeFileSync as
|
|
59141
|
+
import { writeFileSync as writeFileSync13 } from "fs";
|
|
58845
59142
|
|
|
58846
59143
|
// src/fix/edits.ts
|
|
58847
59144
|
function openTag(source, start2) {
|
|
@@ -59055,7 +59352,7 @@ function fixOne(file, source, opts, canWrite = true) {
|
|
|
59055
59352
|
regression = after.length > findings.length || after.some((f) => !beforeKinds.has(f.ruleId));
|
|
59056
59353
|
if (opts.write && !regression && file !== "<stdin>") {
|
|
59057
59354
|
if (canWrite) {
|
|
59058
|
-
|
|
59355
|
+
writeFileSync13(file, output);
|
|
59059
59356
|
written = true;
|
|
59060
59357
|
} else {
|
|
59061
59358
|
skippedPartialStage = true;
|
|
@@ -59137,9 +59434,9 @@ function fixSummary(r, lang = "fr", write = false) {
|
|
|
59137
59434
|
}
|
|
59138
59435
|
|
|
59139
59436
|
// src/init.ts
|
|
59140
|
-
import { writeFileSync as
|
|
59437
|
+
import { writeFileSync as writeFileSync14, mkdirSync as mkdirSync11, chmodSync, realpathSync as realpathSync2 } from "fs";
|
|
59141
59438
|
import { execFileSync as execFileSync7 } from "child_process";
|
|
59142
|
-
import { join as
|
|
59439
|
+
import { join as join38, relative as relative4, sep as sep4 } from "path";
|
|
59143
59440
|
var EN_SEV = { bloquant: "blocking", majeur: "major", mineur: "minor" };
|
|
59144
59441
|
function repoRoot() {
|
|
59145
59442
|
try {
|
|
@@ -59257,24 +59554,24 @@ jobs:
|
|
|
59257
59554
|
`;
|
|
59258
59555
|
}
|
|
59259
59556
|
function writeHook(root, enginePath, failOn, mode = "staged") {
|
|
59260
|
-
const dir =
|
|
59261
|
-
|
|
59262
|
-
const path =
|
|
59263
|
-
|
|
59557
|
+
const dir = join38(root, ".git", "hooks");
|
|
59558
|
+
mkdirSync11(dir, { recursive: true });
|
|
59559
|
+
const path = join38(dir, "pre-commit");
|
|
59560
|
+
writeFileSync14(path, mode === "baseline" ? hookScript(enginePath, failOn) : stagedHookScript(enginePath, failOn));
|
|
59264
59561
|
chmodSync(path, 493);
|
|
59265
59562
|
return path;
|
|
59266
59563
|
}
|
|
59267
59564
|
function writeCi(root, enginePath, failOn) {
|
|
59268
|
-
const dir =
|
|
59269
|
-
|
|
59270
|
-
const path =
|
|
59271
|
-
|
|
59565
|
+
const dir = join38(root, ".github", "workflows");
|
|
59566
|
+
mkdirSync11(dir, { recursive: true });
|
|
59567
|
+
const path = join38(dir, "a11y.yml");
|
|
59568
|
+
writeFileSync14(path, ciWorkflow(enginePath, failOn));
|
|
59272
59569
|
return path;
|
|
59273
59570
|
}
|
|
59274
59571
|
|
|
59275
59572
|
// src/install/json-edit.ts
|
|
59276
|
-
import { copyFileSync as copyFileSync2, existsSync as
|
|
59277
|
-
import { dirname as
|
|
59573
|
+
import { copyFileSync as copyFileSync2, existsSync as existsSync25, mkdirSync as mkdirSync12, readFileSync as readFileSync21, renameSync as renameSync2, rmSync as rmSync4, writeFileSync as writeFileSync15 } from "fs";
|
|
59574
|
+
import { dirname as dirname11 } from "path";
|
|
59278
59575
|
var SettingsParseError = class extends Error {
|
|
59279
59576
|
constructor(path, cause) {
|
|
59280
59577
|
super(`${path} is not valid JSON (${cause}) \u2014 fix or move it, then run install again. It has NOT been modified.`);
|
|
@@ -59287,16 +59584,16 @@ function stamp() {
|
|
|
59287
59584
|
return (/* @__PURE__ */ new Date()).toISOString().replaceAll(/[:.]/g, "-");
|
|
59288
59585
|
}
|
|
59289
59586
|
function writeTextWithBackup(path, content, marker = "ultra11y") {
|
|
59290
|
-
if (
|
|
59291
|
-
|
|
59587
|
+
if (existsSync25(path) && readFileSync21(path, "utf8") === content) return { path, changed: false };
|
|
59588
|
+
mkdirSync12(dirname11(path), { recursive: true });
|
|
59292
59589
|
let backup;
|
|
59293
|
-
if (
|
|
59590
|
+
if (existsSync25(path)) {
|
|
59294
59591
|
backup = `${path}.${marker}-backup-${stamp()}`;
|
|
59295
59592
|
copyFileSync2(path, backup);
|
|
59296
59593
|
}
|
|
59297
59594
|
const tmp = `${path}.${process.pid}.tmp`;
|
|
59298
59595
|
try {
|
|
59299
|
-
|
|
59596
|
+
writeFileSync15(tmp, content, { mode: 420 });
|
|
59300
59597
|
renameSync2(tmp, path);
|
|
59301
59598
|
} catch (e) {
|
|
59302
59599
|
rmSync4(tmp, { force: true });
|
|
@@ -59306,8 +59603,8 @@ function writeTextWithBackup(path, content, marker = "ultra11y") {
|
|
|
59306
59603
|
}
|
|
59307
59604
|
function editJsonFile(path, mutate) {
|
|
59308
59605
|
let root = {};
|
|
59309
|
-
if (
|
|
59310
|
-
const raw =
|
|
59606
|
+
if (existsSync25(path)) {
|
|
59607
|
+
const raw = readFileSync21(path, "utf8");
|
|
59311
59608
|
if (raw.trim()) {
|
|
59312
59609
|
let parsed;
|
|
59313
59610
|
try {
|
|
@@ -59323,13 +59620,13 @@ function editJsonFile(path, mutate) {
|
|
|
59323
59620
|
}
|
|
59324
59621
|
const before = JSON.stringify(root);
|
|
59325
59622
|
mutate(root);
|
|
59326
|
-
if (JSON.stringify(root) === before &&
|
|
59623
|
+
if (JSON.stringify(root) === before && existsSync25(path)) return { path, changed: false };
|
|
59327
59624
|
return writeTextWithBackup(path, `${JSON.stringify(root, null, 2)}
|
|
59328
59625
|
`);
|
|
59329
59626
|
}
|
|
59330
59627
|
function readJsonSafe(path) {
|
|
59331
59628
|
try {
|
|
59332
|
-
const parsed = JSON.parse(
|
|
59629
|
+
const parsed = JSON.parse(readFileSync21(path, "utf8"));
|
|
59333
59630
|
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
59334
59631
|
} catch {
|
|
59335
59632
|
return null;
|
|
@@ -59370,7 +59667,7 @@ function claudeCodeWired(settingsPath) {
|
|
|
59370
59667
|
}
|
|
59371
59668
|
|
|
59372
59669
|
// src/install/text-edit.ts
|
|
59373
|
-
import { existsSync as
|
|
59670
|
+
import { existsSync as existsSync26, readFileSync as readFileSync23, rmSync as rmSync5 } from "fs";
|
|
59374
59671
|
var BLOCK_BEGIN = "<!-- BEGIN ultra11y (managed by `ultra11y install --agents-md`; edit outside this block) -->";
|
|
59375
59672
|
var BLOCK_END = "<!-- END ultra11y -->";
|
|
59376
59673
|
var blockRe = () => new RegExp(`${escapeRe2(BLOCK_BEGIN)}[\\s\\S]*?${escapeRe2(BLOCK_END)}\\n?`);
|
|
@@ -59382,15 +59679,15 @@ function upsertManagedBlock(path, body3) {
|
|
|
59382
59679
|
${body3.trim()}
|
|
59383
59680
|
${BLOCK_END}
|
|
59384
59681
|
`;
|
|
59385
|
-
if (!
|
|
59386
|
-
const current =
|
|
59682
|
+
if (!existsSync26(path)) return writeTextWithBackup(path, block);
|
|
59683
|
+
const current = readFileSync23(path, "utf8");
|
|
59387
59684
|
if (blockRe().test(current)) return writeTextWithBackup(path, current.replace(blockRe(), block));
|
|
59388
59685
|
const sep8 = current.endsWith("\n\n") ? "" : current.endsWith("\n") ? "\n" : "\n\n";
|
|
59389
59686
|
return writeTextWithBackup(path, `${current}${sep8}${block}`);
|
|
59390
59687
|
}
|
|
59391
59688
|
function removeManagedBlock(path) {
|
|
59392
|
-
if (!
|
|
59393
|
-
const current =
|
|
59689
|
+
if (!existsSync26(path)) return { path, changed: false };
|
|
59690
|
+
const current = readFileSync23(path, "utf8");
|
|
59394
59691
|
if (!blockRe().test(current)) return { path, changed: false };
|
|
59395
59692
|
const rest = current.replace(blockRe(), "");
|
|
59396
59693
|
if (rest.trim() === "") {
|
|
@@ -59400,13 +59697,13 @@ function removeManagedBlock(path) {
|
|
|
59400
59697
|
return writeTextWithBackup(path, rest.replace(/\n+$/, "\n"));
|
|
59401
59698
|
}
|
|
59402
59699
|
function hasManagedBlock(path) {
|
|
59403
|
-
return
|
|
59700
|
+
return existsSync26(path) && blockRe().test(readFileSync23(path, "utf8"));
|
|
59404
59701
|
}
|
|
59405
59702
|
|
|
59406
59703
|
// src/install/agents-md.ts
|
|
59407
|
-
import { join as
|
|
59704
|
+
import { join as join39 } from "path";
|
|
59408
59705
|
function agentsMdPath(root) {
|
|
59409
|
-
return
|
|
59706
|
+
return join39(root, "AGENTS.md");
|
|
59410
59707
|
}
|
|
59411
59708
|
function agentsMdBlock(root) {
|
|
59412
59709
|
const e = engineInvocation(root);
|
|
@@ -59461,25 +59758,25 @@ function agentsMdWired(root) {
|
|
|
59461
59758
|
}
|
|
59462
59759
|
|
|
59463
59760
|
// src/install/codex.ts
|
|
59464
|
-
import { existsSync as
|
|
59465
|
-
import { join as
|
|
59761
|
+
import { existsSync as existsSync28, readFileSync as readFileSync25 } from "fs";
|
|
59762
|
+
import { join as join41 } from "path";
|
|
59466
59763
|
|
|
59467
59764
|
// src/install/paths.ts
|
|
59468
|
-
import { copyFileSync as copyFileSync3, cpSync, existsSync as
|
|
59765
|
+
import { copyFileSync as copyFileSync3, cpSync, existsSync as existsSync27, mkdirSync as mkdirSync13, readFileSync as readFileSync24, realpathSync as realpathSync3, statSync as statSync10 } from "fs";
|
|
59469
59766
|
import { homedir as homedir2 } from "os";
|
|
59470
|
-
import { dirname as
|
|
59767
|
+
import { dirname as dirname12, join as join40 } from "path";
|
|
59471
59768
|
function codexHome() {
|
|
59472
|
-
return process.env.CODEX_HOME ||
|
|
59769
|
+
return process.env.CODEX_HOME || join40(homedir2(), ".codex");
|
|
59473
59770
|
}
|
|
59474
59771
|
function opencodeConfigDir() {
|
|
59475
59772
|
const xdg = process.env.XDG_CONFIG_HOME;
|
|
59476
|
-
return
|
|
59773
|
+
return join40(xdg && xdg !== "" ? xdg : join40(homedir2(), ".config"), "opencode");
|
|
59477
59774
|
}
|
|
59478
59775
|
function claudeSettingsPath(project, cwd = process.cwd()) {
|
|
59479
|
-
return project ?
|
|
59776
|
+
return project ? join40(cwd, ".claude", "settings.json") : join40(homedir2(), ".claude", "settings.json");
|
|
59480
59777
|
}
|
|
59481
59778
|
function pinnedEnginePath() {
|
|
59482
|
-
return
|
|
59779
|
+
return join40(homedir2(), ".ultra11y", "bin", "ultra11y.mjs");
|
|
59483
59780
|
}
|
|
59484
59781
|
function installedCliCommand() {
|
|
59485
59782
|
const override = process.env.ULTRA11Y_BIN;
|
|
@@ -59494,7 +59791,7 @@ function installedCliCommand() {
|
|
|
59494
59791
|
if (source && !ephemeral) return `node ${JSON.stringify(source)}`;
|
|
59495
59792
|
const pin = pinnedEnginePath();
|
|
59496
59793
|
if (source) {
|
|
59497
|
-
|
|
59794
|
+
mkdirSync13(join40(pin, ".."), { recursive: true });
|
|
59498
59795
|
copyFileSync3(source, pin);
|
|
59499
59796
|
}
|
|
59500
59797
|
return `node ${JSON.stringify(pin)}`;
|
|
@@ -59502,37 +59799,37 @@ function installedCliCommand() {
|
|
|
59502
59799
|
function packageRoot() {
|
|
59503
59800
|
let dir;
|
|
59504
59801
|
try {
|
|
59505
|
-
dir =
|
|
59802
|
+
dir = dirname12(realpathSync3(process.argv[1] ?? ""));
|
|
59506
59803
|
} catch {
|
|
59507
59804
|
return null;
|
|
59508
59805
|
}
|
|
59509
59806
|
for (let i2 = 0; i2 < 6 && dir && dir !== "/"; i2++) {
|
|
59510
|
-
if (
|
|
59511
|
-
dir =
|
|
59807
|
+
if (existsSync27(join40(dir, "skills", "review-a11y", "SKILL.md"))) return dir;
|
|
59808
|
+
dir = dirname12(dir);
|
|
59512
59809
|
}
|
|
59513
59810
|
return null;
|
|
59514
59811
|
}
|
|
59515
59812
|
function bundledSkillsDir() {
|
|
59516
59813
|
const root = packageRoot();
|
|
59517
|
-
const p = root ?
|
|
59518
|
-
return p &&
|
|
59814
|
+
const p = root ? join40(root, "skills") : null;
|
|
59815
|
+
return p && existsSync27(p) ? p : null;
|
|
59519
59816
|
}
|
|
59520
59817
|
function bundledOpencodePlugin() {
|
|
59521
59818
|
const root = packageRoot();
|
|
59522
|
-
const p = root ?
|
|
59523
|
-
return p &&
|
|
59819
|
+
const p = root ? join40(root, ".opencode", "plugins", "ultra11y.js") : null;
|
|
59820
|
+
return p && existsSync27(p) ? p : null;
|
|
59524
59821
|
}
|
|
59525
59822
|
function copySkillsInto(source, dest) {
|
|
59526
59823
|
const out2 = [];
|
|
59527
59824
|
for (const name2 of ["ultra11y", "review-a11y"]) {
|
|
59528
|
-
const from =
|
|
59529
|
-
if (!
|
|
59530
|
-
const to =
|
|
59531
|
-
const same = ["SKILL.md",
|
|
59532
|
-
const a =
|
|
59533
|
-
const b =
|
|
59825
|
+
const from = join40(source, name2);
|
|
59826
|
+
if (!existsSync27(from)) continue;
|
|
59827
|
+
const to = join40(dest, name2);
|
|
59828
|
+
const same = ["SKILL.md", join40("scripts", "ultra11y.mjs")].every((f) => {
|
|
59829
|
+
const a = join40(from, f);
|
|
59830
|
+
const b = join40(to, f);
|
|
59534
59831
|
try {
|
|
59535
|
-
return
|
|
59832
|
+
return existsSync27(b) && statSync10(a).size === statSync10(b).size && readFileSync24(a).equals(readFileSync24(b));
|
|
59536
59833
|
} catch {
|
|
59537
59834
|
return false;
|
|
59538
59835
|
}
|
|
@@ -59582,7 +59879,7 @@ function featuresBody(content) {
|
|
|
59582
59879
|
function installCodex({ codexDir, command, skillsSource }) {
|
|
59583
59880
|
const reports = [];
|
|
59584
59881
|
reports.push(
|
|
59585
|
-
editJsonFile(
|
|
59882
|
+
editJsonFile(join41(codexDir, "hooks.json"), (root) => {
|
|
59586
59883
|
const hooks = root.hooks ??= {};
|
|
59587
59884
|
hooks.PreToolUse = [
|
|
59588
59885
|
...withoutOurs2(hooks.PreToolUse),
|
|
@@ -59590,11 +59887,11 @@ function installCodex({ codexDir, command, skillsSource }) {
|
|
|
59590
59887
|
];
|
|
59591
59888
|
})
|
|
59592
59889
|
);
|
|
59593
|
-
const configPath =
|
|
59594
|
-
const current =
|
|
59890
|
+
const configPath = join41(codexDir, "config.toml");
|
|
59891
|
+
const current = existsSync28(configPath) ? readFileSync25(configPath, "utf8") : "";
|
|
59595
59892
|
const edit = enableHooksFeature(current);
|
|
59596
59893
|
if (edit.changed) reports.push(writeTextWithBackup(configPath, edit.content));
|
|
59597
|
-
if (skillsSource) reports.push(...copySkillsInto(skillsSource,
|
|
59894
|
+
if (skillsSource) reports.push(...copySkillsInto(skillsSource, join41(codexDir, "skills")));
|
|
59598
59895
|
return {
|
|
59599
59896
|
reports,
|
|
59600
59897
|
guidance: [
|
|
@@ -59609,8 +59906,8 @@ function installCodex({ codexDir, command, skillsSource }) {
|
|
|
59609
59906
|
}
|
|
59610
59907
|
function uninstallCodex({ codexDir }) {
|
|
59611
59908
|
const reports = [];
|
|
59612
|
-
const hooksPath =
|
|
59613
|
-
if (
|
|
59909
|
+
const hooksPath = join41(codexDir, "hooks.json");
|
|
59910
|
+
if (existsSync28(hooksPath)) {
|
|
59614
59911
|
reports.push(
|
|
59615
59912
|
editJsonFile(hooksPath, (root) => {
|
|
59616
59913
|
const hooks = root.hooks;
|
|
@@ -59625,33 +59922,33 @@ function uninstallCodex({ codexDir }) {
|
|
|
59625
59922
|
return { reports, guidance: ["codex: `[features] hooks = true` was left enabled \u2014 other hooks may depend on it."] };
|
|
59626
59923
|
}
|
|
59627
59924
|
function codexWired(codexDir) {
|
|
59628
|
-
const root = readJsonSafe(
|
|
59925
|
+
const root = readJsonSafe(join41(codexDir, "hooks.json"));
|
|
59629
59926
|
const groups = root?.hooks?.PreToolUse;
|
|
59630
59927
|
if (!Array.isArray(groups)) return 0;
|
|
59631
59928
|
return groups.reduce((n, g) => n + (g.hooks ?? []).filter((h) => String(h.command ?? "").includes(CODEX_MARKER)).length, 0);
|
|
59632
59929
|
}
|
|
59633
59930
|
function codexHooksEnabled(codexDir) {
|
|
59634
|
-
const p =
|
|
59635
|
-
return
|
|
59931
|
+
const p = join41(codexDir, "config.toml");
|
|
59932
|
+
return existsSync28(p) && HOOKS_ON.test(featuresBody(readFileSync25(p, "utf8")));
|
|
59636
59933
|
}
|
|
59637
59934
|
|
|
59638
59935
|
// src/install/opencode.ts
|
|
59639
|
-
import { existsSync as
|
|
59640
|
-
import { join as
|
|
59936
|
+
import { existsSync as existsSync29, readFileSync as readFileSync26, rmSync as rmSync6 } from "fs";
|
|
59937
|
+
import { join as join42 } from "path";
|
|
59641
59938
|
var OPENCODE_MARKER = "ULTRA11Y_OPENCODE_PLUGIN";
|
|
59642
59939
|
function pluginTarget(configDir) {
|
|
59643
|
-
return
|
|
59940
|
+
return join42(configDir, "plugin", "ultra11y.js");
|
|
59644
59941
|
}
|
|
59645
59942
|
function installOpencode({ configDir, pluginSource, skillsSource, enginePath }) {
|
|
59646
59943
|
const target = pluginTarget(configDir);
|
|
59647
|
-
if (
|
|
59944
|
+
if (existsSync29(target) && !readFileSync26(target, "utf8").includes(OPENCODE_MARKER)) {
|
|
59648
59945
|
throw new Error(`refusing to overwrite ${target}: it is not a file ultra11y wrote. Remove it manually if you want the plugin here.`);
|
|
59649
59946
|
}
|
|
59650
|
-
const reports = [writeTextWithBackup(target,
|
|
59651
|
-
if (enginePath &&
|
|
59652
|
-
reports.push(writeTextWithBackup(
|
|
59947
|
+
const reports = [writeTextWithBackup(target, readFileSync26(pluginSource, "utf8"))];
|
|
59948
|
+
if (enginePath && existsSync29(enginePath)) {
|
|
59949
|
+
reports.push(writeTextWithBackup(join42(configDir, "plugin", "ultra11y.mjs"), readFileSync26(enginePath, "utf8")));
|
|
59653
59950
|
}
|
|
59654
|
-
if (skillsSource) reports.push(...copySkillsInto(skillsSource,
|
|
59951
|
+
if (skillsSource) reports.push(...copySkillsInto(skillsSource, join42(configDir, "skills")));
|
|
59655
59952
|
return {
|
|
59656
59953
|
reports,
|
|
59657
59954
|
guidance: [
|
|
@@ -59664,13 +59961,13 @@ function installOpencode({ configDir, pluginSource, skillsSource, enginePath })
|
|
|
59664
59961
|
function uninstallOpencode({ configDir }) {
|
|
59665
59962
|
const reports = [];
|
|
59666
59963
|
const target = pluginTarget(configDir);
|
|
59667
|
-
if (
|
|
59964
|
+
if (existsSync29(target) && readFileSync26(target, "utf8").includes(OPENCODE_MARKER)) {
|
|
59668
59965
|
rmSync6(target, { force: true });
|
|
59669
|
-
rmSync6(
|
|
59966
|
+
rmSync6(join42(configDir, "plugin", "ultra11y.mjs"), { force: true });
|
|
59670
59967
|
reports.push({ path: target, changed: true });
|
|
59671
59968
|
}
|
|
59672
|
-
const configPath =
|
|
59673
|
-
if (
|
|
59969
|
+
const configPath = join42(configDir, "opencode.json");
|
|
59970
|
+
if (existsSync29(configPath)) {
|
|
59674
59971
|
reports.push(
|
|
59675
59972
|
editJsonFile(configPath, (root) => {
|
|
59676
59973
|
if (!Array.isArray(root.plugin)) return;
|
|
@@ -59684,9 +59981,9 @@ function uninstallOpencode({ configDir }) {
|
|
|
59684
59981
|
}
|
|
59685
59982
|
function opencodeWired(configDir) {
|
|
59686
59983
|
const target = pluginTarget(configDir);
|
|
59687
|
-
if (
|
|
59984
|
+
if (existsSync29(target) && readFileSync26(target, "utf8").includes(OPENCODE_MARKER)) return true;
|
|
59688
59985
|
try {
|
|
59689
|
-
const cfg = JSON.parse(
|
|
59986
|
+
const cfg = JSON.parse(readFileSync26(join42(configDir, "opencode.json"), "utf8"));
|
|
59690
59987
|
return Array.isArray(cfg.plugin) && cfg.plugin.some((p) => /^ultra11y(@|$)/.test(String(p)));
|
|
59691
59988
|
} catch {
|
|
59692
59989
|
return false;
|
|
@@ -59694,8 +59991,8 @@ function opencodeWired(configDir) {
|
|
|
59694
59991
|
}
|
|
59695
59992
|
|
|
59696
59993
|
// src/install/index.ts
|
|
59697
|
-
import { existsSync as
|
|
59698
|
-
import { join as
|
|
59994
|
+
import { existsSync as existsSync30 } from "fs";
|
|
59995
|
+
import { join as join43 } from "path";
|
|
59699
59996
|
var ALL_TARGETS = ["claude-code", "codex", "opencode"];
|
|
59700
59997
|
function parseTargets(flags2) {
|
|
59701
59998
|
const picked = /* @__PURE__ */ new Set();
|
|
@@ -59734,7 +60031,7 @@ function installForTargets(opts) {
|
|
|
59734
60031
|
const out3 = installCodex({ codexDir: codexHome(), command: cmd(), skillsSource: opts.skills === false ? null : bundledSkillsDir() });
|
|
59735
60032
|
r.reports.push(...out3.reports);
|
|
59736
60033
|
r.guidance.push(...out3.guidance);
|
|
59737
|
-
} else r.reports.push({ path:
|
|
60034
|
+
} else r.reports.push({ path: join43(codexHome(), "hooks.json"), changed: false });
|
|
59738
60035
|
break;
|
|
59739
60036
|
}
|
|
59740
60037
|
case "opencode": {
|
|
@@ -59743,18 +60040,18 @@ function installForTargets(opts) {
|
|
|
59743
60040
|
r.error = "opencode: this engine was not run from a package carrying .opencode/plugins/ultra11y.js \u2014 install ultra11y from npm and retry.";
|
|
59744
60041
|
break;
|
|
59745
60042
|
}
|
|
59746
|
-
const configDir = opts.project === true ?
|
|
60043
|
+
const configDir = opts.project === true ? join43(cwd, ".opencode") : opencodeConfigDir();
|
|
59747
60044
|
if (!opts.dryRun) {
|
|
59748
|
-
const engine =
|
|
60045
|
+
const engine = existsSync30(pinnedEnginePath()) ? pinnedEnginePath() : join43(packageRoot() ?? "", "scripts", "ultra11y.mjs");
|
|
59749
60046
|
const out3 = installOpencode({
|
|
59750
60047
|
configDir,
|
|
59751
60048
|
pluginSource: source,
|
|
59752
60049
|
skillsSource: opts.skills === false ? null : bundledSkillsDir(),
|
|
59753
|
-
enginePath:
|
|
60050
|
+
enginePath: existsSync30(engine) ? engine : null
|
|
59754
60051
|
});
|
|
59755
60052
|
r.reports.push(...out3.reports);
|
|
59756
60053
|
r.guidance.push(...out3.guidance);
|
|
59757
|
-
} else r.reports.push({ path:
|
|
60054
|
+
} else r.reports.push({ path: join43(configDir, "plugin", "ultra11y.js"), changed: false });
|
|
59758
60055
|
break;
|
|
59759
60056
|
}
|
|
59760
60057
|
case "agents-md": {
|
|
@@ -59792,7 +60089,7 @@ function uninstallForTargets(opts) {
|
|
|
59792
60089
|
break;
|
|
59793
60090
|
}
|
|
59794
60091
|
case "opencode": {
|
|
59795
|
-
const out3 = uninstallOpencode({ configDir: opts.project === true ?
|
|
60092
|
+
const out3 = uninstallOpencode({ configDir: opts.project === true ? join43(cwd, ".opencode") : opencodeConfigDir() });
|
|
59796
60093
|
r.reports.push(...out3.reports);
|
|
59797
60094
|
r.guidance.push(...out3.guidance);
|
|
59798
60095
|
break;
|
|
@@ -59813,18 +60110,18 @@ function statusReport(opts = {}) {
|
|
|
59813
60110
|
const claudePath = claudeSettingsPath(opts.project === true, cwd);
|
|
59814
60111
|
const root = projectRoot(cwd);
|
|
59815
60112
|
const codexDir = codexHome();
|
|
59816
|
-
const ocDir = opts.project === true ?
|
|
60113
|
+
const ocDir = opts.project === true ? join43(cwd, ".opencode") : opencodeConfigDir();
|
|
59817
60114
|
const codexOn = codexWired(codexDir) > 0;
|
|
59818
60115
|
return [
|
|
59819
60116
|
{ target: "claude-code", wired: claudeCodeWired(claudePath) > 0, path: claudePath, note: "the plugin route wires this separately and does not show here" },
|
|
59820
60117
|
{
|
|
59821
60118
|
target: "codex",
|
|
59822
60119
|
wired: codexOn,
|
|
59823
|
-
path:
|
|
60120
|
+
path: join43(codexDir, "hooks.json"),
|
|
59824
60121
|
// A wired hook behind a disabled feature flag never fires and looks fine. Say so.
|
|
59825
60122
|
note: codexOn && !codexHooksEnabled(codexDir) ? "WIRED BUT INERT: `[features] hooks = true` is not set in config.toml" : void 0
|
|
59826
60123
|
},
|
|
59827
|
-
{ target: "opencode", wired: opencodeWired(ocDir), path:
|
|
60124
|
+
{ target: "opencode", wired: opencodeWired(ocDir), path: join43(ocDir, "plugin", "ultra11y.js") },
|
|
59828
60125
|
{ target: "agents-md", wired: agentsMdWired(root), path: agentsMdPath(root) }
|
|
59829
60126
|
];
|
|
59830
60127
|
}
|
|
@@ -60359,9 +60656,9 @@ function pagesComment(result, opts = {}) {
|
|
|
60359
60656
|
}
|
|
60360
60657
|
|
|
60361
60658
|
// src/evidence.ts
|
|
60362
|
-
import { createHash as
|
|
60363
|
-
import { existsSync as
|
|
60364
|
-
import { join as
|
|
60659
|
+
import { createHash as createHash7 } from "crypto";
|
|
60660
|
+
import { existsSync as existsSync31, mkdirSync as mkdirSync14, readFileSync as readFileSync27, writeFileSync as writeFileSync16 } from "fs";
|
|
60661
|
+
import { join as join44 } from "path";
|
|
60365
60662
|
var DEFAULT_GEOMETRY = { pad: 24, minWidth: 320, minHeight: 120, maxWidth: 960, maxHeight: 540 };
|
|
60366
60663
|
var DEFAULT_CAPS = { perRule: 6, perPage: 12, total: 200 };
|
|
60367
60664
|
var HALO = { r: 255, g: 255, b: 255, a: 0.92 };
|
|
@@ -60378,7 +60675,7 @@ function pageIdOfSnapshot(file) {
|
|
|
60378
60675
|
}
|
|
60379
60676
|
function readJson3(file) {
|
|
60380
60677
|
try {
|
|
60381
|
-
return JSON.parse(
|
|
60678
|
+
return JSON.parse(readFileSync27(file, "utf8"));
|
|
60382
60679
|
} catch {
|
|
60383
60680
|
return void 0;
|
|
60384
60681
|
}
|
|
@@ -60394,21 +60691,21 @@ function resolveScale(img, viewport, boxes) {
|
|
|
60394
60691
|
}
|
|
60395
60692
|
function loadPageEvidence(root, pageId) {
|
|
60396
60693
|
const dir = snapshotDir(root, pageId);
|
|
60397
|
-
const domFile =
|
|
60398
|
-
const shotFile =
|
|
60399
|
-
if (!
|
|
60400
|
-
if (!
|
|
60694
|
+
const domFile = join44(dir, "dom.html");
|
|
60695
|
+
const shotFile = join44(dir, "screen.png");
|
|
60696
|
+
if (!existsSync31(domFile)) return { skip: "no-snapshot" };
|
|
60697
|
+
if (!existsSync31(shotFile)) return { skip: "no-screenshot" };
|
|
60401
60698
|
let dom;
|
|
60402
60699
|
let shot;
|
|
60403
60700
|
try {
|
|
60404
|
-
dom =
|
|
60405
|
-
shot =
|
|
60701
|
+
dom = readFileSync27(domFile, "utf8");
|
|
60702
|
+
shot = readFileSync27(shotFile);
|
|
60406
60703
|
} catch {
|
|
60407
60704
|
return { skip: "no-snapshot" };
|
|
60408
60705
|
}
|
|
60409
60706
|
const img = decodePng(shot);
|
|
60410
60707
|
if (!img) return { skip: "unreadable-image" };
|
|
60411
|
-
const digest = readJson3(
|
|
60708
|
+
const digest = readJson3(join44(dir, "boxes.json"));
|
|
60412
60709
|
if (!digest) return { skip: "no-boxes" };
|
|
60413
60710
|
const doc = parseHtml(dom, domFile);
|
|
60414
60711
|
const boxes = alignedBoxes(dom, digest, doc);
|
|
@@ -60424,7 +60721,7 @@ function loadPageEvidence(root, pageId) {
|
|
|
60424
60721
|
return { pageId, img, scale, viewport, ordinalOf, boxes, truncated: Boolean(digest.truncated) };
|
|
60425
60722
|
}
|
|
60426
60723
|
function metaOf(dir) {
|
|
60427
|
-
const raw = readJson3(
|
|
60724
|
+
const raw = readJson3(join44(dir, "meta.json"));
|
|
60428
60725
|
if (raw === void 0) return void 0;
|
|
60429
60726
|
const v = validateSnapshotMeta(raw);
|
|
60430
60727
|
return v.ok && v.meta ? v.meta : void 0;
|
|
@@ -60556,7 +60853,7 @@ function bump(t2, reason) {
|
|
|
60556
60853
|
t2.skipped[reason] = (t2.skipped[reason] ?? 0) + 1;
|
|
60557
60854
|
}
|
|
60558
60855
|
function cropName(f) {
|
|
60559
|
-
return `${
|
|
60856
|
+
return `${createHash7("sha1").update(findingId(f)).digest("hex").slice(0, 12)}.png`;
|
|
60560
60857
|
}
|
|
60561
60858
|
function writeEvidence(result, opts) {
|
|
60562
60859
|
const root = opts.root ?? process.cwd();
|
|
@@ -60589,7 +60886,7 @@ function writeEvidence(result, opts) {
|
|
|
60589
60886
|
}
|
|
60590
60887
|
continue;
|
|
60591
60888
|
}
|
|
60592
|
-
const dir =
|
|
60889
|
+
const dir = join44(opts.outDir, "assets", pageId);
|
|
60593
60890
|
let wrote = false;
|
|
60594
60891
|
const perRule = /* @__PURE__ */ new Map();
|
|
60595
60892
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -60614,13 +60911,13 @@ function writeEvidence(result, opts) {
|
|
|
60614
60911
|
continue;
|
|
60615
60912
|
}
|
|
60616
60913
|
const name2 = cropName(f);
|
|
60617
|
-
const path =
|
|
60914
|
+
const path = join44(dir, name2);
|
|
60618
60915
|
try {
|
|
60619
60916
|
if (!wrote) {
|
|
60620
|
-
|
|
60917
|
+
mkdirSync14(dir, { recursive: true });
|
|
60621
60918
|
wrote = true;
|
|
60622
60919
|
}
|
|
60623
|
-
|
|
60920
|
+
writeFileSync16(path, crop.png);
|
|
60624
60921
|
} catch {
|
|
60625
60922
|
manifest.skipped.set(id, "no-screenshot");
|
|
60626
60923
|
bump(tally, "no-screenshot");
|
|
@@ -60652,8 +60949,8 @@ function writeEvidence(result, opts) {
|
|
|
60652
60949
|
}
|
|
60653
60950
|
|
|
60654
60951
|
// src/html-emit.ts
|
|
60655
|
-
import { mkdirSync as
|
|
60656
|
-
import { join as
|
|
60952
|
+
import { mkdirSync as mkdirSync15, readFileSync as readFileSync28, statSync as statSync11, writeFileSync as writeFileSync17 } from "fs";
|
|
60953
|
+
import { join as join45 } from "path";
|
|
60657
60954
|
|
|
60658
60955
|
// src/html.ts
|
|
60659
60956
|
var MARK4 = { C: "C", NC: "NC", NA: "\u2014", manual: "?" };
|
|
@@ -61215,7 +61512,7 @@ function inlineSize(path) {
|
|
|
61215
61512
|
}
|
|
61216
61513
|
function dataUri(path) {
|
|
61217
61514
|
try {
|
|
61218
|
-
return `data:image/png;base64,${
|
|
61515
|
+
return `data:image/png;base64,${readFileSync28(path).toString("base64")}`;
|
|
61219
61516
|
} catch {
|
|
61220
61517
|
return void 0;
|
|
61221
61518
|
}
|
|
@@ -61254,7 +61551,7 @@ function writeHtml(result, opts) {
|
|
|
61254
61551
|
const t2 = T5[lang];
|
|
61255
61552
|
const budget = opts.inlineBudget ?? DEFAULT_INLINE_BUDGET;
|
|
61256
61553
|
const stdKey = isCore(standard) ? "wcag" : loadPack(standard).key;
|
|
61257
|
-
|
|
61554
|
+
mkdirSync15(opts.outDir, { recursive: true });
|
|
61258
61555
|
const crops = [...opts.evidence?.crops.values() ?? []];
|
|
61259
61556
|
const shots = [...opts.screenshots?.values() ?? []];
|
|
61260
61557
|
const rung = pickRung(
|
|
@@ -61286,7 +61583,7 @@ function writeHtml(result, opts) {
|
|
|
61286
61583
|
return drawn;
|
|
61287
61584
|
};
|
|
61288
61585
|
const flat = opts.layout === "pages";
|
|
61289
|
-
const sheetDir = flat ? opts.outDir :
|
|
61586
|
+
const sheetDir = flat ? opts.outDir : join45(opts.outDir, "pages");
|
|
61290
61587
|
const up = flat ? "./" : "../";
|
|
61291
61588
|
const compositeName = `ultra11y-${stdKey}-${result.date}.html`;
|
|
61292
61589
|
const nav = flat ? [{ href: "./index.html", text: t2.pagesTitle }] : [
|
|
@@ -61305,26 +61602,26 @@ function writeHtml(result, opts) {
|
|
|
61305
61602
|
nav: nav.map((n) => ({ ...n, current: n.href === `./${compositeName}` }))
|
|
61306
61603
|
});
|
|
61307
61604
|
if (notices.length) composite.blocks.unshift({ kind: "note", tone: "warn", runs: noticeRuns(notices) });
|
|
61308
|
-
compositePath =
|
|
61309
|
-
|
|
61605
|
+
compositePath = join45(opts.outDir, compositeName);
|
|
61606
|
+
writeFileSync17(compositePath, renderHtmlDocument(composite));
|
|
61310
61607
|
}
|
|
61311
61608
|
const fileCrops = cropLookup(opts.evidence, lang, (_p, href) => href.replace(/^\.\//, up));
|
|
61312
61609
|
const indexNav = nav.map((n) => ({ ...n, current: n.href === "./index.html" }));
|
|
61313
61610
|
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") });
|
|
61314
|
-
const indexPath =
|
|
61315
|
-
|
|
61611
|
+
const indexPath = join45(opts.outDir, "index.html");
|
|
61612
|
+
writeFileSync17(indexPath, renderHtmlDocument(index));
|
|
61316
61613
|
const sheets = [];
|
|
61317
61614
|
if (opts.pages) {
|
|
61318
61615
|
const derived = derivePages(result, pagesOf(result));
|
|
61319
|
-
|
|
61616
|
+
mkdirSync15(sheetDir, { recursive: true });
|
|
61320
61617
|
const sheetNav = flat ? [{ href: "./index.html", text: t2.pagesTitle }] : [
|
|
61321
61618
|
{ href: `${up}index.html`, text: t2.indexTitle },
|
|
61322
61619
|
{ href: `${up}${compositeName}`, text: t2.docTitle },
|
|
61323
61620
|
{ href: "./index.html", text: t2.pagesTitle }
|
|
61324
61621
|
];
|
|
61325
61622
|
if (!flat) {
|
|
61326
|
-
|
|
61327
|
-
|
|
61623
|
+
writeFileSync17(
|
|
61624
|
+
join45(sheetDir, "index.html"),
|
|
61328
61625
|
renderHtmlDocument(
|
|
61329
61626
|
pagesIndexDoc(result, {
|
|
61330
61627
|
standard,
|
|
@@ -61334,7 +61631,7 @@ function writeHtml(result, opts) {
|
|
|
61334
61631
|
})
|
|
61335
61632
|
)
|
|
61336
61633
|
);
|
|
61337
|
-
sheets.push(
|
|
61634
|
+
sheets.push(join45(sheetDir, "index.html"));
|
|
61338
61635
|
}
|
|
61339
61636
|
for (const p of derived) {
|
|
61340
61637
|
const doc = pageDoc(result, p, {
|
|
@@ -61347,8 +61644,8 @@ function writeHtml(result, opts) {
|
|
|
61347
61644
|
// beside the entry point, seen from wherever this sheet sits.
|
|
61348
61645
|
...opts.screenshots?.has(p.id) ? { screenshot: `${up}assets/${p.id}.png` } : {}
|
|
61349
61646
|
});
|
|
61350
|
-
const path =
|
|
61351
|
-
|
|
61647
|
+
const path = join45(sheetDir, `page-${p.id}.html`);
|
|
61648
|
+
writeFileSync17(path, renderHtmlDocument(doc));
|
|
61352
61649
|
sheets.push(path);
|
|
61353
61650
|
}
|
|
61354
61651
|
}
|
|
@@ -61368,8 +61665,8 @@ function noticeRuns(notices) {
|
|
|
61368
61665
|
|
|
61369
61666
|
// src/dev.ts
|
|
61370
61667
|
import { createServer } from "http";
|
|
61371
|
-
import { mkdirSync as
|
|
61372
|
-
import { dirname as
|
|
61668
|
+
import { mkdirSync as mkdirSync16, readFileSync as readFileSync29, writeFileSync as writeFileSync18 } from "fs";
|
|
61669
|
+
import { dirname as dirname13, join as join46 } from "path";
|
|
61373
61670
|
var DEV_DEFAULT_PORT = 4111;
|
|
61374
61671
|
function criterionLabel3(f, standard) {
|
|
61375
61672
|
if (isCore(standard)) return `WCAG ${f.criteriaId}`;
|
|
@@ -61591,13 +61888,13 @@ function auditCollected(root, payload) {
|
|
|
61591
61888
|
...payload.css ? { css: payload.css } : {},
|
|
61592
61889
|
...shot ? { screenshotBase64: shot } : {}
|
|
61593
61890
|
});
|
|
61594
|
-
return { ok: true, result: runAudit({ inputs: [
|
|
61891
|
+
return { ok: true, result: runAudit({ inputs: [join46(dir, "dom.html")] }) };
|
|
61595
61892
|
}
|
|
61596
61893
|
function projectPages(root) {
|
|
61597
61894
|
const snaps = readSnapshots(root);
|
|
61598
61895
|
if (!snaps.length) return { result: null, pages: [] };
|
|
61599
61896
|
const scope = pageScopesFrom(snaps);
|
|
61600
|
-
const result = runAudit({ inputs: [
|
|
61897
|
+
const result = runAudit({ inputs: [join46(root, ".ultra11y/pages")] });
|
|
61601
61898
|
result.scope.pages = scope;
|
|
61602
61899
|
attributePages(result, scope);
|
|
61603
61900
|
foldRecordedAdjudication(root, result);
|
|
@@ -61611,7 +61908,7 @@ function judgePages(result) {
|
|
|
61611
61908
|
function foldRecordedAdjudication(root, fresh) {
|
|
61612
61909
|
let prior;
|
|
61613
61910
|
try {
|
|
61614
|
-
prior = JSON.parse(
|
|
61911
|
+
prior = JSON.parse(readFileSync29(join46(root, "audits", "audit-latest.json"), "utf8"));
|
|
61615
61912
|
} catch {
|
|
61616
61913
|
return;
|
|
61617
61914
|
}
|
|
@@ -61726,9 +62023,9 @@ function startDevServer(opts) {
|
|
|
61726
62023
|
});
|
|
61727
62024
|
let auditPath;
|
|
61728
62025
|
if (applied.ok) {
|
|
61729
|
-
auditPath =
|
|
61730
|
-
|
|
61731
|
-
|
|
62026
|
+
auditPath = join46(opts.root, "audits", "audit-latest.json");
|
|
62027
|
+
mkdirSync16(dirname13(auditPath), { recursive: true });
|
|
62028
|
+
writeFileSync18(auditPath, `${JSON.stringify(applied.audit, null, 2)}
|
|
61732
62029
|
`);
|
|
61733
62030
|
}
|
|
61734
62031
|
res.writeHead(200, { "content-type": "application/json" }).end(
|
|
@@ -61755,10 +62052,10 @@ function startDevServer(opts) {
|
|
|
61755
62052
|
}
|
|
61756
62053
|
res.writeHead(404, { "content-type": "text/plain" }).end("not found");
|
|
61757
62054
|
});
|
|
61758
|
-
return new Promise((
|
|
62055
|
+
return new Promise((resolve17, reject) => {
|
|
61759
62056
|
server.once("error", reject);
|
|
61760
62057
|
server.listen(opts.port, "127.0.0.1", () => {
|
|
61761
|
-
|
|
62058
|
+
resolve17({
|
|
61762
62059
|
port: server.address().port,
|
|
61763
62060
|
close: () => new Promise((r) => server.close(() => r()))
|
|
61764
62061
|
});
|
|
@@ -62042,11 +62339,11 @@ function e2eSetupPlan(runners, paths, lang = "en") {
|
|
|
62042
62339
|
}
|
|
62043
62340
|
|
|
62044
62341
|
// src/orchestrate.ts
|
|
62045
|
-
import { existsSync as
|
|
62046
|
-
import { join as
|
|
62342
|
+
import { existsSync as existsSync32, mkdirSync as mkdirSync17, readFileSync as readFileSync30, rmSync as rmSync7, writeFileSync as writeFileSync19 } from "fs";
|
|
62343
|
+
import { join as join48, resolve as resolve13 } from "path";
|
|
62047
62344
|
|
|
62048
62345
|
// src/orchestrate-templates.ts
|
|
62049
|
-
import { join as
|
|
62346
|
+
import { join as join47 } from "path";
|
|
62050
62347
|
var ONE_WRITER_FOOTER = `
|
|
62051
62348
|
## Return, don't write
|
|
62052
62349
|
|
|
@@ -62132,7 +62429,7 @@ var PHASE_SPECS = {
|
|
|
62132
62429
|
title: "Adjudicate",
|
|
62133
62430
|
schema: ADJUDICATE_SCHEMA,
|
|
62134
62431
|
description: (n) => `Adjudicate the ${n} residual judgment criterion(ia) of an ultra11y audit (fan-out, fail-closed fold)`,
|
|
62135
|
-
applyHint: (engine, worklist, run2) => `node ${engine} verify --apply ${worklist} --in ${
|
|
62432
|
+
applyHint: (engine, worklist, run2) => `node ${engine} verify --apply ${worklist} --in ${join47(run2, "audit-latest.json")} --out ${run2}`
|
|
62136
62433
|
},
|
|
62137
62434
|
"verify-report": {
|
|
62138
62435
|
role: "refuter",
|
|
@@ -62154,7 +62451,7 @@ function toBatches(ids, batchSize) {
|
|
|
62154
62451
|
}
|
|
62155
62452
|
function phaseWorkflowScript(ph, runAbs, engineAbs, batchSize) {
|
|
62156
62453
|
const spec = phaseSpec(ph.name);
|
|
62157
|
-
const scriptPath =
|
|
62454
|
+
const scriptPath = join47(runAbs, "orchestration", `${ph.name}.workflow.mjs`);
|
|
62158
62455
|
const meta2 = { name: `ultra11y-${ph.name}`, description: spec.description(ph.items), phases: [{ title: spec.title }] };
|
|
62159
62456
|
return [
|
|
62160
62457
|
`export const meta = ${JSON.stringify(meta2)}`,
|
|
@@ -62198,7 +62495,7 @@ function agentContracts(runAbs, engineAbs) {
|
|
|
62198
62495
|
|
|
62199
62496
|
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.
|
|
62200
62497
|
|
|
62201
|
-
Worklist: \`${
|
|
62498
|
+
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>\`).
|
|
62202
62499
|
|
|
62203
62500
|
For EACH of your criteria:
|
|
62204
62501
|
|
|
@@ -62210,13 +62507,13 @@ For EACH of your criteria:
|
|
|
62210
62507
|
- \`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).
|
|
62211
62508
|
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.
|
|
62212
62509
|
|
|
62213
|
-
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
|
|
62510
|
+
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.
|
|
62214
62511
|
${footer}`,
|
|
62215
62512
|
refuter: `# Contract: refuter
|
|
62216
62513
|
|
|
62217
62514
|
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.
|
|
62218
62515
|
|
|
62219
|
-
Worklist: \`${
|
|
62516
|
+
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>\`).
|
|
62220
62517
|
|
|
62221
62518
|
For EACH of your entries:
|
|
62222
62519
|
|
|
@@ -62252,14 +62549,14 @@ ${status}
|
|
|
62252
62549
|
|
|
62253
62550
|
## The loop (play every role yourself, one item at a time)
|
|
62254
62551
|
|
|
62255
|
-
1. **Audit** (if not done): \`${engine} audit "<globs>" --graph --out ${runAbs}\` \u2192 \`${
|
|
62256
|
-
2. **Adjudicate the residual criteria** \u2014 \`${engine} verify --manual --in ${
|
|
62257
|
-
3. **Report**: \`${engine} report --in ${
|
|
62258
|
-
4. **Verify the report's claims** \u2014 \`${engine} verify --report <the report .md> --out ${runAbs}\` writes \`${
|
|
62552
|
+
1. **Audit** (if not done): \`${engine} audit "<globs>" --graph --out ${runAbs}\` \u2192 \`${join47(runAbs, "audit-latest.json")}\`.
|
|
62553
|
+
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.
|
|
62554
|
+
3. **Report**: \`${engine} report --in ${join47(runAbs, "audit-latest.json")} --out ${runAbs}\`.
|
|
62555
|
+
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>\`.
|
|
62259
62556
|
5. **Gate**: \`${engine} check --report <the report .md> --semantic\` must exit 0 before presenting anything.
|
|
62260
62557
|
6. **Fix & re-audit**: \`${engine} fix <globs> --write --iterate\`, hand-apply the judgment fixes, then loop from step 1 until the gate stays green.
|
|
62261
62558
|
|
|
62262
|
-
With subagents available, prefer the emitted workflows instead: \`orchestrate --run ${runAbs} --phase <p>\` then \`Workflow({ scriptPath: "${
|
|
62559
|
+
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.
|
|
62263
62560
|
`;
|
|
62264
62561
|
}
|
|
62265
62562
|
|
|
@@ -62268,13 +62565,13 @@ var PHASES = ["adjudicate", "verify-report"];
|
|
|
62268
62565
|
var SMALL_WORKLIST = 3;
|
|
62269
62566
|
var BATCH_SIZE2 = 8;
|
|
62270
62567
|
function listPhases(runDir, engineAbs) {
|
|
62271
|
-
const run2 =
|
|
62272
|
-
const adjPath =
|
|
62568
|
+
const run2 = resolve13(runDir);
|
|
62569
|
+
const adjPath = join48(run2, "ADJUDICATE.todo.json");
|
|
62273
62570
|
let adjIds = [];
|
|
62274
62571
|
let adjReady = false;
|
|
62275
|
-
if (
|
|
62572
|
+
if (existsSync32(adjPath)) {
|
|
62276
62573
|
try {
|
|
62277
|
-
const f = JSON.parse(
|
|
62574
|
+
const f = JSON.parse(readFileSync30(adjPath, "utf8"));
|
|
62278
62575
|
if (f && f.kind === "adjudication" && Array.isArray(f.items)) {
|
|
62279
62576
|
adjReady = true;
|
|
62280
62577
|
adjIds = f.items.map((i2) => i2.criteriaId);
|
|
@@ -62282,12 +62579,12 @@ function listPhases(runDir, engineAbs) {
|
|
|
62282
62579
|
} catch {
|
|
62283
62580
|
}
|
|
62284
62581
|
}
|
|
62285
|
-
const verPath =
|
|
62582
|
+
const verPath = join48(run2, "VERIFY.todo.json");
|
|
62286
62583
|
let verIds = [];
|
|
62287
62584
|
let verReady = false;
|
|
62288
|
-
if (
|
|
62585
|
+
if (existsSync32(verPath)) {
|
|
62289
62586
|
try {
|
|
62290
|
-
const items = JSON.parse(
|
|
62587
|
+
const items = JSON.parse(readFileSync30(verPath, "utf8"));
|
|
62291
62588
|
if (Array.isArray(items)) {
|
|
62292
62589
|
verReady = true;
|
|
62293
62590
|
verIds = items.map((i2) => String(i2.n));
|
|
@@ -62302,7 +62599,7 @@ function listPhases(runDir, engineAbs) {
|
|
|
62302
62599
|
worklist: adjPath,
|
|
62303
62600
|
items: adjIds.length,
|
|
62304
62601
|
ids: adjIds,
|
|
62305
|
-
prerequisite: `node ${engineAbs} verify --manual --in ${
|
|
62602
|
+
prerequisite: `node ${engineAbs} verify --manual --in ${join48(run2, "audit-latest.json")} --out ${run2}`
|
|
62306
62603
|
},
|
|
62307
62604
|
{
|
|
62308
62605
|
name: "verify-report",
|
|
@@ -62315,8 +62612,8 @@ function listPhases(runDir, engineAbs) {
|
|
|
62315
62612
|
];
|
|
62316
62613
|
}
|
|
62317
62614
|
function orchestrateRun(runDir, engineAbs, opts = {}) {
|
|
62318
|
-
const run2 =
|
|
62319
|
-
if (!
|
|
62615
|
+
const run2 = resolve13(runDir);
|
|
62616
|
+
if (!existsSync32(run2)) {
|
|
62320
62617
|
return { exitCode: 2, written: [], notices: [], errors: [`run dir not found: ${run2}`], phases: [] };
|
|
62321
62618
|
}
|
|
62322
62619
|
const phases = listPhases(run2, engineAbs);
|
|
@@ -62343,22 +62640,22 @@ function orchestrateRun(runDir, engineAbs, opts = {}) {
|
|
|
62343
62640
|
}
|
|
62344
62641
|
selected = [ph];
|
|
62345
62642
|
}
|
|
62346
|
-
const orchDir =
|
|
62347
|
-
const agentsDir =
|
|
62348
|
-
|
|
62349
|
-
|
|
62643
|
+
const orchDir = join48(run2, "orchestration");
|
|
62644
|
+
const agentsDir = join48(orchDir, "agents");
|
|
62645
|
+
mkdirSync17(join48(orchDir, "out"), { recursive: true });
|
|
62646
|
+
mkdirSync17(agentsDir, { recursive: true });
|
|
62350
62647
|
const written = [];
|
|
62351
62648
|
const notices = [];
|
|
62352
62649
|
for (const [name2, content] of Object.entries(agentContracts(run2, engineAbs))) {
|
|
62353
|
-
const p =
|
|
62354
|
-
|
|
62650
|
+
const p = join48(agentsDir, `${name2}.md`);
|
|
62651
|
+
writeFileSync19(p, content);
|
|
62355
62652
|
written.push(p);
|
|
62356
62653
|
}
|
|
62357
62654
|
const emitted = new Set(opts.eco ? [] : selected.filter((p) => p.items > 0).map((p) => p.name));
|
|
62358
62655
|
for (const ph of phases) {
|
|
62359
62656
|
if (emitted.has(ph.name)) continue;
|
|
62360
|
-
const stale =
|
|
62361
|
-
if (
|
|
62657
|
+
const stale = join48(orchDir, `${ph.name}.workflow.mjs`);
|
|
62658
|
+
if (existsSync32(stale)) {
|
|
62362
62659
|
rmSync7(stale, { force: true });
|
|
62363
62660
|
notices.push(`phase "${ph.name}": stale workflow removed \u2014 its worklist ${ph.ready ? "is now empty" : "no longer exists"}.`);
|
|
62364
62661
|
}
|
|
@@ -62372,13 +62669,13 @@ function orchestrateRun(runDir, engineAbs, opts = {}) {
|
|
|
62372
62669
|
if (ph.items <= SMALL_WORKLIST) {
|
|
62373
62670
|
notices.push(`phase "${ph.name}": only ${ph.items} item(s) \u2014 the sequential --eco path is equivalent and cheaper.`);
|
|
62374
62671
|
}
|
|
62375
|
-
const p =
|
|
62376
|
-
|
|
62672
|
+
const p = join48(orchDir, `${ph.name}.workflow.mjs`);
|
|
62673
|
+
writeFileSync19(p, phaseWorkflowScript(ph, run2, engineAbs, BATCH_SIZE2));
|
|
62377
62674
|
written.push(p);
|
|
62378
62675
|
}
|
|
62379
62676
|
}
|
|
62380
|
-
const rb =
|
|
62381
|
-
|
|
62677
|
+
const rb = join48(orchDir, "RUNBOOK.md");
|
|
62678
|
+
writeFileSync19(rb, runbookMd(phases, run2, engineAbs));
|
|
62382
62679
|
written.push(rb);
|
|
62383
62680
|
return { exitCode: 0, written, notices, errors: [], phases };
|
|
62384
62681
|
}
|
|
@@ -62387,8 +62684,8 @@ function orchestrateRun(runDir, engineAbs, opts = {}) {
|
|
|
62387
62684
|
import { createInterface as createInterface2 } from "readline";
|
|
62388
62685
|
|
|
62389
62686
|
// src/mcp/handlers.ts
|
|
62390
|
-
import { existsSync as
|
|
62391
|
-
import { isAbsolute as isAbsolute3, join as
|
|
62687
|
+
import { existsSync as existsSync33, readFileSync as readFileSync31, realpathSync as realpathSync4, statSync as statSync12 } from "fs";
|
|
62688
|
+
import { isAbsolute as isAbsolute3, join as join49, resolve as resolve14, sep as sep5 } from "path";
|
|
62392
62689
|
|
|
62393
62690
|
// src/criteria-view.ts
|
|
62394
62691
|
var CriteriaLookupError = class extends Error {
|
|
@@ -62865,8 +63162,8 @@ function positive(v, key) {
|
|
|
62865
63162
|
function requiredCwd(args2, defaults) {
|
|
62866
63163
|
const cwd = str2(args2.cwd) ?? defaults.defaultCwd;
|
|
62867
63164
|
if (!cwd) throw new ToolError("`cwd` is required: an absolute path to the project root.");
|
|
62868
|
-
const abs =
|
|
62869
|
-
if (!
|
|
63165
|
+
const abs = resolve14(cwd);
|
|
63166
|
+
if (!existsSync33(abs)) throw new ToolError(`project root not found: ${abs}`);
|
|
62870
63167
|
if (!statSync12(abs).isDirectory()) throw new ToolError(`\`cwd\` is not a directory: ${abs}`);
|
|
62871
63168
|
return abs;
|
|
62872
63169
|
}
|
|
@@ -62977,7 +63274,7 @@ function audit(args2, cwd) {
|
|
|
62977
63274
|
process.chdir(cwd);
|
|
62978
63275
|
const inputs = strArray2(args2.globs) ?? DEFAULT_GLOBS;
|
|
62979
63276
|
const scopedToDiff = bool(args2.changed) || bool(args2.staged) || str2(args2.since) !== void 0;
|
|
62980
|
-
const extra = [CAPTURES_DIR, PAGES_DIR].filter((d) => !scopedToDiff &&
|
|
63277
|
+
const extra = [CAPTURES_DIR, PAGES_DIR].filter((d) => !scopedToDiff && existsSync33(d) && !inputs.includes(d));
|
|
62981
63278
|
const result = runAudit({
|
|
62982
63279
|
inputs: [...inputs, ...extra],
|
|
62983
63280
|
include: strArray2(args2.include),
|
|
@@ -63096,13 +63393,13 @@ function handlePackCheck(args2, cwd) {
|
|
|
63096
63393
|
return { cwd, ...res };
|
|
63097
63394
|
}
|
|
63098
63395
|
function handleSampleCheck(cwd, standard) {
|
|
63099
|
-
const file =
|
|
63100
|
-
if (!
|
|
63396
|
+
const file = join49(cwd, ".ultra11yrc.json");
|
|
63397
|
+
if (!existsSync33(file)) {
|
|
63101
63398
|
throw new ToolError(`no .ultra11yrc.json at ${cwd} \u2014 a page sample must be declared before it can be linted.`);
|
|
63102
63399
|
}
|
|
63103
63400
|
let raw;
|
|
63104
63401
|
try {
|
|
63105
|
-
raw = JSON.parse(
|
|
63402
|
+
raw = JSON.parse(readFileSync31(file, "utf8"));
|
|
63106
63403
|
} catch (e) {
|
|
63107
63404
|
throw new ToolError(`.ultra11yrc.json is not valid JSON: ${e.message}`);
|
|
63108
63405
|
}
|
|
@@ -63193,10 +63490,10 @@ function handleInit(args2, cwd) {
|
|
|
63193
63490
|
throw new ToolError(`\`fail_on\` must be one of: error, warning, notice (got "${failOnRaw}")`);
|
|
63194
63491
|
}
|
|
63195
63492
|
const failOn = failOnRaw;
|
|
63196
|
-
const enginePath =
|
|
63493
|
+
const enginePath = resolve14("scripts/ultra11y.mjs");
|
|
63197
63494
|
const written = [];
|
|
63198
63495
|
if (bool(args2.hook)) written.push(writeHook(cwd, enginePath, failOn));
|
|
63199
|
-
if (bool(args2.ci)) written.push(
|
|
63496
|
+
if (bool(args2.ci)) written.push(join49(cwd, ".github/workflows/a11y.yml"));
|
|
63200
63497
|
if (!written.length) {
|
|
63201
63498
|
throw new ToolError("nothing to install \u2014 pass hook:true and/or ci:true.");
|
|
63202
63499
|
}
|
|
@@ -63216,8 +63513,8 @@ function reportText(args2, tool) {
|
|
|
63216
63513
|
if (inline) return inline;
|
|
63217
63514
|
if (!file) throw new ToolError(`\`report_text\` is required \u2014 the report markdown for ultra11y_${tool} to work on.`);
|
|
63218
63515
|
if (!isAbsolute3(file)) throw new ToolError("`report_file` must be an absolute path.");
|
|
63219
|
-
if (!
|
|
63220
|
-
return
|
|
63516
|
+
if (!existsSync33(file)) throw new ToolError(`report file not found: ${file}`);
|
|
63517
|
+
return readFileSync31(file, "utf8");
|
|
63221
63518
|
}
|
|
63222
63519
|
function handleCriteria(args2) {
|
|
63223
63520
|
const lang = langOf(args2);
|
|
@@ -63284,7 +63581,7 @@ function handleMethod(args2) {
|
|
|
63284
63581
|
function handleRead(args2, cwd) {
|
|
63285
63582
|
const raw = str2(args2.path);
|
|
63286
63583
|
if (!raw) throw new ToolError("`path` is required \u2014 relative to the project root, or an absolute path inside it.");
|
|
63287
|
-
const target = isAbsolute3(raw) ? raw :
|
|
63584
|
+
const target = isAbsolute3(raw) ? raw : join49(cwd, raw);
|
|
63288
63585
|
let real;
|
|
63289
63586
|
try {
|
|
63290
63587
|
real = realpathSync4(target);
|
|
@@ -63298,7 +63595,7 @@ function handleRead(args2, cwd) {
|
|
|
63298
63595
|
const st = statSync12(real);
|
|
63299
63596
|
if (!st.isFile()) throw new ToolError(`not a file: ${raw}`);
|
|
63300
63597
|
if (st.size > MAX_READ_BYTES) throw new ToolError(`file is too large to read (${st.size} bytes): ${raw}`);
|
|
63301
|
-
const lines =
|
|
63598
|
+
const lines = readFileSync31(real, "utf8").split("\n");
|
|
63302
63599
|
const total = lines.length;
|
|
63303
63600
|
const start2 = Math.max(1, Math.floor(num2(args2.start_line) ?? 1));
|
|
63304
63601
|
if (start2 > total) throw new ToolError(`start_line ${start2} is past the end of the file (${total} lines).`);
|
|
@@ -63953,25 +64250,25 @@ function str3(v) {
|
|
|
63953
64250
|
var DECLARED = new Set([...TOOLS2, ...WRITE_TOOLS].map((t2) => t2.name));
|
|
63954
64251
|
|
|
63955
64252
|
// src/mcp/resources.ts
|
|
63956
|
-
import { existsSync as
|
|
63957
|
-
import { basename as basename3, dirname as
|
|
64253
|
+
import { existsSync as existsSync34, readdirSync as readdirSync6, readFileSync as readFileSync32, realpathSync as realpathSync5, statSync as statSync13 } from "fs";
|
|
64254
|
+
import { basename as basename3, dirname as dirname14, join as join50, resolve as resolve15, sep as sep6 } from "path";
|
|
63958
64255
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
63959
64256
|
var SKILL_NAME = "ultra11y";
|
|
63960
64257
|
var URI_SCHEME = "skill://";
|
|
63961
64258
|
function resolveSkillRoot(moduleDir) {
|
|
63962
|
-
const here = moduleDir ??
|
|
63963
|
-
const candidates2 = [
|
|
63964
|
-
return candidates2.find((dir) =>
|
|
64259
|
+
const here = moduleDir ?? dirname14(fileURLToPath4(import.meta.url));
|
|
64260
|
+
const candidates2 = [resolve15(here, ".."), resolve15(here, "..", "skills", SKILL_NAME), resolve15(here, "..", "..", "skills", SKILL_NAME)];
|
|
64261
|
+
return candidates2.find((dir) => existsSync34(join50(dir, "SKILL.md")));
|
|
63965
64262
|
}
|
|
63966
64263
|
function listResources(moduleDir) {
|
|
63967
64264
|
const root = resolveSkillRoot(moduleDir);
|
|
63968
64265
|
if (!root) return [];
|
|
63969
64266
|
const out2 = [describe(root, "SKILL.md", `${SKILL_NAME}: the skill`)];
|
|
63970
|
-
const refDir =
|
|
63971
|
-
if (!
|
|
64267
|
+
const refDir = join50(root, "references");
|
|
64268
|
+
if (!existsSync34(refDir)) return out2;
|
|
63972
64269
|
for (const file of readdirSync6(refDir).sort()) {
|
|
63973
64270
|
if (!file.endsWith(".md")) continue;
|
|
63974
|
-
out2.push(describe(root,
|
|
64271
|
+
out2.push(describe(root, join50("references", file), `${SKILL_NAME} reference: ${basename3(file, ".md")}`));
|
|
63975
64272
|
}
|
|
63976
64273
|
return out2;
|
|
63977
64274
|
}
|
|
@@ -63983,7 +64280,7 @@ function readResource(uri, moduleDir) {
|
|
|
63983
64280
|
if (!root) throw new ResourceError("no skill payload found next to this build \u2014 nothing to read");
|
|
63984
64281
|
const rel2 = uri.slice(URI_SCHEME.length);
|
|
63985
64282
|
if (!rel2) throw new ResourceError("empty resource path");
|
|
63986
|
-
const target =
|
|
64283
|
+
const target = resolve15(root, rel2);
|
|
63987
64284
|
const rootReal = realpathSync5(root);
|
|
63988
64285
|
let targetReal;
|
|
63989
64286
|
try {
|
|
@@ -63995,7 +64292,7 @@ function readResource(uri, moduleDir) {
|
|
|
63995
64292
|
throw new ResourceError(`resource path escapes the skill root: ${uri}`);
|
|
63996
64293
|
}
|
|
63997
64294
|
if (!statSync13(targetReal).isFile()) throw new ResourceError(`not a file: ${uri}`);
|
|
63998
|
-
return { uri, mimeType: "text/markdown", text:
|
|
64295
|
+
return { uri, mimeType: "text/markdown", text: readFileSync32(targetReal, "utf8") };
|
|
63999
64296
|
}
|
|
64000
64297
|
var ResourceError = class extends Error {
|
|
64001
64298
|
};
|
|
@@ -64006,14 +64303,14 @@ function describe(root, rel2, fallbackTitle) {
|
|
|
64006
64303
|
title: fallbackTitle,
|
|
64007
64304
|
mimeType: "text/markdown"
|
|
64008
64305
|
};
|
|
64009
|
-
const summary = firstProse(
|
|
64306
|
+
const summary = firstProse(join50(root, rel2));
|
|
64010
64307
|
if (summary) decl.description = summary;
|
|
64011
64308
|
return decl;
|
|
64012
64309
|
}
|
|
64013
64310
|
function firstProse(file) {
|
|
64014
64311
|
let text;
|
|
64015
64312
|
try {
|
|
64016
|
-
text =
|
|
64313
|
+
text = readFileSync32(file, "utf8");
|
|
64017
64314
|
} catch {
|
|
64018
64315
|
return void 0;
|
|
64019
64316
|
}
|
|
@@ -64395,14 +64692,14 @@ function startHttpServer(opts = {}) {
|
|
|
64395
64692
|
server.requestTimeout = 0;
|
|
64396
64693
|
server.headersTimeout = 6e4;
|
|
64397
64694
|
server.keepAliveTimeout = 12e4;
|
|
64398
|
-
return new Promise((
|
|
64695
|
+
return new Promise((resolve17, reject) => {
|
|
64399
64696
|
server.once("error", reject);
|
|
64400
64697
|
server.listen(opts.port ?? 0, bind, () => {
|
|
64401
64698
|
server.removeListener("error", reject);
|
|
64402
64699
|
const addr2 = server.address();
|
|
64403
64700
|
const port = typeof addr2 === "object" && addr2 ? addr2.port : opts.port ?? 0;
|
|
64404
64701
|
const host = bind.includes(":") ? `[${bind}]` : bind;
|
|
64405
|
-
|
|
64702
|
+
resolve17({
|
|
64406
64703
|
server,
|
|
64407
64704
|
port,
|
|
64408
64705
|
url: `http://${host}:${port}${MCP_PATH}`,
|
|
@@ -64511,7 +64808,7 @@ function sendJson(res, status, body3, origin, extra = {}) {
|
|
|
64511
64808
|
}
|
|
64512
64809
|
var DRAIN_LIMIT = MAX_BODY_BYTES * 8;
|
|
64513
64810
|
function readBody2(req) {
|
|
64514
|
-
return new Promise((
|
|
64811
|
+
return new Promise((resolve17, reject) => {
|
|
64515
64812
|
const chunks = [];
|
|
64516
64813
|
let size = 0;
|
|
64517
64814
|
let over = false;
|
|
@@ -64535,7 +64832,7 @@ function readBody2(req) {
|
|
|
64535
64832
|
});
|
|
64536
64833
|
req.on("end", () => {
|
|
64537
64834
|
if (over) reject(new Error("too large"));
|
|
64538
|
-
else
|
|
64835
|
+
else resolve17(Buffer.concat(chunks).toString("utf8"));
|
|
64539
64836
|
});
|
|
64540
64837
|
req.on("error", reject);
|
|
64541
64838
|
req.on("aborted", () => reject(new Error("client aborted the request")));
|
|
@@ -64837,6 +65134,13 @@ Options:
|
|
|
64837
65134
|
--generate criteria: emit the bundled references/criteria.md (WCAG 2.2 AA)
|
|
64838
65135
|
--apply <file> verify: reduce a filled verdicts file to a pass/fail gate
|
|
64839
65136
|
(requires --report \u2014 coverage is re-derived from the report, uncapped)
|
|
65137
|
+
Also accepts an ADJUDICATION (--in <audit.json>), or a VERDICT LEDGER,
|
|
65138
|
+
which it REPLAYS onto the audit with no model in the loop
|
|
65139
|
+
--ledger <file> verify --apply / judge --apply: record the verdicts the fold ACCEPTED,
|
|
65140
|
+
so a later run replays them for free (default:
|
|
65141
|
+
.ultra11y/verdicts/<standard>.json). Replay re-derives the evidence
|
|
65142
|
+
and re-runs the same gate; a verdict whose evidence changed is
|
|
65143
|
+
dropped as stale and its criterion says so
|
|
64840
65144
|
--max-verify <n> verify: cap the worklist size; 0 = no cap (default: 40)
|
|
64841
65145
|
--verdicts <file> check --semantic: the adjudicated verdicts artifact
|
|
64842
65146
|
(default: VERIFY.todo.json next to the report)
|
|
@@ -64899,6 +65203,10 @@ Options:
|
|
|
64899
65203
|
--apply judge: fold the verdicts straight into the audit, through the SAME
|
|
64900
65204
|
fail-closed gate an agent's verdicts pass (no unjustified C/NA, no
|
|
64901
65205
|
ungroundable NC, no unadjudicated criterion)
|
|
65206
|
+
--strict verify --apply / judge --apply: all-or-nothing fold \u2014 one refused
|
|
65207
|
+
verdict discards the whole adjudication. The default folds PER
|
|
65208
|
+
VERDICT: a refusal costs its own criterion, which stays to assess
|
|
65209
|
+
carrying the refusal, and every verdict that proved itself lands
|
|
64902
65210
|
--semantic verify: fold the support-check into one pass
|
|
64903
65211
|
check: engage the semantic gate \u2014 requires an adjudicated verdicts
|
|
64904
65212
|
artifact (fails closed when absent) and re-grounds every passing
|
|
@@ -64973,6 +65281,10 @@ var VALUE_FLAGS2 = /* @__PURE__ */ new Set([
|
|
|
64973
65281
|
"dedup",
|
|
64974
65282
|
"only",
|
|
64975
65283
|
"standard",
|
|
65284
|
+
// `verify --apply` / `judge --apply`: where to RECORD the verdicts the fold accepted, so a
|
|
65285
|
+
// later run can replay them without a model (src/ledger.ts). A path; empty falls back to the
|
|
65286
|
+
// standard's default location under .ultra11y/verdicts/.
|
|
65287
|
+
"ledger",
|
|
64976
65288
|
"baseline",
|
|
64977
65289
|
"fail-on",
|
|
64978
65290
|
"split",
|
|
@@ -65044,6 +65356,9 @@ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
65044
65356
|
"generate",
|
|
65045
65357
|
"semantic",
|
|
65046
65358
|
"manual",
|
|
65359
|
+
// `verify --apply` / `judge --apply`: restore the all-or-nothing fold, where one refused
|
|
65360
|
+
// verdict discards the whole adjudication. The default is per-verdict.
|
|
65361
|
+
"strict",
|
|
65047
65362
|
"no-technical",
|
|
65048
65363
|
"override",
|
|
65049
65364
|
"local",
|
|
@@ -65268,9 +65583,9 @@ async function cmdAudit(p) {
|
|
|
65268
65583
|
const capturesFlag = typeof p.flags.captures === "string" && p.flags.captures ? p.flags.captures : void 0;
|
|
65269
65584
|
const capturesDir = capturesFlag ?? CAPTURES_DIR;
|
|
65270
65585
|
const scopedToDiff = p.flags.changed === true || p.flags.staged === true || since !== void 0;
|
|
65271
|
-
const capturesWanted = p.flags["no-captures"] !== true && !inputs.includes("-") && (capturesFlag !== void 0 ||
|
|
65586
|
+
const capturesWanted = p.flags["no-captures"] !== true && !inputs.includes("-") && (capturesFlag !== void 0 || existsSync35(capturesDir));
|
|
65272
65587
|
const useCaptures = capturesWanted && !scopedToDiff && !inputs.includes(capturesDir);
|
|
65273
|
-
const pagesWanted = p.flags["no-captures"] !== true && !inputs.includes("-") &&
|
|
65588
|
+
const pagesWanted = p.flags["no-captures"] !== true && !inputs.includes("-") && existsSync35(PAGES_DIR);
|
|
65274
65589
|
const usePages = pagesWanted && !scopedToDiff && !inputs.includes(PAGES_DIR);
|
|
65275
65590
|
const pagesInScope = pagesWanted && !scopedToDiff;
|
|
65276
65591
|
const auditInputs = [...inputs, ...useCaptures ? [capturesDir] : [], ...usePages ? [PAGES_DIR] : []];
|
|
@@ -65312,10 +65627,10 @@ async function cmdAudit(p) {
|
|
|
65312
65627
|
if (typeof p.flags.out === "string") {
|
|
65313
65628
|
const out2 = p.flags.out;
|
|
65314
65629
|
const asFile = out2.toLowerCase().endsWith(".json");
|
|
65315
|
-
const target = asFile ? out2 :
|
|
65630
|
+
const target = asFile ? out2 : join51(out2, "audit-latest.json");
|
|
65316
65631
|
try {
|
|
65317
|
-
|
|
65318
|
-
|
|
65632
|
+
mkdirSync18(asFile ? dirname15(out2) : out2, { recursive: true });
|
|
65633
|
+
writeFileSync20(target, JSON.stringify(result, null, 2) + "\n");
|
|
65319
65634
|
console.error(lang === "fr" ? `\u2192 audit \xE9crit dans ${target}` : `\u2192 audit written to ${target}`);
|
|
65320
65635
|
} catch {
|
|
65321
65636
|
}
|
|
@@ -65334,7 +65649,7 @@ async function cmdAudit(p) {
|
|
|
65334
65649
|
const baselineFlag = p.flags.baseline;
|
|
65335
65650
|
if (typeof baselineFlag === "string" && baselineFlag) {
|
|
65336
65651
|
let baseline = null;
|
|
65337
|
-
if (
|
|
65652
|
+
if (existsSync35(baselineFlag)) {
|
|
65338
65653
|
try {
|
|
65339
65654
|
const parsed = JSON.parse(readText(baselineFlag));
|
|
65340
65655
|
if (isCurrentAudit(parsed)) baseline = parsed;
|
|
@@ -65388,11 +65703,11 @@ async function cmdDev(p) {
|
|
|
65388
65703
|
return 2;
|
|
65389
65704
|
}
|
|
65390
65705
|
if (p.flags.next === true) {
|
|
65391
|
-
const dir =
|
|
65706
|
+
const dir = join51(root, ".ultra11y", "next");
|
|
65392
65707
|
const rel2 = ".ultra11y/next/overlay.jsx";
|
|
65393
65708
|
try {
|
|
65394
|
-
|
|
65395
|
-
|
|
65709
|
+
mkdirSync18(dir, { recursive: true });
|
|
65710
|
+
writeFileSync20(join51(dir, "overlay.jsx"), nextOverlayComponent(port));
|
|
65396
65711
|
} catch (e) {
|
|
65397
65712
|
console.error(`ultra11y dev: could not write ${rel2}: ${e instanceof Error ? e.message : String(e)}`);
|
|
65398
65713
|
return 1;
|
|
@@ -65423,9 +65738,9 @@ async function cmdDev(p) {
|
|
|
65423
65738
|
}
|
|
65424
65739
|
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}`);
|
|
65425
65740
|
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.");
|
|
65426
|
-
await new Promise((
|
|
65741
|
+
await new Promise((resolve17) => {
|
|
65427
65742
|
const stop2 = () => {
|
|
65428
|
-
void server.close().then(
|
|
65743
|
+
void server.close().then(resolve17);
|
|
65429
65744
|
};
|
|
65430
65745
|
process.once("SIGINT", stop2);
|
|
65431
65746
|
process.once("SIGTERM", stop2);
|
|
@@ -65441,7 +65756,7 @@ async function cmdPagesDiscover(p) {
|
|
|
65441
65756
|
const snaps = sampleFromSnapshots(readSnapshots(root));
|
|
65442
65757
|
if (!snaps.length) {
|
|
65443
65758
|
console.error(
|
|
65444
|
-
lang === "fr" ? `ultra11y pages discover : aucun instantan\xE9 sous ${
|
|
65759
|
+
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\`.`
|
|
65445
65760
|
);
|
|
65446
65761
|
return 1;
|
|
65447
65762
|
}
|
|
@@ -65521,9 +65836,9 @@ ${found} page(s) discovered, ${merged.added.length} new. Nothing was written \u2
|
|
|
65521
65836
|
}
|
|
65522
65837
|
return 0;
|
|
65523
65838
|
}
|
|
65524
|
-
const file =
|
|
65839
|
+
const file = join51(process.cwd(), ".ultra11yrc.json");
|
|
65525
65840
|
let doc = {};
|
|
65526
|
-
if (
|
|
65841
|
+
if (existsSync35(file)) {
|
|
65527
65842
|
try {
|
|
65528
65843
|
doc = JSON.parse(readText(file));
|
|
65529
65844
|
} catch {
|
|
@@ -65532,7 +65847,7 @@ ${found} page(s) discovered, ${merged.added.length} new. Nothing was written \u2
|
|
|
65532
65847
|
}
|
|
65533
65848
|
}
|
|
65534
65849
|
doc.sample = v.sample;
|
|
65535
|
-
|
|
65850
|
+
writeFileSync20(file, `${JSON.stringify(doc, null, 2)}
|
|
65536
65851
|
`);
|
|
65537
65852
|
if (!p.flags.json)
|
|
65538
65853
|
console.log(
|
|
@@ -65592,27 +65907,27 @@ async function cmdPages(p) {
|
|
|
65592
65907
|
const derived = derivePages(result, scope);
|
|
65593
65908
|
const split = p.flags.split === "page";
|
|
65594
65909
|
const outDir = typeof p.flags.out === "string" && p.flags.out ? p.flags.out : void 0;
|
|
65595
|
-
const shotOf = (id) =>
|
|
65910
|
+
const shotOf = (id) => join51(PAGES_DIR, id, "screen.png");
|
|
65596
65911
|
const shotsRelative = (fileDir) => {
|
|
65597
65912
|
const m = /* @__PURE__ */ new Map();
|
|
65598
65913
|
for (const pg of derived) {
|
|
65599
|
-
if (
|
|
65914
|
+
if (existsSync35(shotOf(pg.id))) m.set(pg.id, relative5(fileDir, shotOf(pg.id)).split("\\").join("/"));
|
|
65600
65915
|
}
|
|
65601
65916
|
return m;
|
|
65602
65917
|
};
|
|
65603
65918
|
const shotPaths = (pages) => {
|
|
65604
65919
|
const m = /* @__PURE__ */ new Map();
|
|
65605
|
-
for (const pg of pages) if (
|
|
65920
|
+
for (const pg of pages) if (existsSync35(shotOf(pg.id))) m.set(pg.id, shotOf(pg.id));
|
|
65606
65921
|
return m;
|
|
65607
65922
|
};
|
|
65608
65923
|
const shotsCopiedInto = (dir) => {
|
|
65609
65924
|
const m = /* @__PURE__ */ new Map();
|
|
65610
|
-
const assets =
|
|
65925
|
+
const assets = join51(dir, "assets");
|
|
65611
65926
|
for (const pg of derived) {
|
|
65612
65927
|
const src = shotOf(pg.id);
|
|
65613
|
-
if (!
|
|
65614
|
-
|
|
65615
|
-
copyFileSync4(src,
|
|
65928
|
+
if (!existsSync35(src)) continue;
|
|
65929
|
+
mkdirSync18(assets, { recursive: true });
|
|
65930
|
+
copyFileSync4(src, join51(assets, `${pg.id}.png`));
|
|
65616
65931
|
m.set(pg.id, `./assets/${pg.id}.png`);
|
|
65617
65932
|
}
|
|
65618
65933
|
return m;
|
|
@@ -65629,7 +65944,7 @@ async function cmdPages(p) {
|
|
|
65629
65944
|
console.log(renderPagesDocument(result, derived, { standard, lang, screenshots: shotsRelative(".") }));
|
|
65630
65945
|
return 0;
|
|
65631
65946
|
}
|
|
65632
|
-
|
|
65947
|
+
mkdirSync18(outDir, { recursive: true });
|
|
65633
65948
|
const manifest = wantEvidence ? writeEvidence(result, { outDir, ...evidenceCapsOf(p) }) : void 0;
|
|
65634
65949
|
const cropFor = manifest ? (f) => {
|
|
65635
65950
|
const c2 = manifest.crops.get(findingId(f));
|
|
@@ -65638,7 +65953,7 @@ async function cmdPages(p) {
|
|
|
65638
65953
|
if (manifest) {
|
|
65639
65954
|
const total = evidenceNotice(manifest, null, lang);
|
|
65640
65955
|
console.error(
|
|
65641
|
-
lang === "fr" ? `ultra11y : ${manifest.totals.imaged} vignette(s) \xE9crite(s) dans ${
|
|
65956
|
+
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")}.`
|
|
65642
65957
|
);
|
|
65643
65958
|
for (const line of total) console.error(line);
|
|
65644
65959
|
}
|
|
@@ -65647,8 +65962,8 @@ async function cmdPages(p) {
|
|
|
65647
65962
|
...manifest ? { evidenceNotice: (id) => evidenceNotice(manifest, id, lang) } : {}
|
|
65648
65963
|
};
|
|
65649
65964
|
if (!split) {
|
|
65650
|
-
const file =
|
|
65651
|
-
|
|
65965
|
+
const file = join51(outDir, `pages-${result.date}.md`);
|
|
65966
|
+
writeFileSync20(file, `${renderPagesDocument(result, derived, { standard, lang, screenshots: shotsCopiedInto(outDir), ...evidenceOpts })}
|
|
65652
65967
|
`);
|
|
65653
65968
|
const html2 = wantHtml ? emitHtml(result, {
|
|
65654
65969
|
outDir,
|
|
@@ -65667,11 +65982,11 @@ async function cmdPages(p) {
|
|
|
65667
65982
|
const sheet = (id) => `page-${id}.md`;
|
|
65668
65983
|
const hrefs = new Map(derived.map((pg) => [pg.id, `./${sheet(pg.id)}`]));
|
|
65669
65984
|
for (const pg of derived) {
|
|
65670
|
-
|
|
65985
|
+
writeFileSync20(join51(outDir, sheet(pg.id)), `${renderPageDocument(result, pg, { standard, lang, screenshots: shots, ...evidenceOpts })}
|
|
65671
65986
|
`);
|
|
65672
65987
|
}
|
|
65673
|
-
const index =
|
|
65674
|
-
|
|
65988
|
+
const index = join51(outDir, "index.md");
|
|
65989
|
+
writeFileSync20(index, `${renderPagesIndex(result, derived, { standard, lang, hrefs })}
|
|
65675
65990
|
`);
|
|
65676
65991
|
const html = wantHtml ? emitHtml(result, {
|
|
65677
65992
|
outDir,
|
|
@@ -65722,7 +66037,7 @@ async function cmdSnapshot(p) {
|
|
|
65722
66037
|
2
|
|
65723
66038
|
)
|
|
65724
66039
|
);
|
|
65725
|
-
else if (!snaps.length) console.log(lang === "fr" ? `Aucun instantan\xE9 dans ${
|
|
66040
|
+
else if (!snaps.length) console.log(lang === "fr" ? `Aucun instantan\xE9 dans ${join51(root, PAGES_DIR)}.` : `No snapshot in ${join51(root, PAGES_DIR)}.`);
|
|
65726
66041
|
else for (const s of snaps) console.log(`${s.meta.id} ${s.meta.name} ${s.meta.url}${s.meta.auth ? " [auth]" : ""}`);
|
|
65727
66042
|
return 0;
|
|
65728
66043
|
}
|
|
@@ -65769,7 +66084,7 @@ async function cmdSnapshot(p) {
|
|
|
65769
66084
|
console.error(`ultra11y snapshot write: could not write the snapshot: ${e instanceof Error ? e.message : String(e)}`);
|
|
65770
66085
|
return 1;
|
|
65771
66086
|
}
|
|
65772
|
-
const result = runAudit({ inputs: [
|
|
66087
|
+
const result = runAudit({ inputs: [join51(dir, "dom.html")], onWarn: (m) => console.error(m) });
|
|
65773
66088
|
const failOnRaw = p.flags["fail-on"];
|
|
65774
66089
|
const failOnParsed = parseFailOn(failOnRaw);
|
|
65775
66090
|
if (failOnRaw !== void 0 && failOnParsed === null) {
|
|
@@ -65801,9 +66116,9 @@ function cmdInit(p) {
|
|
|
65801
66116
|
if (want.baseline) {
|
|
65802
66117
|
const inputs = p.positionals.length ? p.positionals : ["."];
|
|
65803
66118
|
const result = runAudit({ inputs, onWarn: (m) => console.error(m) });
|
|
65804
|
-
|
|
65805
|
-
const bp =
|
|
65806
|
-
|
|
66119
|
+
mkdirSync18(join51(root, "audits"), { recursive: true });
|
|
66120
|
+
const bp = join51(root, "audits", "baseline.json");
|
|
66121
|
+
writeFileSync20(bp, JSON.stringify(result, null, 2) + "\n");
|
|
65807
66122
|
wrote.push(bp);
|
|
65808
66123
|
}
|
|
65809
66124
|
if (want.hook) wrote.push(writeHook(root, engineRel, failOn, legacy ? "baseline" : "staged"));
|
|
@@ -66102,8 +66417,8 @@ async function cmdTickets(p) {
|
|
|
66102
66417
|
const ticketsOut = typeof p.flags.out === "string" && p.flags.out ? p.flags.out : void 0;
|
|
66103
66418
|
let setPath;
|
|
66104
66419
|
if (ticketsOut) {
|
|
66105
|
-
|
|
66106
|
-
setPath =
|
|
66420
|
+
mkdirSync18(ticketsOut, { recursive: true });
|
|
66421
|
+
setPath = join51(ticketsOut, `issues-${result.date}.json`);
|
|
66107
66422
|
const payload = {
|
|
66108
66423
|
tool: "ultra11y",
|
|
66109
66424
|
kind: "issues",
|
|
@@ -66114,7 +66429,7 @@ async function cmdTickets(p) {
|
|
|
66114
66429
|
count: plan.tickets.length,
|
|
66115
66430
|
issues: plan.tickets
|
|
66116
66431
|
};
|
|
66117
|
-
|
|
66432
|
+
writeFileSync20(setPath, `${JSON.stringify(payload, null, 2)}
|
|
66118
66433
|
`);
|
|
66119
66434
|
if (!json) console.log(setPath);
|
|
66120
66435
|
}
|
|
@@ -66204,8 +66519,8 @@ async function cmdTickets(p) {
|
|
|
66204
66519
|
return pushed.failed > 0 ? 1 : 0;
|
|
66205
66520
|
}
|
|
66206
66521
|
function depsAt(root) {
|
|
66207
|
-
const pkgPath =
|
|
66208
|
-
if (!
|
|
66522
|
+
const pkgPath = join51(root, "package.json");
|
|
66523
|
+
if (!existsSync35(pkgPath)) return {};
|
|
66209
66524
|
try {
|
|
66210
66525
|
const pkg = JSON.parse(readText(pkgPath));
|
|
66211
66526
|
return { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
@@ -66231,24 +66546,24 @@ function cmdRender(p) {
|
|
|
66231
66546
|
console.error(`ultra11y render: --runner must be playwright|cypress|auto (got "${forced}").`);
|
|
66232
66547
|
return 2;
|
|
66233
66548
|
}
|
|
66234
|
-
const detected = detectE2eRunner(depsAt(root), (f) =>
|
|
66549
|
+
const detected = detectE2eRunner(depsAt(root), (f) => existsSync35(join51(root, f)));
|
|
66235
66550
|
const runners = forced && forced !== "auto" ? [forced] : detected;
|
|
66236
66551
|
if (!runners.length) {
|
|
66237
66552
|
console.error(e2eSetupPlan([], {}, lang));
|
|
66238
66553
|
return 1;
|
|
66239
66554
|
}
|
|
66240
66555
|
const engineRef = engineRefFor(root);
|
|
66241
|
-
const dir =
|
|
66556
|
+
const dir = join51(root, ".ultra11y", "e2e");
|
|
66242
66557
|
const paths = {};
|
|
66243
66558
|
try {
|
|
66244
|
-
|
|
66559
|
+
mkdirSync18(dir, { recursive: true });
|
|
66245
66560
|
if (runners.includes("playwright")) {
|
|
66246
|
-
|
|
66561
|
+
writeFileSync20(join51(dir, "playwright.mjs"), playwrightFixture(engineRef));
|
|
66247
66562
|
paths.playwright = ".ultra11y/e2e/playwright.mjs";
|
|
66248
66563
|
}
|
|
66249
66564
|
if (runners.includes("cypress")) {
|
|
66250
|
-
|
|
66251
|
-
|
|
66565
|
+
writeFileSync20(join51(dir, "cypress-plugin.mjs"), cypressPlugin(engineRef));
|
|
66566
|
+
writeFileSync20(join51(dir, "cypress-commands.mjs"), cypressCommands());
|
|
66252
66567
|
paths.cypressPlugin = ".ultra11y/e2e/cypress-plugin.mjs";
|
|
66253
66568
|
paths.cypressCommands = ".ultra11y/e2e/cypress-commands.mjs";
|
|
66254
66569
|
}
|
|
@@ -66263,7 +66578,7 @@ function cmdRender(p) {
|
|
|
66263
66578
|
if (p.flags.scaffold === true) {
|
|
66264
66579
|
const out2 = typeof p.flags.out === "string" && p.flags.out ? p.flags.out : "ultra11y-render.tsx";
|
|
66265
66580
|
try {
|
|
66266
|
-
|
|
66581
|
+
writeFileSync20(out2, ssrHarness());
|
|
66267
66582
|
} catch (e) {
|
|
66268
66583
|
console.error(`ultra11y render: could not write ${out2}: ${e instanceof Error ? e.message : String(e)}`);
|
|
66269
66584
|
return 1;
|
|
@@ -66277,29 +66592,29 @@ Fill in COMPONENTS, run it (e.g. npx tsx ${out2}), then: node scripts/ultra11y.m
|
|
|
66277
66592
|
}
|
|
66278
66593
|
if (p.flags.setup === true) {
|
|
66279
66594
|
const rel2 = ".ultra11y/capture-setup.mjs";
|
|
66280
|
-
const out2 =
|
|
66595
|
+
const out2 = join51(root, rel2);
|
|
66281
66596
|
try {
|
|
66282
|
-
|
|
66283
|
-
|
|
66597
|
+
mkdirSync18(dirname15(out2), { recursive: true });
|
|
66598
|
+
writeFileSync20(out2, captureSetup());
|
|
66284
66599
|
} catch (e) {
|
|
66285
66600
|
console.error(`ultra11y render: could not write ${out2}: ${e instanceof Error ? e.message : String(e)}`);
|
|
66286
66601
|
return 1;
|
|
66287
66602
|
}
|
|
66288
66603
|
let setupDeps = {};
|
|
66289
|
-
const setupPkg =
|
|
66290
|
-
if (
|
|
66604
|
+
const setupPkg = join51(root, "package.json");
|
|
66605
|
+
if (existsSync35(setupPkg)) {
|
|
66291
66606
|
try {
|
|
66292
66607
|
const pkg = JSON.parse(readText(setupPkg));
|
|
66293
66608
|
setupDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
66294
66609
|
} catch {
|
|
66295
66610
|
}
|
|
66296
66611
|
}
|
|
66297
|
-
const tr = detectTestRunner(setupDeps, (f) =>
|
|
66612
|
+
const tr = detectTestRunner(setupDeps, (f) => existsSync35(join51(root, f)));
|
|
66298
66613
|
console.log(captureSetupPlan(tr, rel2, lang));
|
|
66299
66614
|
const gaLine = ".ultra11y/captures/*.html text eol=lf linguist-generated=true";
|
|
66300
|
-
const gaPath =
|
|
66615
|
+
const gaPath = join51(root, ".gitattributes");
|
|
66301
66616
|
try {
|
|
66302
|
-
const existing =
|
|
66617
|
+
const existing = existsSync35(gaPath) ? readFileSync33(gaPath, "utf8") : "";
|
|
66303
66618
|
if (!existing.includes(".ultra11y/captures/")) {
|
|
66304
66619
|
appendFileSync(gaPath, (existing && !existing.endsWith("\n") ? "\n" : "") + gaLine + "\n");
|
|
66305
66620
|
console.log(lang === "fr" ? `.gitattributes : ajout\xE9 \xAB ${gaLine} \xBB` : `.gitattributes: added "${gaLine}"`);
|
|
@@ -66307,8 +66622,8 @@ Fill in COMPONENTS, run it (e.g. npx tsx ${out2}), then: node scripts/ultra11y.m
|
|
|
66307
66622
|
} catch {
|
|
66308
66623
|
}
|
|
66309
66624
|
try {
|
|
66310
|
-
const giPath =
|
|
66311
|
-
if (
|
|
66625
|
+
const giPath = join51(root, ".gitignore");
|
|
66626
|
+
if (existsSync35(giPath) && /^\s*\/?\.ultra11y(\/\**)?\/?\s*$/m.test(readFileSync33(giPath, "utf8")))
|
|
66312
66627
|
console.error(
|
|
66313
66628
|
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/").'
|
|
66314
66629
|
);
|
|
@@ -66318,8 +66633,8 @@ Fill in COMPONENTS, run it (e.g. npx tsx ${out2}), then: node scripts/ultra11y.m
|
|
|
66318
66633
|
}
|
|
66319
66634
|
if (p.flags.storybook === true || typeof p.flags.storybook === "string") {
|
|
66320
66635
|
const sbDir = p.positionals[0] ?? "storybook-static";
|
|
66321
|
-
const indexPath =
|
|
66322
|
-
if (!
|
|
66636
|
+
const indexPath = existsSync35(join51(sbDir, "index.json")) ? join51(sbDir, "index.json") : join51(sbDir, "stories.json");
|
|
66637
|
+
if (!existsSync35(indexPath)) {
|
|
66323
66638
|
console.error(
|
|
66324
66639
|
lang === "fr" ? `ultra11y render : aucun index Storybook (index.json/stories.json) dans ${sbDir}.` : `ultra11y render: no Storybook index (index.json/stories.json) in ${sbDir}.`
|
|
66325
66640
|
);
|
|
@@ -66329,7 +66644,7 @@ Fill in COMPONENTS, run it (e.g. npx tsx ${out2}), then: node scripts/ultra11y.m
|
|
|
66329
66644
|
const provById = new Map(stories.map((s) => [s.id, storyProvenance(s)]));
|
|
66330
66645
|
const capturesFlag = typeof p.flags.captures === "string" && p.flags.captures ? p.flags.captures : void 0;
|
|
66331
66646
|
const htmlDir = capturesFlag ?? sbDir;
|
|
66332
|
-
const htmlFiles =
|
|
66647
|
+
const htmlFiles = existsSync35(htmlDir) ? discover([htmlDir]).files.filter((f) => /\.html?$/i.test(f)) : [];
|
|
66333
66648
|
const outDir = ".ultra11y/captures";
|
|
66334
66649
|
let attributed = 0;
|
|
66335
66650
|
let skipped = 0;
|
|
@@ -66351,8 +66666,8 @@ Fill in COMPONENTS, run it (e.g. npx tsx ${out2}), then: node scripts/ultra11y.m
|
|
|
66351
66666
|
continue;
|
|
66352
66667
|
}
|
|
66353
66668
|
try {
|
|
66354
|
-
|
|
66355
|
-
|
|
66669
|
+
mkdirSync18(outDir, { recursive: true });
|
|
66670
|
+
writeFileSync20(join51(outDir, `${hitId}.html`), `${formatCaptureComment(prov)}
|
|
66356
66671
|
${raw}${raw.endsWith("\n") ? "" : "\n"}`);
|
|
66357
66672
|
attributed++;
|
|
66358
66673
|
} catch {
|
|
@@ -66374,11 +66689,11 @@ ${raw}${raw.endsWith("\n") ? "" : "\n"}`);
|
|
|
66374
66689
|
}
|
|
66375
66690
|
if (p.flags.coverage === true) {
|
|
66376
66691
|
const capturesFlag = typeof p.flags.captures === "string" && p.flags.captures ? p.flags.captures : void 0;
|
|
66377
|
-
const capturesDir = capturesFlag ??
|
|
66692
|
+
const capturesDir = capturesFlag ?? join51(root, ".ultra11y/captures");
|
|
66378
66693
|
const graphExt = [...GRAPH_ONLY_EXT, ...asList(p.flags.ext) ?? []];
|
|
66379
66694
|
const sourceFiles = discover([root], { include: asList(p.flags.include), exclude: asList(p.flags.exclude), ext: graphExt }).files;
|
|
66380
66695
|
const graph = buildGraphStreaming(sourceFiles);
|
|
66381
|
-
const capFiles =
|
|
66696
|
+
const capFiles = existsSync35(capturesDir) ? discover([capturesDir]).files : [];
|
|
66382
66697
|
const entries = capFiles.map((f) => ({ file: toPosix(f), provenance: parseCaptureProvenance(readText(f)) }));
|
|
66383
66698
|
const cov = computeCaptureCoverage(graph, entries);
|
|
66384
66699
|
if (p.flags.json) console.log(JSON.stringify(cov, null, 2));
|
|
@@ -66386,15 +66701,15 @@ ${raw}${raw.endsWith("\n") ? "" : "\n"}`);
|
|
|
66386
66701
|
return 0;
|
|
66387
66702
|
}
|
|
66388
66703
|
let deps = {};
|
|
66389
|
-
const pkgPath =
|
|
66390
|
-
if (
|
|
66704
|
+
const pkgPath = join51(root, "package.json");
|
|
66705
|
+
if (existsSync35(pkgPath)) {
|
|
66391
66706
|
try {
|
|
66392
66707
|
const pkg = JSON.parse(readText(pkgPath));
|
|
66393
66708
|
deps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
66394
66709
|
} catch {
|
|
66395
66710
|
}
|
|
66396
66711
|
}
|
|
66397
|
-
const detection = detectFrameworks(deps, (f) =>
|
|
66712
|
+
const detection = detectFrameworks(deps, (f) => existsSync35(join51(root, f)));
|
|
66398
66713
|
if (p.flags.json) console.log(JSON.stringify(detection, null, 2));
|
|
66399
66714
|
else console.log(renderPlan(detection, lang));
|
|
66400
66715
|
return 0;
|
|
@@ -66467,6 +66782,9 @@ function cmdVerify(p) {
|
|
|
66467
66782
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed) && parsed.kind === "adjudication") {
|
|
66468
66783
|
return applyAdjudicationFile(p, parsed, lang);
|
|
66469
66784
|
}
|
|
66785
|
+
if (isLedger(parsed)) {
|
|
66786
|
+
return replayLedgerFile(p, parsed, lang);
|
|
66787
|
+
}
|
|
66470
66788
|
if (!Array.isArray(parsed)) {
|
|
66471
66789
|
console.error("ultra11y verify: --apply must be a JSON array of verdicts, or an adjudication object.");
|
|
66472
66790
|
return 2;
|
|
@@ -66568,6 +66886,84 @@ function cmdVerify(p) {
|
|
|
66568
66886
|
);
|
|
66569
66887
|
return 0;
|
|
66570
66888
|
}
|
|
66889
|
+
function ledgerTarget(p, standard) {
|
|
66890
|
+
const flag = p.flags.ledger;
|
|
66891
|
+
if (flag === void 0 || flag === false) return void 0;
|
|
66892
|
+
return typeof flag === "string" && flag ? flag : ledgerPath(standard);
|
|
66893
|
+
}
|
|
66894
|
+
function replayLedgerFile(p, ledger, lang) {
|
|
66895
|
+
const inFlag = p.flags.in;
|
|
66896
|
+
if (typeof inFlag !== "string" || !inFlag) {
|
|
66897
|
+
console.error(
|
|
66898
|
+
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)."
|
|
66899
|
+
);
|
|
66900
|
+
return 2;
|
|
66901
|
+
}
|
|
66902
|
+
let audit2;
|
|
66903
|
+
try {
|
|
66904
|
+
audit2 = JSON.parse(readText(inFlag));
|
|
66905
|
+
} catch {
|
|
66906
|
+
console.error(`ultra11y verify: --in file not found or not valid JSON: ${inFlag}.`);
|
|
66907
|
+
return 2;
|
|
66908
|
+
}
|
|
66909
|
+
const cwd = typeof p.flags.cwd === "string" ? p.flags.cwd : void 0;
|
|
66910
|
+
const standard = resolveStandard(p.flags.standard) ?? ledger.standard;
|
|
66911
|
+
const rp = replayLedger(audit2, ledger, { cwd, standard });
|
|
66912
|
+
const r = applyAdjudication(audit2, rp.adj, { cwd, strict: p.flags.strict === true, residualReasons: rp.residualReasons });
|
|
66913
|
+
if (!r.ok && (p.flags.strict === true || r.applied + r.stillManual === 0)) {
|
|
66914
|
+
if (p.flags.json) console.log(JSON.stringify({ ...r, replay: rp }, null, 2));
|
|
66915
|
+
else {
|
|
66916
|
+
console.error(lang === "fr" ? `\u2717 Registre rejet\xE9 (${r.issues.length} probl\xE8me(s)) :` : `\u2717 Ledger rejected (${r.issues.length} issue(s)):`);
|
|
66917
|
+
for (const i2 of r.issues) console.error(` \u2717 ${i2}`);
|
|
66918
|
+
}
|
|
66919
|
+
return 1;
|
|
66920
|
+
}
|
|
66921
|
+
const out2 = typeof p.flags.out === "string" ? p.flags.out : ".";
|
|
66922
|
+
mkdirSync18(out2, { recursive: true });
|
|
66923
|
+
const auditPath = join51(out2, "audit-latest.json");
|
|
66924
|
+
writeFileSync20(auditPath, `${JSON.stringify(r.audit, null, 2)}
|
|
66925
|
+
`);
|
|
66926
|
+
if (p.flags.json)
|
|
66927
|
+
console.log(
|
|
66928
|
+
JSON.stringify(
|
|
66929
|
+
{
|
|
66930
|
+
ok: true,
|
|
66931
|
+
auditPath,
|
|
66932
|
+
applied: r.applied,
|
|
66933
|
+
stillManual: r.stillManual,
|
|
66934
|
+
rejected: r.rejected,
|
|
66935
|
+
rejectedCriteria: r.rejectedCriteria,
|
|
66936
|
+
replayed: rp.fresh.length,
|
|
66937
|
+
stale: rp.stale,
|
|
66938
|
+
obsolete: rp.obsolete,
|
|
66939
|
+
missing: rp.missing,
|
|
66940
|
+
issues: r.issues,
|
|
66941
|
+
conformancePct: r.audit.conformancePct,
|
|
66942
|
+
grounding: r.grounding
|
|
66943
|
+
},
|
|
66944
|
+
null,
|
|
66945
|
+
2
|
|
66946
|
+
)
|
|
66947
|
+
);
|
|
66948
|
+
else {
|
|
66949
|
+
console.log(
|
|
66950
|
+
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}`
|
|
66951
|
+
);
|
|
66952
|
+
if (rp.stale.length)
|
|
66953
|
+
console.error(
|
|
66954
|
+
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(", ")}`
|
|
66955
|
+
);
|
|
66956
|
+
if (rp.missing.length)
|
|
66957
|
+
console.error(
|
|
66958
|
+
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(", ")}`
|
|
66959
|
+
);
|
|
66960
|
+
if (rp.obsolete.length)
|
|
66961
|
+
console.error(
|
|
66962
|
+
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(", ")}`
|
|
66963
|
+
);
|
|
66964
|
+
}
|
|
66965
|
+
return 0;
|
|
66966
|
+
}
|
|
66571
66967
|
function applyAdjudicationFile(p, adj, lang) {
|
|
66572
66968
|
const inFlag = p.flags.in;
|
|
66573
66969
|
if (typeof inFlag !== "string" || !inFlag) {
|
|
@@ -66583,9 +66979,11 @@ function applyAdjudicationFile(p, adj, lang) {
|
|
|
66583
66979
|
console.error(`ultra11y verify: --in file not found or not valid JSON: ${inFlag}.`);
|
|
66584
66980
|
return 2;
|
|
66585
66981
|
}
|
|
66586
|
-
|
|
66587
|
-
|
|
66588
|
-
|
|
66982
|
+
const cwd = typeof p.flags.cwd === "string" ? p.flags.cwd : void 0;
|
|
66983
|
+
hydrateAdjudication(adj, audit2, { cwd });
|
|
66984
|
+
const strict = p.flags.strict === true;
|
|
66985
|
+
const r = applyAdjudication(audit2, adj, { cwd, strict });
|
|
66986
|
+
if (!r.ok && (strict || r.applied + r.stillManual === 0)) {
|
|
66589
66987
|
if (p.flags.json) console.log(JSON.stringify(r, null, 2));
|
|
66590
66988
|
else {
|
|
66591
66989
|
console.error(lang === "fr" ? `\u2717 Adjudication rejet\xE9e (${r.issues.length} probl\xE8me(s)) :` : `\u2717 Adjudication rejected (${r.issues.length} issue(s)):`);
|
|
@@ -66593,10 +66991,27 @@ function applyAdjudicationFile(p, adj, lang) {
|
|
|
66593
66991
|
}
|
|
66594
66992
|
return 1;
|
|
66595
66993
|
}
|
|
66994
|
+
if (r.rejected > 0 && !p.flags.json) {
|
|
66995
|
+
console.error(
|
|
66996
|
+
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:`
|
|
66997
|
+
);
|
|
66998
|
+
for (const i2 of r.issues) console.error(` \u2717 ${i2}`);
|
|
66999
|
+
}
|
|
66596
67000
|
const out2 = typeof p.flags.out === "string" ? p.flags.out : ".";
|
|
66597
|
-
|
|
66598
|
-
const auditPath =
|
|
66599
|
-
|
|
67001
|
+
mkdirSync18(out2, { recursive: true });
|
|
67002
|
+
const auditPath = join51(out2, "audit-latest.json");
|
|
67003
|
+
writeFileSync20(auditPath, JSON.stringify(r.audit, null, 2) + "\n");
|
|
67004
|
+
const ledgerOut = ledgerTarget(p, adj.standard);
|
|
67005
|
+
if (ledgerOut) {
|
|
67006
|
+
const refused = new Set(r.rejectedCriteria);
|
|
67007
|
+
const accepted = new Set(adj.items.map((it) => it.criteriaId).filter((id) => !refused.has(id)));
|
|
67008
|
+
const fresh = entriesFrom(adj, accepted, r.audit.date);
|
|
67009
|
+
writeLedger(ledgerOut, mergeLedger(readLedger(ledgerOut), adj.standard, fresh));
|
|
67010
|
+
if (!p.flags.json)
|
|
67011
|
+
console.log(
|
|
67012
|
+
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}`
|
|
67013
|
+
);
|
|
67014
|
+
}
|
|
66600
67015
|
if (p.flags.json)
|
|
66601
67016
|
console.log(
|
|
66602
67017
|
JSON.stringify(
|
|
@@ -66605,6 +67020,9 @@ function applyAdjudicationFile(p, adj, lang) {
|
|
|
66605
67020
|
auditPath,
|
|
66606
67021
|
applied: r.applied,
|
|
66607
67022
|
stillManual: r.stillManual,
|
|
67023
|
+
rejected: r.rejected,
|
|
67024
|
+
rejectedCriteria: r.rejectedCriteria,
|
|
67025
|
+
issues: r.issues,
|
|
66608
67026
|
conformancePct: r.audit.conformancePct,
|
|
66609
67027
|
findings: r.audit.findings.length,
|
|
66610
67028
|
grounding: r.grounding
|
|
@@ -66615,7 +67033,7 @@ function applyAdjudicationFile(p, adj, lang) {
|
|
|
66615
67033
|
);
|
|
66616
67034
|
else
|
|
66617
67035
|
console.log(
|
|
66618
|
-
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}`
|
|
67036
|
+
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}`
|
|
66619
67037
|
);
|
|
66620
67038
|
return 0;
|
|
66621
67039
|
}
|
|
@@ -66654,11 +67072,11 @@ async function cmdJudge(p) {
|
|
|
66654
67072
|
}
|
|
66655
67073
|
const max = typeof p.flags.max === "string" ? Number(p.flags.max) : void 0;
|
|
66656
67074
|
const truncated = max !== void 0 && Number.isFinite(max) && max > 0 && items.length > max;
|
|
66657
|
-
if (truncated && p.flags.apply === true) {
|
|
67075
|
+
if (truncated && p.flags.apply === true && p.flags.strict === true) {
|
|
66658
67076
|
console.error(
|
|
66659
|
-
lang === "fr" ? `ultra11y judge : --max ${max} ne couvre que ${max} des ${items.length} crit\xE8res, et --apply exige une adjudication COMPL\xC8TE (le
|
|
66660
|
-
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
|
|
66661
|
-
Drop --max to adjudicate everything, or drop --apply to produce the worklist and apply it later.`
|
|
67077
|
+
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).
|
|
67078
|
+
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).
|
|
67079
|
+
Drop --strict to apply what is covered, drop --max to adjudicate everything, or drop --apply to produce the worklist and apply it later.`
|
|
66662
67080
|
);
|
|
66663
67081
|
return 2;
|
|
66664
67082
|
}
|
|
@@ -66697,19 +67115,36 @@ async function cmdJudge(p) {
|
|
|
66697
67115
|
return 0;
|
|
66698
67116
|
}
|
|
66699
67117
|
const adj = JSON.parse(readText(w.todoPath));
|
|
66700
|
-
const r = applyAdjudication(audit2, adj);
|
|
66701
|
-
if (!r.ok) {
|
|
67118
|
+
const r = applyAdjudication(audit2, adj, { strict: p.flags.strict === true });
|
|
67119
|
+
if (!r.ok && (p.flags.strict === true || r.applied + r.stillManual === 0)) {
|
|
66702
67120
|
console.error(lang === "fr" ? `\u2717 Adjudication rejet\xE9e (${r.issues.length} probl\xE8me(s)) :` : `\u2717 Adjudication rejected (${r.issues.length} issue(s)):`);
|
|
66703
67121
|
for (const i2 of r.issues.slice(0, 40)) console.error(` \u2717 ${i2}`);
|
|
66704
67122
|
if (r.issues.length > 40) console.error(` \u2026 +${r.issues.length - 40}`);
|
|
66705
67123
|
return 1;
|
|
66706
67124
|
}
|
|
66707
|
-
|
|
66708
|
-
|
|
66709
|
-
|
|
67125
|
+
if (r.rejected > 0) {
|
|
67126
|
+
console.error(
|
|
67127
|
+
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:`
|
|
67128
|
+
);
|
|
67129
|
+
for (const i2 of r.issues.slice(0, 40)) console.error(` \u2717 ${i2}`);
|
|
67130
|
+
if (r.issues.length > 40) console.error(` \u2026 +${r.issues.length - 40}`);
|
|
67131
|
+
}
|
|
67132
|
+
mkdirSync18(out2, { recursive: true });
|
|
67133
|
+
const auditPath = join51(out2, "audit-latest.json");
|
|
67134
|
+
writeFileSync20(auditPath, `${JSON.stringify(r.audit, null, 2)}
|
|
66710
67135
|
`);
|
|
67136
|
+
const ledgerOut = ledgerTarget(p, adj.standard);
|
|
67137
|
+
if (ledgerOut) {
|
|
67138
|
+
const refused = new Set(r.rejectedCriteria);
|
|
67139
|
+
const accepted = new Set(adj.items.map((it) => it.criteriaId).filter((id) => !refused.has(id)));
|
|
67140
|
+
const fresh = entriesFrom(adj, accepted, r.audit.date);
|
|
67141
|
+
writeLedger(ledgerOut, mergeLedger(readLedger(ledgerOut), adj.standard, fresh));
|
|
67142
|
+
console.log(
|
|
67143
|
+
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}`
|
|
67144
|
+
);
|
|
67145
|
+
}
|
|
66711
67146
|
console.log(
|
|
66712
|
-
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}`
|
|
67147
|
+
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}`
|
|
66713
67148
|
);
|
|
66714
67149
|
return 0;
|
|
66715
67150
|
}
|
|
@@ -66775,7 +67210,7 @@ async function cmdScan(p) {
|
|
|
66775
67210
|
return 0;
|
|
66776
67211
|
}
|
|
66777
67212
|
for (const target of p.positionals.filter((a) => a !== "-")) {
|
|
66778
|
-
if (/^https?:\/\//i.test(target) ||
|
|
67213
|
+
if (/^https?:\/\//i.test(target) || existsSync35(target)) continue;
|
|
66779
67214
|
console.error(
|
|
66780
67215
|
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.`
|
|
66781
67216
|
);
|
|
@@ -66917,7 +67352,7 @@ async function cmdScan(p) {
|
|
|
66917
67352
|
}
|
|
66918
67353
|
let merged = mergeDynamic(audit2, dynamic, lang);
|
|
66919
67354
|
if (dynamic.snapshots?.length) {
|
|
66920
|
-
const doms = dynamic.snapshots.map((id) =>
|
|
67355
|
+
const doms = dynamic.snapshots.map((id) => join51(snapshotRoot ?? ".", PAGES_DIR, id, "dom.html")).filter((f) => existsSync35(f));
|
|
66921
67356
|
if (doms.length) {
|
|
66922
67357
|
const snapAudit = runAudit({ inputs: doms, onWarn: (m) => console.error(m) });
|
|
66923
67358
|
merged = mergeSnapshotAudit(merged, snapAudit);
|
|
@@ -66928,12 +67363,12 @@ async function cmdScan(p) {
|
|
|
66928
67363
|
attributePages(merged, scope);
|
|
66929
67364
|
}
|
|
66930
67365
|
}
|
|
66931
|
-
|
|
66932
|
-
|
|
67366
|
+
mkdirSync18(out2, { recursive: true });
|
|
67367
|
+
writeFileSync20(join51(out2, "audit-latest.json"), JSON.stringify(merged, null, 2) + "\n");
|
|
66933
67368
|
if (p.flags.json) console.log(JSON.stringify(merged, null, 2));
|
|
66934
67369
|
else {
|
|
66935
67370
|
console.log(
|
|
66936
|
-
lang === "fr" ? `Audit statique + dynamique fusionn\xE9 \u2192 ${
|
|
67371
|
+
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).`
|
|
66937
67372
|
);
|
|
66938
67373
|
if (dynamic.snapshots?.length)
|
|
66939
67374
|
console.log(
|
|
@@ -67175,9 +67610,9 @@ async function cmdImport(p) {
|
|
|
67175
67610
|
return 1;
|
|
67176
67611
|
}
|
|
67177
67612
|
if (outDir) {
|
|
67178
|
-
|
|
67179
|
-
const rawFile =
|
|
67180
|
-
|
|
67613
|
+
mkdirSync18(outDir, { recursive: true });
|
|
67614
|
+
const rawFile = join51(outDir, `external-${adapterId}-${arg}.raw.json`);
|
|
67615
|
+
writeFileSync20(rawFile, rawText.endsWith("\n") ? rawText : `${rawText}
|
|
67181
67616
|
`);
|
|
67182
67617
|
if (!p.flags.json) console.log(rawFile);
|
|
67183
67618
|
}
|
|
@@ -67211,9 +67646,9 @@ async function cmdImport(p) {
|
|
|
67211
67646
|
return 0;
|
|
67212
67647
|
}
|
|
67213
67648
|
if (outDir) {
|
|
67214
|
-
|
|
67215
|
-
const file =
|
|
67216
|
-
|
|
67649
|
+
mkdirSync18(outDir, { recursive: true });
|
|
67650
|
+
const file = join51(outDir, "external-latest.json");
|
|
67651
|
+
writeFileSync20(file, `${JSON.stringify(audit2, null, 2)}
|
|
67217
67652
|
`);
|
|
67218
67653
|
console.log(file);
|
|
67219
67654
|
} else {
|
|
@@ -67318,7 +67753,7 @@ function cmdOrchestrate(p) {
|
|
|
67318
67753
|
}
|
|
67319
67754
|
const engineAbs = realpathSync6(fileURLToPath5(import.meta.url));
|
|
67320
67755
|
if (p.flags.list === true) {
|
|
67321
|
-
if (!
|
|
67756
|
+
if (!existsSync35(runFlag)) {
|
|
67322
67757
|
console.error(`ultra11y orchestrate: run dir not found: ${runFlag}.`);
|
|
67323
67758
|
return 2;
|
|
67324
67759
|
}
|
|
@@ -67345,7 +67780,7 @@ function cmdOrchestrate(p) {
|
|
|
67345
67780
|
);
|
|
67346
67781
|
} else {
|
|
67347
67782
|
console.log(
|
|
67348
|
-
lang === "fr" ? `Suivez ${
|
|
67783
|
+
lang === "fr" ? `Suivez ${join51(runFlag, "orchestration", "RUNBOOK.md")} s\xE9quentiellement (chemin \xE9co).` : `Follow ${join51(runFlag, "orchestration", "RUNBOOK.md")} sequentially (the eco path).`
|
|
67349
67784
|
);
|
|
67350
67785
|
}
|
|
67351
67786
|
if (p.flags.phase === void 0 && workflows.length === 0 && p.flags.eco !== true) {
|
|
@@ -67400,7 +67835,7 @@ async function main(argv) {
|
|
|
67400
67835
|
if (typeof v === "string" && v && !allowed.includes(v)) console.error(`ultra11y: --${flag} "${v}" is not one of ${allowed.join("|")} \u2014 using the default.`);
|
|
67401
67836
|
}
|
|
67402
67837
|
const packList = typeof p.flags.pack === "string" ? p.flags.pack.split(",").map((s) => s.trim()).filter(Boolean) : [];
|
|
67403
|
-
const configRoot = p.command === "mcp" && typeof p.flags.cwd === "string" && p.flags.cwd ?
|
|
67838
|
+
const configRoot = p.command === "mcp" && typeof p.flags.cwd === "string" && p.flags.cwd ? resolve16(p.flags.cwd) : process.cwd();
|
|
67404
67839
|
const loaded2 = loadRuntimeStandards(configRoot, packList, (m) => console.error(m), p.flags.override === true);
|
|
67405
67840
|
if (loaded2.errors.length) {
|
|
67406
67841
|
for (const e of loaded2.errors) console.error(`ultra11y: ${e}`);
|