@hawkeyexl/manni 4.3.1 → 4.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (225) hide show
  1. package/README.md +9 -1
  2. package/dist/batch-GFNLTGOH.js +36 -0
  3. package/dist/build-COYIQ25L.js +31 -0
  4. package/dist/calibrate-MDBLJKEE.js +31 -0
  5. package/dist/capture-LRML2PKV.js +32 -0
  6. package/dist/check-ASPIHA6B.js +40 -0
  7. package/dist/check-ASPIHA6B.js.map +1 -0
  8. package/dist/check-EDLBYL5N.js +27 -0
  9. package/dist/check-EDLBYL5N.js.map +1 -0
  10. package/dist/check-HYNGXYJV.js +18 -0
  11. package/dist/check-HYNGXYJV.js.map +1 -0
  12. package/dist/chunk-2B7ZNTPV.js +230 -0
  13. package/dist/chunk-2B7ZNTPV.js.map +1 -0
  14. package/dist/chunk-2H7UOFLK.js +11 -0
  15. package/dist/chunk-2H7UOFLK.js.map +1 -0
  16. package/dist/chunk-37LP5HMZ.js +1042 -0
  17. package/dist/chunk-37LP5HMZ.js.map +1 -0
  18. package/dist/chunk-3VK37OR2.js +2957 -0
  19. package/dist/chunk-3VK37OR2.js.map +1 -0
  20. package/dist/chunk-4IUC2MNL.js +2165 -0
  21. package/dist/chunk-4IUC2MNL.js.map +1 -0
  22. package/dist/{chunk-3R23C5DT.js → chunk-4TSWU2SU.js} +2 -14
  23. package/dist/chunk-4TSWU2SU.js.map +1 -0
  24. package/dist/chunk-57NEQRVO.js +10921 -0
  25. package/dist/chunk-57NEQRVO.js.map +1 -0
  26. package/dist/chunk-6JSSEWBG.js +6737 -0
  27. package/dist/chunk-6JSSEWBG.js.map +1 -0
  28. package/dist/chunk-6OYEI2VV.js +88 -0
  29. package/dist/chunk-6OYEI2VV.js.map +1 -0
  30. package/dist/chunk-77AINBYM.js +19 -0
  31. package/dist/chunk-77AINBYM.js.map +1 -0
  32. package/dist/chunk-7UKO3ASZ.js +54 -0
  33. package/dist/chunk-7UKO3ASZ.js.map +1 -0
  34. package/dist/chunk-A5ULMVHQ.js +164 -0
  35. package/dist/chunk-A5ULMVHQ.js.map +1 -0
  36. package/dist/chunk-C4SQM22E.js +299 -0
  37. package/dist/chunk-C4SQM22E.js.map +1 -0
  38. package/dist/chunk-CK7SKVHR.js +1042 -0
  39. package/dist/chunk-CK7SKVHR.js.map +1 -0
  40. package/dist/{chunk-G5CM2VGQ.js → chunk-DXNZS72W.js} +8 -94
  41. package/dist/chunk-DXNZS72W.js.map +1 -0
  42. package/dist/chunk-EPH54CAY.js +374 -0
  43. package/dist/chunk-EPH54CAY.js.map +1 -0
  44. package/dist/{chunk-KJR7OVDX.js → chunk-FBEQCK47.js} +56 -137
  45. package/dist/chunk-FBEQCK47.js.map +1 -0
  46. package/dist/{chunk-IOYXPBCL.js → chunk-G3T47CFV.js} +1 -7
  47. package/dist/{chunk-IOYXPBCL.js.map → chunk-G3T47CFV.js.map} +1 -1
  48. package/dist/chunk-GCQRPUSB.js +1287 -0
  49. package/dist/chunk-GCQRPUSB.js.map +1 -0
  50. package/dist/chunk-GLP5PSXB.js +72 -0
  51. package/dist/chunk-GLP5PSXB.js.map +1 -0
  52. package/dist/chunk-I2AH3CJJ.js +1192 -0
  53. package/dist/chunk-I2AH3CJJ.js.map +1 -0
  54. package/dist/chunk-IDV6HJCB.js +388 -0
  55. package/dist/chunk-IDV6HJCB.js.map +1 -0
  56. package/dist/chunk-IKEWBMCN.js +98 -0
  57. package/dist/chunk-IKEWBMCN.js.map +1 -0
  58. package/dist/chunk-JCDXE76J.js +52 -0
  59. package/dist/chunk-JCDXE76J.js.map +1 -0
  60. package/dist/chunk-JXLTXMJ7.js +455 -0
  61. package/dist/chunk-JXLTXMJ7.js.map +1 -0
  62. package/dist/chunk-LJ2RDH3F.js +49 -0
  63. package/dist/chunk-LJ2RDH3F.js.map +1 -0
  64. package/dist/chunk-M2JCK5F6.js +805 -0
  65. package/dist/chunk-M2JCK5F6.js.map +1 -0
  66. package/dist/chunk-M3W7J6MU.js +4398 -0
  67. package/dist/chunk-M3W7J6MU.js.map +1 -0
  68. package/dist/chunk-MMTSSTG7.js +23 -0
  69. package/dist/chunk-MMTSSTG7.js.map +1 -0
  70. package/dist/chunk-MNH7QO4Z.js +15 -0
  71. package/dist/chunk-MNH7QO4Z.js.map +1 -0
  72. package/dist/chunk-MWJLZMBL.js +934 -0
  73. package/dist/chunk-MWJLZMBL.js.map +1 -0
  74. package/dist/chunk-MYMBCTDE.js +387 -0
  75. package/dist/chunk-MYMBCTDE.js.map +1 -0
  76. package/dist/chunk-NCPWXHDO.js +683 -0
  77. package/dist/chunk-NCPWXHDO.js.map +1 -0
  78. package/dist/chunk-NLXTXE5Y.js +1661 -0
  79. package/dist/chunk-NLXTXE5Y.js.map +1 -0
  80. package/dist/{chunk-CFDUZRM2.js → chunk-NXV7WE2D.js} +32 -240
  81. package/dist/chunk-NXV7WE2D.js.map +1 -0
  82. package/dist/chunk-OWBRDNRZ.js +16 -0
  83. package/dist/chunk-OWBRDNRZ.js.map +1 -0
  84. package/dist/{chunk-KTSJ2ZHA.js → chunk-PAU352SV.js} +5 -3
  85. package/dist/{chunk-KTSJ2ZHA.js.map → chunk-PAU352SV.js.map} +1 -1
  86. package/dist/chunk-PCE7QHXJ.js +68 -0
  87. package/dist/chunk-PCE7QHXJ.js.map +1 -0
  88. package/dist/chunk-PIKOVN6W.js +1651 -0
  89. package/dist/chunk-PIKOVN6W.js.map +1 -0
  90. package/dist/chunk-PNPQTHBT.js +17 -0
  91. package/dist/chunk-PNPQTHBT.js.map +1 -0
  92. package/dist/chunk-PU6DFJMY.js +1975 -0
  93. package/dist/chunk-PU6DFJMY.js.map +1 -0
  94. package/dist/chunk-Q35YIDBV.js +1202 -0
  95. package/dist/chunk-Q35YIDBV.js.map +1 -0
  96. package/dist/{chunk-5HNHEUD5.js → chunk-QQUKGCAJ.js} +3 -3
  97. package/dist/chunk-QRWGPQKO.js +198 -0
  98. package/dist/chunk-QRWGPQKO.js.map +1 -0
  99. package/dist/{chunk-UBMMXS2Y.js → chunk-QZ6LRSNV.js} +69 -48
  100. package/dist/{chunk-UBMMXS2Y.js.map → chunk-QZ6LRSNV.js.map} +1 -1
  101. package/dist/chunk-RL5XWQOL.js +3872 -0
  102. package/dist/chunk-RL5XWQOL.js.map +1 -0
  103. package/dist/chunk-RNVYGWIC.js +30 -0
  104. package/dist/chunk-RNVYGWIC.js.map +1 -0
  105. package/dist/chunk-SE4JN5XN.js +19 -0
  106. package/dist/chunk-SE4JN5XN.js.map +1 -0
  107. package/dist/chunk-SKSDRUUK.js +622 -0
  108. package/dist/chunk-SKSDRUUK.js.map +1 -0
  109. package/dist/chunk-TIUTIOFB.js +25 -0
  110. package/dist/chunk-TIUTIOFB.js.map +1 -0
  111. package/dist/chunk-TK3FC4RF.js +3090 -0
  112. package/dist/chunk-TK3FC4RF.js.map +1 -0
  113. package/dist/chunk-TU2WUBVZ.js +2 -0
  114. package/dist/chunk-TU2WUBVZ.js.map +1 -0
  115. package/dist/chunk-TZOYZK2E.js +98 -0
  116. package/dist/chunk-TZOYZK2E.js.map +1 -0
  117. package/dist/chunk-UJCVAWP7.js +178 -0
  118. package/dist/chunk-UJCVAWP7.js.map +1 -0
  119. package/dist/chunk-UKZNLVJW.js +17 -0
  120. package/dist/chunk-UKZNLVJW.js.map +1 -0
  121. package/dist/{chunk-HAPTXWU2.js → chunk-UQISLDAV.js} +4 -4
  122. package/dist/chunk-WB3QM6PP.js +5440 -0
  123. package/dist/chunk-WB3QM6PP.js.map +1 -0
  124. package/dist/chunk-YB4IBW3M.js +96 -0
  125. package/dist/chunk-YB4IBW3M.js.map +1 -0
  126. package/dist/chunk-YEUYKR62.js +72 -0
  127. package/dist/chunk-YEUYKR62.js.map +1 -0
  128. package/dist/chunk-YJOGUXGP.js +634 -0
  129. package/dist/chunk-YJOGUXGP.js.map +1 -0
  130. package/dist/chunk-YNHZHSSM.js +41 -0
  131. package/dist/chunk-YNHZHSSM.js.map +1 -0
  132. package/dist/chunk-YTURO7SI.js +979 -0
  133. package/dist/chunk-YTURO7SI.js.map +1 -0
  134. package/dist/chunk-ZXRAE55H.js +31 -0
  135. package/dist/chunk-ZXRAE55H.js.map +1 -0
  136. package/dist/cli-3BIBYHFK.js +473 -0
  137. package/dist/cli-3BIBYHFK.js.map +1 -0
  138. package/dist/cli-673OTRGE.js +28 -0
  139. package/dist/cli-673OTRGE.js.map +1 -0
  140. package/dist/cli-6HIDIFVT.js +1203 -0
  141. package/dist/cli-6HIDIFVT.js.map +1 -0
  142. package/dist/cli-6UB5N6CU.js +232 -0
  143. package/dist/cli-6UB5N6CU.js.map +1 -0
  144. package/dist/cli-LFP5ZGYS.js +346 -0
  145. package/dist/cli-LFP5ZGYS.js.map +1 -0
  146. package/dist/cli-MN7PYMBI.js +552 -0
  147. package/dist/cli-MN7PYMBI.js.map +1 -0
  148. package/dist/cli-PYPX6O6E.js +739 -0
  149. package/dist/cli-PYPX6O6E.js.map +1 -0
  150. package/dist/cli-RPM5PELX.js +626 -0
  151. package/dist/cli-RPM5PELX.js.map +1 -0
  152. package/dist/cli-VI7VHWNZ.js +423 -0
  153. package/dist/cli-VI7VHWNZ.js.map +1 -0
  154. package/dist/cli.d.ts +12 -2
  155. package/dist/cli.js +109 -7661
  156. package/dist/cli.js.map +1 -1
  157. package/dist/docmeta.js +15 -3
  158. package/dist/docmeta.js.map +1 -1
  159. package/dist/github-6QMYOHC3.js +27 -0
  160. package/dist/github-6QMYOHC3.js.map +1 -0
  161. package/dist/github-SXPGP5DB.js +32 -0
  162. package/dist/github-SXPGP5DB.js.map +1 -0
  163. package/dist/index.d.ts +65 -3
  164. package/dist/index.js +407 -337
  165. package/dist/index.js.map +1 -1
  166. package/dist/json-7WV7KL6H.js +17 -0
  167. package/dist/json-7WV7KL6H.js.map +1 -0
  168. package/dist/json-SY7KLNC7.js +35 -0
  169. package/dist/json-SY7KLNC7.js.map +1 -0
  170. package/dist/lint-ASMJLLPA.js +28 -0
  171. package/dist/lint-ASMJLLPA.js.map +1 -0
  172. package/dist/list-KZNVTNVC.js +31 -0
  173. package/dist/list-KZNVTNVC.js.map +1 -0
  174. package/dist/list-Q57YTFUQ.js +29 -0
  175. package/dist/list-Q57YTFUQ.js.map +1 -0
  176. package/dist/page-KSGV5RD4.js +41 -0
  177. package/dist/page-KSGV5RD4.js.map +1 -0
  178. package/dist/picker-3XF36MJF.js +14 -0
  179. package/dist/picker-3XF36MJF.js.map +1 -0
  180. package/dist/pretty-A5EGTN35.js +37 -0
  181. package/dist/pretty-A5EGTN35.js.map +1 -0
  182. package/dist/pretty-CXSDXNAS.js +43 -0
  183. package/dist/pretty-CXSDXNAS.js.map +1 -0
  184. package/dist/reporters-HUY7UXFS.js +42 -0
  185. package/dist/reporters-HUY7UXFS.js.map +1 -0
  186. package/dist/reporters-K2VUKSAK.js +71 -0
  187. package/dist/reporters-K2VUKSAK.js.map +1 -0
  188. package/dist/reporters-M6LJ2IYU.js +21 -0
  189. package/dist/reporters-M6LJ2IYU.js.map +1 -0
  190. package/dist/run-3DYJNRIG.js +33 -0
  191. package/dist/run-3DYJNRIG.js.map +1 -0
  192. package/dist/term-BIQUPMOR.js +1592 -0
  193. package/dist/term-BIQUPMOR.js.map +1 -0
  194. package/dist/types-Y6TXHM7M.js +10 -0
  195. package/dist/types-Y6TXHM7M.js.map +1 -0
  196. package/dist/validate-53RKM2XG.js +16 -0
  197. package/dist/validate-53RKM2XG.js.map +1 -0
  198. package/package.json +1 -1
  199. package/plugin/manni/.claude-plugin/plugin.json +9 -0
  200. package/plugin/manni/hooks/hooks.json +69 -0
  201. package/plugin/manni/hooks/manni.mjs +157 -0
  202. package/plugin/manni/skills/check/SKILL.md +20 -0
  203. package/plugin/manni/skills/evals/SKILL.md +27 -0
  204. package/plugin/manni/skills/fix/SKILL.md +56 -0
  205. package/plugin/manni/skills/setup/SKILL.md +41 -0
  206. package/dist/batch-A5HZKYIW.js +0 -18
  207. package/dist/calibrate-S5K6QHUY.js +0 -13
  208. package/dist/capture-OZEZDABQ.js +0 -15
  209. package/dist/chunk-3R23C5DT.js.map +0 -1
  210. package/dist/chunk-CFDUZRM2.js.map +0 -1
  211. package/dist/chunk-G5CM2VGQ.js.map +0 -1
  212. package/dist/chunk-KJR7OVDX.js.map +0 -1
  213. package/dist/chunk-V4KUVE47.js +0 -27170
  214. package/dist/chunk-V4KUVE47.js.map +0 -1
  215. package/dist/chunk-ZQBTK3DO.js +0 -26518
  216. package/dist/chunk-ZQBTK3DO.js.map +0 -1
  217. package/dist/picker-6HRMETE7.js +0 -12
  218. package/plugin/tracevals/.claude-plugin/plugin.json +0 -9
  219. package/plugin/tracevals/hooks/hooks.json +0 -17
  220. /package/dist/{batch-A5HZKYIW.js.map → batch-GFNLTGOH.js.map} +0 -0
  221. /package/dist/{calibrate-S5K6QHUY.js.map → build-COYIQ25L.js.map} +0 -0
  222. /package/dist/{capture-OZEZDABQ.js.map → calibrate-MDBLJKEE.js.map} +0 -0
  223. /package/dist/{picker-6HRMETE7.js.map → capture-LRML2PKV.js.map} +0 -0
  224. /package/dist/{chunk-5HNHEUD5.js.map → chunk-QQUKGCAJ.js.map} +0 -0
  225. /package/dist/{chunk-HAPTXWU2.js.map → chunk-UQISLDAV.js.map} +0 -0
@@ -0,0 +1,1203 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ SEVERITIES,
4
+ SEVERITY_LIST,
5
+ isSeverity,
6
+ meetsSeverity
7
+ } from "./chunk-77AINBYM.js";
8
+ import {
9
+ fail
10
+ } from "./chunk-YEUYKR62.js";
11
+ import {
12
+ palette,
13
+ shouldColor
14
+ } from "./chunk-RNVYGWIC.js";
15
+ import {
16
+ escapeWorkflowCommandMessage,
17
+ escapeWorkflowCommandProperty
18
+ } from "./chunk-MNH7QO4Z.js";
19
+ import {
20
+ FAMILY_CONFIG_NAMES,
21
+ findConfigFile,
22
+ matchesFileGlob,
23
+ readConfigFile,
24
+ selectCollections
25
+ } from "./chunk-Q35YIDBV.js";
26
+ import {
27
+ package_default
28
+ } from "./chunk-UJCVAWP7.js";
29
+ import {
30
+ programName
31
+ } from "./chunk-OWBRDNRZ.js";
32
+ import {
33
+ ToolError
34
+ } from "./chunk-G3T47CFV.js";
35
+ import "./chunk-2H7UOFLK.js";
36
+
37
+ // src/a11y/cli.ts
38
+ import { Command } from "commander";
39
+
40
+ // src/a11y/types.ts
41
+ var A11yError = class extends ToolError {
42
+ constructor(message) {
43
+ super(message);
44
+ this.name = "A11yError";
45
+ }
46
+ };
47
+ var AXE_IMPACTS = ["minor", "moderate", "serious", "critical"];
48
+ function isAxeImpact(value) {
49
+ return AXE_IMPACTS.includes(value);
50
+ }
51
+
52
+ // src/a11y/core/url.ts
53
+ function normalizeUrl(url) {
54
+ const parsed = parse(url);
55
+ if (parsed === null) throw new A11yError(`Not a valid URL: "${url}".`);
56
+ parsed.hash = "";
57
+ return parsed.href;
58
+ }
59
+ function dedupeKey(url) {
60
+ const parsed = new URL(normalizeUrl(url));
61
+ if (parsed.pathname.length > 1 && parsed.pathname.endsWith("/")) {
62
+ parsed.pathname = parsed.pathname.slice(0, -1);
63
+ }
64
+ return parsed.href;
65
+ }
66
+ function isHttpUrl(url) {
67
+ const parsed = parse(url);
68
+ return parsed !== null && isHttpProtocol(parsed);
69
+ }
70
+ function sameHost(a, b) {
71
+ const left = parse(a);
72
+ const right = parse(b);
73
+ if (left === null || right === null) return false;
74
+ return left.hostname.toLowerCase() === right.hostname.toLowerCase() && left.port === right.port;
75
+ }
76
+ function isPageLink(url) {
77
+ const parsed = parse(url);
78
+ if (parsed === null || !isHttpProtocol(parsed)) return false;
79
+ const last = parsed.pathname.slice(parsed.pathname.lastIndexOf("/") + 1);
80
+ const dot = last.lastIndexOf(".");
81
+ if (dot === -1) return true;
82
+ return !ASSET_EXTENSIONS.has(last.slice(dot).toLowerCase());
83
+ }
84
+ var ASSET_EXTENSIONS = /* @__PURE__ */ new Set([
85
+ ".pdf",
86
+ ".zip",
87
+ ".gz",
88
+ ".png",
89
+ ".jpg",
90
+ ".jpeg",
91
+ ".gif",
92
+ ".svg",
93
+ ".webp",
94
+ ".avif",
95
+ ".ico",
96
+ ".css",
97
+ ".js",
98
+ ".mjs",
99
+ ".map",
100
+ ".json",
101
+ ".xml",
102
+ ".txt",
103
+ ".mp3",
104
+ ".mp4",
105
+ ".webm",
106
+ ".woff",
107
+ ".woff2",
108
+ ".ttf"
109
+ ]);
110
+ function parse(url) {
111
+ try {
112
+ return new URL(url);
113
+ } catch {
114
+ return null;
115
+ }
116
+ }
117
+ function isHttpProtocol(url) {
118
+ return url.protocol === "http:" || url.protocol === "https:";
119
+ }
120
+
121
+ // src/a11y/core/crawl.ts
122
+ async function crawl(opts, analyzer) {
123
+ const frontier = [];
124
+ const seen = /* @__PURE__ */ new Set();
125
+ const visited = /* @__PURE__ */ new Set();
126
+ const excluded = /* @__PURE__ */ new Set();
127
+ const progress = opts.onProgress ?? (() => void 0);
128
+ const enqueue = (raw, source) => {
129
+ let url;
130
+ let key;
131
+ try {
132
+ url = normalizeUrl(raw);
133
+ key = dedupeKey(url);
134
+ } catch {
135
+ return { as: "invalid" };
136
+ }
137
+ if (source !== "seed" && !isPageLink(url)) return { as: "asset" };
138
+ if (!opts.seeds.some((seed) => sameHost(seed, url))) return { as: "off-host" };
139
+ const pattern = opts.exclude?.match(url) ?? null;
140
+ if (pattern !== null) {
141
+ excluded.add(key);
142
+ return { as: "excluded", pattern };
143
+ }
144
+ if (seen.has(key)) return { as: "seen" };
145
+ seen.add(key);
146
+ frontier.push({ url, key, source });
147
+ return { as: "queued" };
148
+ };
149
+ const landed = (finalUrl) => {
150
+ if (finalUrl === void 0) return;
151
+ try {
152
+ const key = dedupeKey(finalUrl);
153
+ seen.add(key);
154
+ visited.add(key);
155
+ } catch {
156
+ }
157
+ };
158
+ for (const seed of opts.seeds) enqueue(seed, "seed");
159
+ if (opts.crawl) for (const url of opts.extra) enqueue(url, "sitemap");
160
+ if (excluded.size > 0) {
161
+ progress({ kind: "excluded", urls: excluded.size, patterns: opts.exclude?.patterns.length ?? 0 });
162
+ }
163
+ const pages = [];
164
+ const redirects = [];
165
+ let duplicates = 0;
166
+ const capped = () => opts.maxPages !== void 0 && pages.length >= opts.maxPages;
167
+ for (let next = 0; next < frontier.length && !capped(); next++) {
168
+ const candidate = frontier[next];
169
+ if (candidate === void 0) break;
170
+ const { url, key, source } = candidate;
171
+ if (visited.has(key)) {
172
+ duplicates += 1;
173
+ continue;
174
+ }
175
+ visited.add(key);
176
+ const index = pages.length + 1;
177
+ if (pages.length + redirects.length === 0) progress({ kind: "browser" });
178
+ progress({ kind: "page", index, queued: frontier.length, url });
179
+ try {
180
+ const analyzed = await analyzer.analyze(url, opts.analyze);
181
+ if ("redirect" in analyzed) {
182
+ const to = normalized(analyzed.redirect);
183
+ redirects.push({ url, to, source });
184
+ progress({ kind: "redirected", index, url, to });
185
+ const outcome2 = enqueue(to, source);
186
+ if (source === "seed") {
187
+ if (!isHttpUrl(to)) {
188
+ throw new A11yError(`${url} redirects to ${to}, which is not an http(s) URL.`);
189
+ }
190
+ if (outcome2.as === "off-host") {
191
+ throw new A11yError(`${url} redirects to ${to}, which is on another host. Check ${to} instead.`);
192
+ }
193
+ if (outcome2.as === "excluded") {
194
+ throw new A11yError(`${outcome2.pattern.source} excludes ${to}, where the seed ${url} redirects.`);
195
+ }
196
+ }
197
+ continue;
198
+ }
199
+ pages.push({ ...analyzed.result, url, source });
200
+ landed(analyzed.finalUrl);
201
+ if (opts.crawl) for (const link of analyzed.links) enqueue(link, "link");
202
+ progress({ kind: "checked", index, url, violations: analyzed.result.violations.length });
203
+ } catch (err) {
204
+ if (source === "seed") throw err;
205
+ const error = err instanceof Error ? err.message : String(err);
206
+ pages.push({ url, source, violations: [], passes: 0, incomplete: 0, error });
207
+ progress({ kind: "checked", index, url, violations: 0, error });
208
+ }
209
+ }
210
+ const skipped = frontier.length - pages.length - duplicates - redirects.length;
211
+ progress({ kind: "done", checked: pages.length, skipped });
212
+ return {
213
+ pages,
214
+ discovered: frontier.length,
215
+ skipped,
216
+ duplicates,
217
+ redirects,
218
+ excluded: excluded.size
219
+ };
220
+ }
221
+ function normalized(url) {
222
+ try {
223
+ return normalizeUrl(url);
224
+ } catch {
225
+ return url;
226
+ }
227
+ }
228
+
229
+ // src/a11y/core/exclude.ts
230
+ var MUST_START_WITH_SLASH = 'must start with "/": it matches a URL path.';
231
+ function isUrlPathGlob(pattern) {
232
+ return pattern.startsWith("/");
233
+ }
234
+ function excludePath(url) {
235
+ let parsed;
236
+ try {
237
+ parsed = new URL(url);
238
+ } catch {
239
+ return null;
240
+ }
241
+ const path = parsed.pathname;
242
+ return path.length > 1 && path.endsWith("/") ? path.slice(0, -1) : path;
243
+ }
244
+ function createExcludeFilter(patterns) {
245
+ const list = [...patterns];
246
+ const match = (url) => {
247
+ if (list.length === 0) return null;
248
+ const path = excludePath(url);
249
+ if (path === null) return null;
250
+ return list.find((pattern) => matchesFileGlob(path, pattern.glob)) ?? null;
251
+ };
252
+ return {
253
+ patterns: list,
254
+ match,
255
+ matches: (url) => match(url) !== null
256
+ };
257
+ }
258
+
259
+ // src/a11y/core/seeds.ts
260
+ var NO_SEEDS_MESSAGE = "No URLs to check. Pass one or more, set url: on a collection, or set a11y.urls in manni.config.yaml.";
261
+ var CONFIG_NAME = "manni.config.yaml";
262
+ function resolveSeeds(sources) {
263
+ const { urls, collections: names, declared } = sources;
264
+ if (names.length > 0) {
265
+ if (urls.length > 0) {
266
+ throw new A11yError(
267
+ "--collection selects a configured collection; it cannot be combined with URLs."
268
+ );
269
+ }
270
+ if (sources.source == null && declared.length === 0) {
271
+ throw new A11yError("--collection needs a config file to select from.");
272
+ }
273
+ const selected = selectCollections(
274
+ declared,
275
+ names,
276
+ sources.source ?? CONFIG_NAME,
277
+ (message) => new A11yError(message)
278
+ );
279
+ const seeds = [];
280
+ for (const collection of selected) {
281
+ if (collection.url === void 0) {
282
+ throw new A11yError(`collection "${collection.name}" has no url: to check.`);
283
+ }
284
+ seeds.push(collection.url);
285
+ }
286
+ return unique(seeds);
287
+ }
288
+ if (urls.length > 0) return [...urls];
289
+ const configUrls = sources.configUrls ?? [];
290
+ if (configUrls.length > 0) return [...configUrls];
291
+ const published = unique(
292
+ declared.flatMap((collection) => collection.url === void 0 ? [] : [collection.url])
293
+ );
294
+ if (published.length > 0) return published;
295
+ throw new A11yError(NO_SEEDS_MESSAGE);
296
+ }
297
+ function unique(values) {
298
+ return [...new Set(values)];
299
+ }
300
+
301
+ // src/a11y/core/sitemap.ts
302
+ import { DOMParser } from "@xmldom/xmldom";
303
+ var MAX_INDEX_DEPTH = 3;
304
+ var MAX_CHILD_SITEMAPS = 50;
305
+ var FETCH_TIMEOUT_MS = 1e4;
306
+ var defaultFetcher = (url) => fetch(url, { signal: AbortSignal.timeout(FETCH_TIMEOUT_MS) });
307
+ var WELL_KNOWN_SITEMAP_FILES = ["sitemap.xml", "sitemap-index.xml", "sitemap_index.xml"];
308
+ async function discoverSitemap(seed, fetcher = defaultFetcher) {
309
+ const origin = new URL(seed).origin;
310
+ const candidates = await robotsSitemaps(`${origin}/robots.txt`, fetcher);
311
+ for (const candidate of wellKnownCandidates(seed)) {
312
+ if (!candidates.includes(candidate)) candidates.push(candidate);
313
+ }
314
+ for (const candidate of candidates) {
315
+ const parsed = await fetchSitemap(candidate, fetcher);
316
+ if (parsed === null) continue;
317
+ const walk = { fetcher, seen: /* @__PURE__ */ new Set([candidate]), childBudget: MAX_CHILD_SITEMAPS };
318
+ const locs = await expand(parsed, 0, walk);
319
+ return { source: candidate, urls: pageUrls(seed, locs) };
320
+ }
321
+ return { source: null, urls: [] };
322
+ }
323
+ function parseSitemapXml(text) {
324
+ if (text.trim() === "") return null;
325
+ let root;
326
+ try {
327
+ const doc = new DOMParser({ onError: () => void 0 }).parseFromString(text, "text/xml");
328
+ root = doc.documentElement;
329
+ } catch {
330
+ return null;
331
+ }
332
+ if (root === null) return null;
333
+ const kind = root.localName;
334
+ if (kind !== "sitemapindex" && kind !== "urlset") return null;
335
+ const entry = kind === "sitemapindex" ? "sitemap" : "url";
336
+ const locs = [];
337
+ for (const child of childElements(root, entry)) {
338
+ for (const loc of childElements(child, "loc")) {
339
+ const value = (loc.textContent ?? "").trim();
340
+ if (value !== "") locs.push(value);
341
+ }
342
+ }
343
+ return { kind: kind === "sitemapindex" ? "index" : "urlset", locs };
344
+ }
345
+ function childElements(node, name) {
346
+ const out = [];
347
+ for (let n = node.firstChild; n !== null; n = n.nextSibling) {
348
+ if (n.nodeType === 1 && n.localName === name) out.push(n);
349
+ }
350
+ return out;
351
+ }
352
+ async function expand(parsed, depth, walk) {
353
+ if (parsed.kind === "urlset") return parsed.locs;
354
+ if (depth >= MAX_INDEX_DEPTH) return [];
355
+ const out = [];
356
+ for (const child of parsed.locs) {
357
+ if (walk.childBudget <= 0) break;
358
+ if (!isHttpUrl(child) || walk.seen.has(child)) continue;
359
+ walk.seen.add(child);
360
+ walk.childBudget -= 1;
361
+ const nested = await fetchSitemap(child, walk.fetcher);
362
+ if (nested === null) continue;
363
+ out.push(...await expand(nested, depth + 1, walk));
364
+ }
365
+ return out;
366
+ }
367
+ function wellKnownCandidates(seed) {
368
+ const url = new URL(seed);
369
+ const directories = ancestorDirectories(url.pathname);
370
+ return directories.flatMap(
371
+ (dir) => WELL_KNOWN_SITEMAP_FILES.map((file) => `${url.origin}${dir}${file}`)
372
+ );
373
+ }
374
+ function ancestorDirectories(pathname) {
375
+ let dir = pathname.slice(0, pathname.lastIndexOf("/") + 1);
376
+ const out = [];
377
+ for (; ; ) {
378
+ out.push(dir);
379
+ if (dir === "/") return out;
380
+ const parent = dir.slice(0, dir.lastIndexOf("/", dir.length - 2) + 1);
381
+ dir = parent === "" ? "/" : parent;
382
+ }
383
+ }
384
+ async function robotsSitemaps(url, fetcher) {
385
+ const text = await fetchText(url, fetcher);
386
+ if (text === null) return [];
387
+ const out = [];
388
+ for (const line of text.split(/\r?\n/)) {
389
+ const match = /^\s*sitemap\s*:\s*(\S+)/i.exec(line);
390
+ const candidate = match?.[1];
391
+ if (candidate !== void 0 && isHttpUrl(candidate) && !out.includes(candidate)) {
392
+ out.push(candidate);
393
+ }
394
+ }
395
+ return out;
396
+ }
397
+ async function fetchSitemap(url, fetcher) {
398
+ const text = await fetchText(url, fetcher);
399
+ return text === null ? null : parseSitemapXml(text);
400
+ }
401
+ async function fetchText(url, fetcher) {
402
+ try {
403
+ const response = await fetcher(url);
404
+ if (!response.ok) return null;
405
+ return await response.text();
406
+ } catch {
407
+ return null;
408
+ }
409
+ }
410
+ function pageUrls(seed, locs) {
411
+ const out = [];
412
+ const seen = /* @__PURE__ */ new Set();
413
+ for (const loc of locs) {
414
+ if (!isHttpUrl(loc) || !sameHost(seed, loc) || !isPageLink(loc)) continue;
415
+ const url = normalizeUrl(loc);
416
+ if (seen.has(url)) continue;
417
+ seen.add(url);
418
+ out.push(url);
419
+ }
420
+ return out;
421
+ }
422
+
423
+ // src/a11y/commands/check.ts
424
+ var CHECK_DEFAULTS = Object.freeze({
425
+ crawl: true,
426
+ tags: [],
427
+ severity: "notice",
428
+ timeout: 3e4,
429
+ exclude: []
430
+ });
431
+ async function runCheck(opts, deps) {
432
+ const { analyzer } = deps;
433
+ try {
434
+ if (opts.urls.length === 0) {
435
+ throw new A11yError(NO_SEEDS_MESSAGE);
436
+ }
437
+ for (const url of opts.urls) {
438
+ if (!isHttpUrl(url)) throw new A11yError(`Not an http(s) URL: "${url}".`);
439
+ }
440
+ const seeds = opts.urls.map(normalizeUrl);
441
+ const exclude = createExcludeFilter(opts.exclude);
442
+ const contradictions = seeds.map((seed) => {
443
+ const pattern = exclude.match(seed);
444
+ return pattern === null ? null : `${pattern.source} excludes the seed ${seed}.`;
445
+ }).filter((line) => line !== null);
446
+ if (contradictions.length > 0) throw new A11yError(contradictions.join("\n"));
447
+ let sitemap = { source: null, urls: [] };
448
+ const first = seeds[0];
449
+ if (opts.crawl && first !== void 0) {
450
+ sitemap = await discoverSitemap(first, deps.fetcher);
451
+ deps.onProgress?.({ kind: "sitemap", source: sitemap.source, urls: sitemap.urls.length });
452
+ }
453
+ const outcome2 = await crawl(
454
+ {
455
+ seeds,
456
+ crawl: opts.crawl,
457
+ maxPages: opts.maxPages,
458
+ extra: sitemap.urls,
459
+ exclude,
460
+ analyze: { tags: opts.tags, timeout: opts.timeout },
461
+ onProgress: deps.onProgress
462
+ },
463
+ analyzer
464
+ );
465
+ const results = outcome2.pages.map((page) => finishPage(page, opts.severity));
466
+ return {
467
+ results,
468
+ redirects: outcome2.redirects,
469
+ summary: summarize(results, outcome2, sitemap, opts.crawl)
470
+ };
471
+ } finally {
472
+ await analyzer.close();
473
+ }
474
+ }
475
+ function finishPage(page, floor) {
476
+ if (page.error !== void 0) return { ...page, violations: [], score: null };
477
+ const violations = page.violations.filter((v) => meetsSeverity(v.severity, floor));
478
+ const applicable = page.passes + violations.length;
479
+ const score = applicable === 0 ? null : Math.round(100 * page.passes / applicable);
480
+ return { ...page, violations, score };
481
+ }
482
+ function fails(page) {
483
+ return page.error !== void 0 || page.violations.length > 0;
484
+ }
485
+ function summarize(results, { discovered, skipped, duplicates, redirects, excluded }, sitemap, crawl2) {
486
+ const bySeverity = { notice: 0, warning: 0, error: 0 };
487
+ let failed = 0;
488
+ let violations = 0;
489
+ for (const page of results) {
490
+ if (fails(page)) failed += 1;
491
+ for (const violation of page.violations) {
492
+ violations += 1;
493
+ bySeverity[violation.severity] += 1;
494
+ }
495
+ }
496
+ return {
497
+ discovered,
498
+ checked: results.length,
499
+ skipped,
500
+ duplicates,
501
+ redirects: redirects.length,
502
+ excluded,
503
+ failed,
504
+ violations,
505
+ bySeverity,
506
+ sitemap: sitemap.source,
507
+ // The count the progress line already reported as `(N pages)`, so the
508
+ // report cannot disagree with the narration that preceded it.
509
+ sitemapPages: sitemap.urls.length,
510
+ crawl: crawl2
511
+ };
512
+ }
513
+
514
+ // src/a11y/core/analyzer.ts
515
+ import { AxeBuilder } from "@axe-core/playwright";
516
+ import {
517
+ chromium
518
+ } from "playwright-core";
519
+ var NO_BROWSER_MESSAGE = "No browser found. Install one with `npx playwright install chromium`, or install Google Chrome or Microsoft Edge.";
520
+ var CHANNELS = ["chrome", "msedge", "chromium"];
521
+ function launch(channel) {
522
+ return channel === "chromium" ? chromium.launch({ headless: true }) : chromium.launch({ channel, headless: true });
523
+ }
524
+ async function launchAny() {
525
+ for (const channel of CHANNELS) {
526
+ try {
527
+ return { browser: await launch(channel), channel };
528
+ } catch {
529
+ }
530
+ }
531
+ return null;
532
+ }
533
+ function createPlaywrightAnalyzer() {
534
+ let session = null;
535
+ let current = null;
536
+ const open = () => {
537
+ session ??= (async () => {
538
+ const found = await launchAny();
539
+ if (found === null) throw new A11yError(NO_BROWSER_MESSAGE);
540
+ const context = await found.browser.newContext();
541
+ return { browser: found.browser, context };
542
+ })();
543
+ return session;
544
+ };
545
+ return {
546
+ async analyze(url, opts) {
547
+ const { context } = await open();
548
+ const page = await context.newPage();
549
+ const previous = current;
550
+ current = page;
551
+ await previous?.close().catch(() => void 0);
552
+ const navigations = [];
553
+ const onRequest = (request) => {
554
+ if (request.isNavigationRequest() && request.frame() === page.mainFrame() && request.redirectedFrom() === null) {
555
+ navigations.push(request);
556
+ }
557
+ };
558
+ page.on("request", onRequest);
559
+ try {
560
+ return await analyzePage(page, url, opts, navigations);
561
+ } finally {
562
+ page.off("request", onRequest);
563
+ }
564
+ },
565
+ async close() {
566
+ if (session === null) return;
567
+ const pending = session;
568
+ session = null;
569
+ current = null;
570
+ try {
571
+ const { browser } = await pending;
572
+ await browser.close();
573
+ } catch {
574
+ }
575
+ }
576
+ };
577
+ }
578
+ async function analyzePage(page, url, opts, navigations) {
579
+ let response;
580
+ try {
581
+ response = await page.goto(url, { waitUntil: "load", timeout: opts.timeout });
582
+ } catch (err) {
583
+ throw new A11yError(`Could not load ${url}: ${reason(err)}`);
584
+ }
585
+ const loaded = response?.url() ?? page.url();
586
+ const nextNavigation = () => {
587
+ let first = response?.request() ?? null;
588
+ for (let from = first?.redirectedFrom() ?? null; from !== null; from = from.redirectedFrom()) {
589
+ first = from;
590
+ }
591
+ const own = first === null ? 0 : navigations.indexOf(first);
592
+ return own === -1 ? void 0 : navigations[own + 1];
593
+ };
594
+ const destination = () => nextNavigation()?.url() ?? page.url();
595
+ const navigatedAway = async () => {
596
+ if (nextNavigation() === void 0 && samePage(page.url(), loaded)) return null;
597
+ try {
598
+ await page.waitForURL((u) => !samePage(u.href, loaded), {
599
+ waitUntil: "commit",
600
+ timeout: opts.timeout
601
+ });
602
+ return { redirect: destination() };
603
+ } catch {
604
+ return null;
605
+ }
606
+ };
607
+ let read;
608
+ try {
609
+ read = await page.evaluate(readPage);
610
+ } catch (err) {
611
+ const moved = await navigatedAway();
612
+ if (moved !== null) return moved;
613
+ throw new A11yError(`Could not analyze ${url}: ${reason(err)}`);
614
+ }
615
+ const refresh = refreshTarget(read.refresh, page.url());
616
+ if (refresh !== null) return { redirect: refresh };
617
+ let builder = new AxeBuilder({ page });
618
+ if (opts.tags.length > 0) builder = builder.withTags(opts.tags);
619
+ let results;
620
+ try {
621
+ results = await builder.analyze();
622
+ } catch (err) {
623
+ const moved = await navigatedAway();
624
+ if (moved !== null) return moved;
625
+ throw new A11yError(`Could not analyze ${url}: ${reason(err)}`);
626
+ }
627
+ if (!samePage(page.url(), loaded)) return { redirect: destination() };
628
+ return {
629
+ result: {
630
+ url,
631
+ violations: results.violations.map(toViolation),
632
+ passes: results.passes.length,
633
+ incomplete: results.incomplete.length
634
+ },
635
+ links: read.links,
636
+ finalUrl: page.url()
637
+ };
638
+ }
639
+ function readPage() {
640
+ const doc = globalThis.document;
641
+ return {
642
+ links: Array.from(doc.querySelectorAll("a[href]"), (a) => a.href),
643
+ refresh: doc.querySelector('meta[http-equiv="refresh" i]')?.getAttribute("content") ?? null
644
+ };
645
+ }
646
+ function refreshUrl(content) {
647
+ const match = /^\s*[\d.]*\s*[;,]\s*(?:url\s*=\s*)?(.*)$/is.exec(content);
648
+ let target = match?.[1]?.trim() ?? "";
649
+ const quote = target[0];
650
+ if (quote === "'" || quote === '"') {
651
+ const end = target.indexOf(quote, 1);
652
+ target = (end === -1 ? target.slice(1) : target.slice(1, end)).trim();
653
+ }
654
+ return target === "" ? null : target;
655
+ }
656
+ function refreshTarget(content, base) {
657
+ if (content === null) return null;
658
+ const target = refreshUrl(content);
659
+ if (target === null) return null;
660
+ let to;
661
+ try {
662
+ to = new URL(target, base).href;
663
+ } catch {
664
+ return null;
665
+ }
666
+ return samePage(to, base) ? null : to;
667
+ }
668
+ function samePage(a, b) {
669
+ try {
670
+ return dedupeKey(a) === dedupeKey(b);
671
+ } catch {
672
+ return a === b;
673
+ }
674
+ }
675
+ function severityOf(impact) {
676
+ switch (impact) {
677
+ case "critical":
678
+ case "serious":
679
+ return "error";
680
+ case "moderate":
681
+ return "warning";
682
+ default:
683
+ return "notice";
684
+ }
685
+ }
686
+ function impactOf(impact) {
687
+ return impact != null && isAxeImpact(impact) ? impact : "minor";
688
+ }
689
+ function toViolation(result) {
690
+ return {
691
+ id: result.id,
692
+ severity: severityOf(result.impact),
693
+ impact: impactOf(result.impact),
694
+ help: result.help,
695
+ helpUrl: result.helpUrl,
696
+ tags: result.tags,
697
+ nodes: result.nodes.map(toNode)
698
+ };
699
+ }
700
+ function toNode(node) {
701
+ return {
702
+ // A shadow-DOM target is a nested list of selectors; flatten it the same way.
703
+ target: node.target.map((t) => Array.isArray(t) ? t.join(" ") : t).join(" "),
704
+ html: node.html,
705
+ summary: node.failureSummary ?? ""
706
+ };
707
+ }
708
+ function reason(err) {
709
+ const message = err instanceof Error ? err.message : String(err);
710
+ return message.split("\n")[0]?.trim() ?? message;
711
+ }
712
+
713
+ // src/a11y/core/config.ts
714
+ var SECTION = "a11y";
715
+ var CONFIG_KEYS = ["urls", "crawl", "maxPages", "tags", "severity", "timeout", "exclude"];
716
+ var CONFIG_FILE = {
717
+ section: SECTION,
718
+ // The a11y tool never had a pre-family file of its own.
719
+ legacyNames: [],
720
+ toError: (message) => new A11yError(message)
721
+ };
722
+ async function loadA11yConfig(cwd, explicitPath) {
723
+ const file = explicitPath === void 0 ? await findConfigFile(cwd, CONFIG_FILE) : await readConfigFile(explicitPath, cwd, CONFIG_FILE);
724
+ if (file === null) return { config: {}, source: null, collections: [] };
725
+ return {
726
+ config: parseA11yConfig(file.value, file.source),
727
+ source: file.source,
728
+ collections: file.collections
729
+ };
730
+ }
731
+ function parseA11yConfig(value, source) {
732
+ if (value == null) return {};
733
+ if (typeof value !== "object" || Array.isArray(value)) {
734
+ throw new A11yError(`${source}: \`${SECTION}:\` must be a mapping.`);
735
+ }
736
+ const obj = value;
737
+ for (const key of Object.keys(obj)) {
738
+ if (!CONFIG_KEYS.includes(key)) {
739
+ throw new A11yError(
740
+ `${source}: \`${SECTION}:\` has unknown key "${key}". Supported keys: ${CONFIG_KEYS.join(", ")}.`
741
+ );
742
+ }
743
+ }
744
+ const config = {};
745
+ if (obj.urls !== void 0) config.urls = asUrls(obj.urls, source);
746
+ if (obj.crawl !== void 0) config.crawl = asBoolean(obj.crawl, "crawl", source);
747
+ if (obj.maxPages !== void 0) {
748
+ config.maxPages = asPositiveInteger(obj.maxPages, "maxPages", source);
749
+ }
750
+ if (obj.tags !== void 0) config.tags = asStringList(obj.tags, "tags", source);
751
+ if (obj.severity !== void 0) config.severity = asSeverity(obj.severity, source);
752
+ if (obj.timeout !== void 0) {
753
+ config.timeout = asPositiveInteger(obj.timeout, "timeout", source);
754
+ }
755
+ if (obj.exclude !== void 0) config.exclude = asExcludes(obj.exclude, source);
756
+ return config;
757
+ }
758
+ function excludeEntryLabel(source, index) {
759
+ return `${source}: "${SECTION}.exclude[${String(index)}]"`;
760
+ }
761
+ function asExcludes(value, source) {
762
+ const globs = asStringList(value, "exclude", source);
763
+ globs.forEach((glob, i) => {
764
+ const at = excludeEntryLabel(source, i);
765
+ if (glob === "") throw new A11yError(`${at} must be a non-empty string.`);
766
+ if (!isUrlPathGlob(glob)) throw new A11yError(`${at} ${MUST_START_WITH_SLASH}`);
767
+ });
768
+ return globs;
769
+ }
770
+ function asStringList(value, field, source) {
771
+ if (!Array.isArray(value) || !value.every((v) => typeof v === "string")) {
772
+ throw new A11yError(`${source}: "${SECTION}.${field}" must be a list of strings.`);
773
+ }
774
+ return value;
775
+ }
776
+ function asUrls(value, source) {
777
+ const urls = asStringList(value, "urls", source);
778
+ urls.forEach((url, i) => {
779
+ if (!isHttpUrl(url)) {
780
+ throw new A11yError(
781
+ `${source}: "${SECTION}.urls[${i}]" must be an http(s) URL, got "${url}".`
782
+ );
783
+ }
784
+ });
785
+ return urls;
786
+ }
787
+ function asBoolean(value, field, source) {
788
+ if (typeof value !== "boolean") {
789
+ throw new A11yError(`${source}: "${SECTION}.${field}" must be a boolean.`);
790
+ }
791
+ return value;
792
+ }
793
+ function asPositiveInteger(value, field, source) {
794
+ if (typeof value !== "number" || !Number.isInteger(value) || value < 1) {
795
+ throw new A11yError(`${source}: "${SECTION}.${field}" must be an integer >= 1.`);
796
+ }
797
+ return value;
798
+ }
799
+ function asSeverity(value, source) {
800
+ if (typeof value !== "string" || !isSeverity(value)) {
801
+ throw new A11yError(
802
+ `${source}: "${SECTION}.severity" must be one of ${SEVERITIES.join(", ")}.`
803
+ );
804
+ }
805
+ return value;
806
+ }
807
+
808
+ // src/a11y/reporters/github.ts
809
+ function annotationLevel(severity) {
810
+ return severity;
811
+ }
812
+ function renderGithub(run) {
813
+ const lines = [];
814
+ for (const page of run.results) {
815
+ if (page.error !== void 0) {
816
+ lines.push(
817
+ `::error title=a11y/load::${escapeWorkflowCommandMessage(`${page.url}: ${page.error}`)}`
818
+ );
819
+ continue;
820
+ }
821
+ for (const v of page.violations) {
822
+ const n = v.nodes.length;
823
+ const title = escapeWorkflowCommandProperty(`a11y/${v.id}`);
824
+ const msg = escapeWorkflowCommandMessage(
825
+ `${v.help} \u2014 ${n} node${n === 1 ? "" : "s"} on ${page.url} (${v.helpUrl})`
826
+ );
827
+ lines.push(`::${annotationLevel(v.severity)} title=${title}::${msg}`);
828
+ }
829
+ }
830
+ return lines.join("\n");
831
+ }
832
+
833
+ // src/a11y/reporters/json.ts
834
+ function renderJson(run) {
835
+ return JSON.stringify(run, null, 2);
836
+ }
837
+
838
+ // src/a11y/reporters/pretty.ts
839
+ var MAX_NODES = 3;
840
+ function renderPretty(run, opts) {
841
+ const c = palette(opts.color);
842
+ const { summary } = run;
843
+ const lines = [];
844
+ lines.push(`Checked ${summary.checked} of ${summary.discovered} pages (${where(run, c)})`);
845
+ for (const page of run.results) {
846
+ if (page.error !== void 0) {
847
+ lines.push(`${c.red("\u2717")} ${c.cyan(page.url)} could not load: ${page.error}`);
848
+ continue;
849
+ }
850
+ if (page.violations.length === 0) {
851
+ if (!opts.quiet) lines.push(`${c.green("\u2713")} ${c.cyan(page.url)} score ${scoreText(page)}`);
852
+ continue;
853
+ }
854
+ lines.push(
855
+ `${c.red("\u2717")} ${c.cyan(page.url)} score ${scoreText(page)} ${severityCounts(page.violations, c)}`
856
+ );
857
+ for (const v of page.violations) lines.push(...violationLines(v, c));
858
+ }
859
+ if (!opts.quiet) {
860
+ for (const r of run.redirects) {
861
+ lines.push(`${c.dim("\u21AA")} ${c.cyan(r.url)} redirects to ${c.cyan(r.to)}`);
862
+ }
863
+ }
864
+ const failed = summary.failed;
865
+ const footer = `${summary.violations} violation${summary.violations === 1 ? "" : "s"} on ${failed} of ${summary.checked} pages` + (summary.skipped > 0 ? `; ${summary.skipped} skipped (--max-pages)` : "") + (summary.duplicates > 0 ? `; ${summary.duplicates} duplicate${summary.duplicates === 1 ? "" : "s"} dropped` : "") + (summary.redirects > 0 ? `; ${summary.redirects} redirect${summary.redirects === 1 ? "" : "s"}` : "") + (summary.excluded > 0 ? `; ${summary.excluded} excluded` : "");
866
+ lines.push("");
867
+ lines.push(failed > 0 ? c.red(footer) : c.green(footer));
868
+ return lines.join("\n");
869
+ }
870
+ function where(run, c) {
871
+ const { summary } = run;
872
+ if (!summary.crawl) return "no crawl";
873
+ if (summary.sitemap === null) return "no sitemap; followed links";
874
+ const found = `sitemap: ${c.cyan(summary.sitemap)}`;
875
+ const n = summary.sitemapPages;
876
+ if (n === 0) return `${found}, 0 pages; followed links`;
877
+ if (n >= summary.discovered) return found;
878
+ return `${found}, ${n} page${n === 1 ? "" : "s"}; followed links for the rest`;
879
+ }
880
+ function scoreText(page) {
881
+ return page.score === null ? "n/a" : String(page.score);
882
+ }
883
+ function severityCounts(violations, c) {
884
+ const counts = { notice: 0, warning: 0, error: 0 };
885
+ for (const v of violations) counts[v.severity] += 1;
886
+ return [...SEVERITIES].reverse().filter((severity) => counts[severity] > 0).map((severity) => {
887
+ const n = counts[severity];
888
+ return paintSeverity(`${n} ${severity}${n === 1 ? "" : "s"}`, severity, c);
889
+ }).join(", ");
890
+ }
891
+ function violationLines(v, c) {
892
+ const n = v.nodes.length;
893
+ const lines = [
894
+ ` ${paintSeverity(v.severity, v.severity, c)} ${v.id} ${c.dim(`(axe: ${v.impact})`)} ${n} node${n === 1 ? "" : "s"} ${v.help} ${c.dim(v.helpUrl)}`
895
+ ];
896
+ for (const node of v.nodes.slice(0, MAX_NODES)) {
897
+ lines.push(` ${node.target} \u2192 ${oneLine(node.summary)}`);
898
+ }
899
+ if (n > MAX_NODES) lines.push(c.dim(` (+${n - MAX_NODES} more)`));
900
+ return lines;
901
+ }
902
+ function oneLine(summary) {
903
+ const [head, ...rest] = summary.split("\n").map((s) => s.trim()).filter((s) => s.length > 0);
904
+ if (head === void 0) return "";
905
+ return rest.length === 0 ? head : `${head} ${rest.join("; ")}`;
906
+ }
907
+ function paintSeverity(text, severity, c) {
908
+ switch (severity) {
909
+ case "notice":
910
+ return c.dim(text);
911
+ case "warning":
912
+ return c.yellow(text);
913
+ case "error":
914
+ return c.red(text);
915
+ }
916
+ }
917
+
918
+ // src/a11y/reporters/index.ts
919
+ var A11Y_FORMATS = ["pretty", "json", "github"];
920
+ var A11Y_FORMAT_LIST = A11Y_FORMATS.join(" | ");
921
+ function isA11yFormat(value) {
922
+ return A11Y_FORMATS.includes(value);
923
+ }
924
+ function render(format, run, opts) {
925
+ switch (format) {
926
+ case "pretty":
927
+ return renderPretty(run, opts);
928
+ case "json":
929
+ return renderJson(run);
930
+ case "github":
931
+ return renderGithub(run);
932
+ default: {
933
+ const unreachable = format;
934
+ throw new A11yError(
935
+ `Unknown report format ${JSON.stringify(unreachable)}. Use ${A11Y_FORMAT_LIST}.`
936
+ );
937
+ }
938
+ }
939
+ }
940
+
941
+ // src/a11y/reporters/progress.ts
942
+ var CLEAR_LINE = "\r\x1B[2K";
943
+ var DEFAULT_COLUMNS = 80;
944
+ function createProgressReporter(opts) {
945
+ return opts.tty ? terminalReporter(opts) : plainReporter(opts.stream);
946
+ }
947
+ function plural(n, noun) {
948
+ return `${n} ${noun}${n === 1 ? "" : "s"}`;
949
+ }
950
+ function counter(index, queued) {
951
+ return `[${index}/${queued}]`;
952
+ }
953
+ function terminalReporter(opts) {
954
+ const { stream } = opts;
955
+ const c = palette(opts.color);
956
+ let queued = 0;
957
+ const show = (text) => {
958
+ stream.write(`${CLEAR_LINE}${text}`);
959
+ };
960
+ const showUrl = (prefix, url, suffix = "") => {
961
+ const columns = stream.columns ?? DEFAULT_COLUMNS;
962
+ const fitted = truncateMiddle(url, columns - visibleLength(prefix) - suffix.length);
963
+ show(`${prefix}${c.cyan(fitted)}${suffix}`);
964
+ };
965
+ return (event) => {
966
+ switch (event.kind) {
967
+ case "browser":
968
+ show("Starting browser\u2026");
969
+ return;
970
+ case "sitemap":
971
+ if (event.source === null) show("No sitemap; following links");
972
+ else showUrl("Sitemap: ", event.source, ` (${plural(event.urls, "page")})`);
973
+ return;
974
+ case "excluded":
975
+ show(`Excluded ${plural(event.urls, "page")} (${plural(event.patterns, "pattern")})`);
976
+ return;
977
+ case "page":
978
+ queued = event.queued;
979
+ showUrl(`${c.dim(counter(event.index, queued))} `, event.url);
980
+ return;
981
+ case "checked":
982
+ showUrl(`${c.dim(counter(event.index, queued))} ${outcome(event, c)}`, event.url);
983
+ return;
984
+ case "redirected":
985
+ showUrl(`${c.dim(counter(event.index, queued))} ${c.dim("\u21AA")} redirects to `, event.to);
986
+ return;
987
+ case "done":
988
+ stream.write(CLEAR_LINE);
989
+ return;
990
+ }
991
+ };
992
+ }
993
+ function outcome(event, c) {
994
+ if (event.error !== void 0) return `${c.red("\u2717")} could not load: ${event.error} `;
995
+ if (event.violations === 0) return `${c.green("\u2713")} `;
996
+ return `${c.red("\u2717")} ${plural(event.violations, "violation")} `;
997
+ }
998
+ function plainReporter(stream) {
999
+ let queued = 0;
1000
+ const say = (text) => {
1001
+ stream.write(`${programName()}: ${text}
1002
+ `);
1003
+ };
1004
+ return (event) => {
1005
+ switch (event.kind) {
1006
+ case "browser":
1007
+ say("starting browser");
1008
+ return;
1009
+ case "sitemap":
1010
+ if (event.source === null) say("no sitemap; following links");
1011
+ else say(`sitemap ${event.source} (${plural(event.urls, "page")})`);
1012
+ return;
1013
+ case "excluded":
1014
+ say(`excluded ${plural(event.urls, "page")} (${plural(event.patterns, "pattern")})`);
1015
+ return;
1016
+ case "page":
1017
+ queued = event.queued;
1018
+ say(`${counter(event.index, queued)} ${event.url}`);
1019
+ return;
1020
+ case "checked": {
1021
+ const head = counter(event.index, queued);
1022
+ if (event.error !== void 0) say(`${head} could not load: ${event.error}`);
1023
+ else if (event.violations === 0) say(`${head} \u2713`);
1024
+ else say(`${head} \u2717 ${plural(event.violations, "violation")}`);
1025
+ return;
1026
+ }
1027
+ case "redirected":
1028
+ say(`${counter(event.index, queued)} \u21AA redirects to ${event.to}`);
1029
+ return;
1030
+ case "done":
1031
+ say(`checked ${plural(event.checked, "page")}, ${event.skipped} skipped`);
1032
+ return;
1033
+ }
1034
+ };
1035
+ }
1036
+ function visibleLength(text) {
1037
+ return text.replace(/\x1b\[[0-9;]*m/g, "").length;
1038
+ }
1039
+ function truncateMiddle(text, room) {
1040
+ const max = Math.max(room, 8);
1041
+ if (text.length <= max) return text;
1042
+ const head = Math.ceil((max - 1) / 2);
1043
+ const tail = max - 1 - head;
1044
+ return `${text.slice(0, head)}\u2026${text.slice(text.length - tail)}`;
1045
+ }
1046
+
1047
+ // src/a11y/cli.ts
1048
+ function collect(value, prev) {
1049
+ return prev.concat([value]);
1050
+ }
1051
+ function splitList(value) {
1052
+ return value.split(",").map((s) => s.trim()).filter(Boolean);
1053
+ }
1054
+ function resolveColor(program, stream) {
1055
+ const noColor = program.opts().color === false;
1056
+ return shouldColor({ noColor, isTTY: stream.isTTY });
1057
+ }
1058
+ function resolveProgress(program, flag) {
1059
+ const tty = process.stderr.isTTY ?? false;
1060
+ const silent = { listener: void 0, clear: () => void 0 };
1061
+ if (!(flag ?? tty)) return silent;
1062
+ const listener = createProgressReporter({
1063
+ stream: process.stderr,
1064
+ color: resolveColor(program, process.stderr),
1065
+ tty
1066
+ });
1067
+ if (!tty) return { listener, clear: silent.clear };
1068
+ return {
1069
+ listener,
1070
+ clear: () => {
1071
+ process.stderr.write(CLEAR_LINE);
1072
+ }
1073
+ };
1074
+ }
1075
+ function positiveInteger(value, flag) {
1076
+ if (!/^[1-9]\d*$/.test(value)) {
1077
+ throw new A11yError(`${flag} must be an integer >= 1.`);
1078
+ }
1079
+ return Number(value);
1080
+ }
1081
+ function parseMaxPages(value) {
1082
+ if (value === false) return "uncapped";
1083
+ if (value === void 0) return void 0;
1084
+ return positiveInteger(value, "--max-pages");
1085
+ }
1086
+ function resolveMaxPages(flag, configured) {
1087
+ if (flag === "uncapped") return void 0;
1088
+ return flag ?? configured;
1089
+ }
1090
+ function resolveCrawl(flag, configured) {
1091
+ return flag ?? configured ?? CHECK_DEFAULTS.crawl;
1092
+ }
1093
+ function assertExcludeGlobs(globs) {
1094
+ return globs.map((glob) => {
1095
+ if (!isUrlPathGlob(glob)) {
1096
+ throw new A11yError(`--exclude "${glob}" ${MUST_START_WITH_SLASH}`);
1097
+ }
1098
+ return { glob, source: `--exclude "${glob}"` };
1099
+ });
1100
+ }
1101
+ function configExcludeGlobs(globs, source) {
1102
+ const file = source ?? FAMILY_CONFIG_NAMES[0] ?? "manni.config.yaml";
1103
+ return globs.map((glob, i) => ({ glob, source: excludeEntryLabel(file, i) }));
1104
+ }
1105
+ function assertSeverity(value) {
1106
+ if (!isSeverity(value)) {
1107
+ throw new A11yError(`Unknown --severity "${value}". Use ${SEVERITY_LIST}.`);
1108
+ }
1109
+ return value;
1110
+ }
1111
+ function buildProgram() {
1112
+ const program = new Command();
1113
+ program.name("a11y").description("Crawl a site and check every page for accessibility violations with axe-core").version(package_default.version, "-V, --version").option("--no-color", "disable colored output").showHelpAfterError("(add --help for usage)").exitOverride();
1114
+ program.command("check").description("Crawl from the given URLs and check every page with axe-core").argument(
1115
+ "[urls...]",
1116
+ "http(s) seed URLs; falls back to a collection's url: or a11y.urls in manni.config.yaml"
1117
+ ).option("-f, --format <format>", `output: ${A11Y_FORMAT_LIST}`, "pretty").option("--crawl", "crawl from the given URLs: sitemap and link following (default)").option("--no-crawl", "check exactly the given URLs: no sitemap, no link following").option(
1118
+ "--max-pages <n>",
1119
+ "cap on pages checked; the rest are reported as skipped (default: no cap)"
1120
+ ).option("--no-max-pages", "check every page found, ignoring a configured maxPages").option(
1121
+ "--exclude <glob>",
1122
+ "URL path glob to keep out of the crawl; repeatable",
1123
+ collect,
1124
+ []
1125
+ ).option("--tags <list>", "comma-separated axe tags to restrict the rules to").option(
1126
+ "--severity <level>",
1127
+ `minimum severity reported: ${SEVERITY_LIST}`,
1128
+ CHECK_DEFAULTS.severity
1129
+ ).option(
1130
+ "--timeout <ms>",
1131
+ "per-page navigation timeout in milliseconds",
1132
+ String(CHECK_DEFAULTS.timeout)
1133
+ ).option("-q, --quiet", "in pretty output, hide pages with no violations").option("--progress", "report progress on stderr (default: only on a terminal)").option("--no-progress", "never report progress").option(
1134
+ "--collection <name>",
1135
+ "configured collection to run over; repeatable",
1136
+ collect,
1137
+ []
1138
+ ).option("-c, --config <path>", "path to a manni config file").option("--no-config", "ignore any discovered config file").action(async (urls, options, command) => {
1139
+ let clearProgress = () => void 0;
1140
+ try {
1141
+ const typed = (name) => command.getOptionValueSource(name) === "cli";
1142
+ const format = options.format;
1143
+ if (!isA11yFormat(format)) {
1144
+ throw new A11yError(`Unknown --format "${format}". Use ${A11Y_FORMAT_LIST}.`);
1145
+ }
1146
+ const severity = assertSeverity(options.severity);
1147
+ const maxPages = parseMaxPages(options.maxPages);
1148
+ const timeout = positiveInteger(options.timeout, "--timeout");
1149
+ const exclude = assertExcludeGlobs(options.exclude);
1150
+ const loaded = options.config === false ? { config: {}, source: null, collections: [] } : await loadA11yConfig(process.cwd(), options.config);
1151
+ const cfg = loaded.config;
1152
+ const seeds = resolveSeeds({
1153
+ urls,
1154
+ collections: options.collection,
1155
+ ...cfg.urls === void 0 ? {} : { configUrls: cfg.urls },
1156
+ declared: loaded.collections,
1157
+ source: loaded.source
1158
+ });
1159
+ const parent = command.parent ?? command;
1160
+ const progress = resolveProgress(parent, options.progress);
1161
+ clearProgress = progress.clear;
1162
+ const run = await runCheck(
1163
+ {
1164
+ urls: seeds,
1165
+ crawl: resolveCrawl(options.crawl, cfg.crawl),
1166
+ // Typed flag > config key > no cap. There is no default to fall
1167
+ // to, and `--no-max-pages` is the typed way to ask for none.
1168
+ maxPages: resolveMaxPages(maxPages, cfg.maxPages),
1169
+ tags: options.tags !== void 0 ? splitList(options.tags) : cfg.tags ?? CHECK_DEFAULTS.tags,
1170
+ // A typed flag replaces the config list entirely; the two do not
1171
+ // merge. Merging would only ever let a run exclude *more* than
1172
+ // the repository's default, so checking one excluded section on
1173
+ // purpose would need --no-config, which also drops collections:.
1174
+ exclude: typed("exclude") ? exclude : configExcludeGlobs(cfg.exclude ?? [], loaded.source),
1175
+ severity: typed("severity") ? severity : cfg.severity ?? CHECK_DEFAULTS.severity,
1176
+ timeout: typed("timeout") ? timeout : cfg.timeout ?? CHECK_DEFAULTS.timeout
1177
+ },
1178
+ { analyzer: createPlaywrightAnalyzer(), onProgress: progress.listener }
1179
+ );
1180
+ const color = resolveColor(parent, process.stdout);
1181
+ const text = render(format, run, { color, quiet: Boolean(options.quiet) });
1182
+ if (text.length > 0 || format !== "github") {
1183
+ process.stdout.write(`${text}
1184
+ `);
1185
+ }
1186
+ process.exitCode = run.summary.failed > 0 ? 1 : 0;
1187
+ } catch (err) {
1188
+ clearProgress();
1189
+ fail(err);
1190
+ }
1191
+ });
1192
+ return program;
1193
+ }
1194
+ export {
1195
+ assertExcludeGlobs,
1196
+ buildProgram,
1197
+ configExcludeGlobs,
1198
+ parseMaxPages,
1199
+ positiveInteger,
1200
+ resolveCrawl,
1201
+ resolveMaxPages
1202
+ };
1203
+ //# sourceMappingURL=cli-6HIDIFVT.js.map