recker 1.0.43 → 1.0.44

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 (459) hide show
  1. package/README.md +47 -0
  2. package/dist/bin/recker-linux-x64 +0 -0
  3. package/dist/bin/recker-macos-x64 +0 -0
  4. package/dist/bin/recker-win-x64.exe +0 -0
  5. package/dist/bin/rek.cjs +85152 -100207
  6. package/dist/browser/ai/adaptive-timeout.d.ts +50 -0
  7. package/dist/browser/ai/adaptive-timeout.js +208 -0
  8. package/dist/browser/ai/client.d.ts +22 -0
  9. package/dist/browser/ai/client.js +294 -0
  10. package/dist/browser/ai/index.d.ts +14 -0
  11. package/dist/browser/ai/index.js +11 -0
  12. package/dist/browser/ai/providers/anthropic.d.ts +63 -0
  13. package/dist/browser/ai/providers/anthropic.js +370 -0
  14. package/dist/browser/ai/providers/base.d.ts +48 -0
  15. package/dist/browser/ai/providers/base.js +150 -0
  16. package/dist/browser/ai/providers/google.d.ts +59 -0
  17. package/dist/browser/ai/providers/google.js +305 -0
  18. package/dist/browser/ai/providers/ollama.d.ts +44 -0
  19. package/dist/browser/ai/providers/ollama.js +240 -0
  20. package/dist/browser/ai/providers/openai.d.ts +64 -0
  21. package/dist/browser/ai/providers/openai.js +298 -0
  22. package/dist/browser/ai/rate-limiter.d.ts +43 -0
  23. package/dist/browser/ai/rate-limiter.js +215 -0
  24. package/dist/browser/ai/vector/index.d.ts +2 -0
  25. package/dist/browser/ai/vector/index.js +2 -0
  26. package/dist/browser/ai/vector/similarity.d.ts +2 -0
  27. package/dist/browser/ai/vector/similarity.js +27 -0
  28. package/dist/browser/ai/vector/store.d.ts +27 -0
  29. package/dist/browser/ai/vector/store.js +82 -0
  30. package/dist/browser/browser/cache.d.ts +2 -40
  31. package/dist/browser/browser/cache.js +2 -199
  32. package/dist/browser/browser/index.d.ts +8 -0
  33. package/dist/browser/browser/index.js +8 -0
  34. package/dist/browser/browser/recker.d.ts +8 -1
  35. package/dist/browser/browser/recker.js +8 -2
  36. package/dist/browser/cache/indexed-db.d.ts +10 -0
  37. package/dist/browser/cache/indexed-db.js +88 -0
  38. package/dist/browser/cache/service-worker-cache.d.ts +18 -0
  39. package/dist/browser/cache/service-worker-cache.js +103 -0
  40. package/dist/browser/cache.d.ts +2 -40
  41. package/dist/browser/cache.js +2 -199
  42. package/dist/browser/constants/user-agents.d.ts +7 -0
  43. package/dist/browser/constants/user-agents.js +7 -0
  44. package/dist/browser/core/client.d.ts +2 -0
  45. package/dist/browser/core/client.js +19 -1
  46. package/dist/browser/index.d.ts +8 -0
  47. package/dist/browser/index.js +8 -0
  48. package/dist/browser/plugins/har-recorder.d.ts +40 -0
  49. package/dist/browser/plugins/har-recorder.js +120 -0
  50. package/dist/browser/plugins/network-simulation.d.ts +7 -0
  51. package/dist/browser/plugins/network-simulation.js +13 -0
  52. package/dist/browser/presets/android.d.ts +2 -0
  53. package/dist/browser/presets/android.js +16 -0
  54. package/dist/browser/presets/anthropic.d.ts +8 -0
  55. package/dist/browser/presets/anthropic.js +27 -0
  56. package/dist/browser/presets/aws.d.ts +19 -0
  57. package/dist/browser/presets/aws.js +68 -0
  58. package/dist/browser/presets/azure-openai.d.ts +10 -0
  59. package/dist/browser/presets/azure-openai.js +35 -0
  60. package/dist/browser/presets/azure.d.ts +41 -0
  61. package/dist/browser/presets/azure.js +104 -0
  62. package/dist/browser/presets/chaturbate.d.ts +2 -0
  63. package/dist/browser/presets/chaturbate.js +17 -0
  64. package/dist/browser/presets/cloudflare.d.ts +12 -0
  65. package/dist/browser/presets/cloudflare.js +39 -0
  66. package/dist/browser/presets/cohere.d.ts +7 -0
  67. package/dist/browser/presets/cohere.js +22 -0
  68. package/dist/browser/presets/deepseek.d.ts +7 -0
  69. package/dist/browser/presets/deepseek.js +22 -0
  70. package/dist/browser/presets/digitalocean.d.ts +5 -0
  71. package/dist/browser/presets/digitalocean.js +16 -0
  72. package/dist/browser/presets/discord.d.ts +6 -0
  73. package/dist/browser/presets/discord.js +17 -0
  74. package/dist/browser/presets/elevenlabs.d.ts +6 -0
  75. package/dist/browser/presets/elevenlabs.js +20 -0
  76. package/dist/browser/presets/enhancers.d.ts +20 -0
  77. package/dist/browser/presets/enhancers.js +85 -0
  78. package/dist/browser/presets/fireworks.d.ts +7 -0
  79. package/dist/browser/presets/fireworks.js +22 -0
  80. package/dist/browser/presets/gcp.d.ts +34 -0
  81. package/dist/browser/presets/gcp.js +91 -0
  82. package/dist/browser/presets/gemini.d.ts +7 -0
  83. package/dist/browser/presets/gemini.js +23 -0
  84. package/dist/browser/presets/github.d.ts +6 -0
  85. package/dist/browser/presets/github.js +17 -0
  86. package/dist/browser/presets/gitlab.d.ts +6 -0
  87. package/dist/browser/presets/gitlab.js +16 -0
  88. package/dist/browser/presets/groq.d.ts +7 -0
  89. package/dist/browser/presets/groq.js +22 -0
  90. package/dist/browser/presets/hubspot.d.ts +9 -0
  91. package/dist/browser/presets/hubspot.js +28 -0
  92. package/dist/browser/presets/huggingface.d.ts +7 -0
  93. package/dist/browser/presets/huggingface.js +23 -0
  94. package/dist/browser/presets/index.d.ts +47 -0
  95. package/dist/browser/presets/index.js +47 -0
  96. package/dist/browser/presets/ios.d.ts +2 -0
  97. package/dist/browser/presets/ios.js +13 -0
  98. package/dist/browser/presets/linear.d.ts +5 -0
  99. package/dist/browser/presets/linear.js +16 -0
  100. package/dist/browser/presets/mailgun.d.ts +7 -0
  101. package/dist/browser/presets/mailgun.js +20 -0
  102. package/dist/browser/presets/meta.d.ts +10 -0
  103. package/dist/browser/presets/meta.js +33 -0
  104. package/dist/browser/presets/mistral.d.ts +7 -0
  105. package/dist/browser/presets/mistral.js +22 -0
  106. package/dist/browser/presets/notion.d.ts +6 -0
  107. package/dist/browser/presets/notion.js +17 -0
  108. package/dist/browser/presets/openai.d.ts +9 -0
  109. package/dist/browser/presets/openai.js +30 -0
  110. package/dist/browser/presets/oracle.d.ts +19 -0
  111. package/dist/browser/presets/oracle.js +117 -0
  112. package/dist/browser/presets/perplexity.d.ts +7 -0
  113. package/dist/browser/presets/perplexity.js +22 -0
  114. package/dist/browser/presets/pinecone.d.ts +8 -0
  115. package/dist/browser/presets/pinecone.js +42 -0
  116. package/dist/browser/presets/registry.d.ts +23 -0
  117. package/dist/browser/presets/registry.js +519 -0
  118. package/dist/browser/presets/replicate.d.ts +7 -0
  119. package/dist/browser/presets/replicate.js +23 -0
  120. package/dist/browser/presets/sendgrid.d.ts +6 -0
  121. package/dist/browser/presets/sendgrid.js +20 -0
  122. package/dist/browser/presets/sentry.d.ts +11 -0
  123. package/dist/browser/presets/sentry.js +48 -0
  124. package/dist/browser/presets/sinch.d.ts +9 -0
  125. package/dist/browser/presets/sinch.js +39 -0
  126. package/dist/browser/presets/slack.d.ts +5 -0
  127. package/dist/browser/presets/slack.js +16 -0
  128. package/dist/browser/presets/square.d.ts +10 -0
  129. package/dist/browser/presets/square.js +33 -0
  130. package/dist/browser/presets/stripe.d.ts +7 -0
  131. package/dist/browser/presets/stripe.js +23 -0
  132. package/dist/browser/presets/supabase.d.ts +6 -0
  133. package/dist/browser/presets/supabase.js +18 -0
  134. package/dist/browser/presets/tiktok.d.ts +10 -0
  135. package/dist/browser/presets/tiktok.js +38 -0
  136. package/dist/browser/presets/together.d.ts +7 -0
  137. package/dist/browser/presets/together.js +22 -0
  138. package/dist/browser/presets/twilio.d.ts +6 -0
  139. package/dist/browser/presets/twilio.js +17 -0
  140. package/dist/browser/presets/vercel.d.ts +6 -0
  141. package/dist/browser/presets/vercel.js +23 -0
  142. package/dist/browser/presets/vultr.d.ts +5 -0
  143. package/dist/browser/presets/vultr.js +16 -0
  144. package/dist/browser/presets/xai.d.ts +8 -0
  145. package/dist/browser/presets/xai.js +23 -0
  146. package/dist/browser/presets/youtube.d.ts +5 -0
  147. package/dist/browser/presets/youtube.js +20 -0
  148. package/dist/browser/recker.d.ts +8 -1
  149. package/dist/browser/recker.js +8 -2
  150. package/dist/browser/scrape/document.d.ts +5 -4
  151. package/dist/browser/scrape/document.js +89 -76
  152. package/dist/browser/scrape/element.d.ts +10 -8
  153. package/dist/browser/scrape/element.js +295 -81
  154. package/dist/browser/scrape/extractors.d.ts +11 -11
  155. package/dist/browser/scrape/extractors.js +145 -113
  156. package/dist/browser/scrape/parser/back.d.ts +1 -0
  157. package/dist/browser/scrape/parser/back.js +3 -0
  158. package/dist/browser/scrape/parser/index.d.ts +20 -0
  159. package/dist/browser/scrape/parser/index.js +19 -0
  160. package/dist/browser/scrape/parser/matcher.d.ts +30 -0
  161. package/dist/browser/scrape/parser/matcher.js +99 -0
  162. package/dist/browser/scrape/parser/nodes/comment.d.ts +12 -0
  163. package/dist/browser/scrape/parser/nodes/comment.js +21 -0
  164. package/dist/browser/scrape/parser/nodes/html.d.ts +110 -0
  165. package/dist/browser/scrape/parser/nodes/html.js +978 -0
  166. package/dist/browser/scrape/parser/nodes/node.d.ts +18 -0
  167. package/dist/browser/scrape/parser/nodes/node.js +31 -0
  168. package/dist/browser/scrape/parser/nodes/text.d.ts +14 -0
  169. package/dist/browser/scrape/parser/nodes/text.js +30 -0
  170. package/dist/browser/scrape/parser/nodes/type.d.ts +6 -0
  171. package/dist/browser/scrape/parser/nodes/type.js +7 -0
  172. package/dist/browser/scrape/parser/parse.d.ts +1 -0
  173. package/dist/browser/scrape/parser/parse.js +1 -0
  174. package/dist/browser/scrape/parser/valid.d.ts +2 -0
  175. package/dist/browser/scrape/parser/valid.js +5 -0
  176. package/dist/browser/scrape/parser/void-tag.d.ts +7 -0
  177. package/dist/browser/scrape/parser/void-tag.js +43 -0
  178. package/dist/browser/scrape/types.d.ts +7 -0
  179. package/dist/browser/seo/analyzer.d.ts +59 -0
  180. package/dist/browser/seo/analyzer.js +1399 -0
  181. package/dist/browser/seo/keywords.d.ts +16 -0
  182. package/dist/browser/seo/keywords.js +55 -0
  183. package/dist/browser/seo/rules/accessibility.d.ts +2 -0
  184. package/dist/browser/seo/rules/accessibility.js +733 -0
  185. package/dist/browser/seo/rules/ai-search.d.ts +2 -0
  186. package/dist/browser/seo/rules/ai-search.js +436 -0
  187. package/dist/browser/seo/rules/analytics.d.ts +2 -0
  188. package/dist/browser/seo/rules/analytics.js +306 -0
  189. package/dist/browser/seo/rules/best-practices.d.ts +2 -0
  190. package/dist/browser/seo/rules/best-practices.js +195 -0
  191. package/dist/browser/seo/rules/canonical.d.ts +12 -0
  192. package/dist/browser/seo/rules/canonical.js +270 -0
  193. package/dist/browser/seo/rules/content.d.ts +2 -0
  194. package/dist/browser/seo/rules/content.js +522 -0
  195. package/dist/browser/seo/rules/crawl.d.ts +2 -0
  196. package/dist/browser/seo/rules/crawl.js +435 -0
  197. package/dist/browser/seo/rules/cwv.d.ts +2 -0
  198. package/dist/browser/seo/rules/cwv.js +248 -0
  199. package/dist/browser/seo/rules/ecommerce.d.ts +2 -0
  200. package/dist/browser/seo/rules/ecommerce.js +312 -0
  201. package/dist/browser/seo/rules/i18n.d.ts +2 -0
  202. package/dist/browser/seo/rules/i18n.js +288 -0
  203. package/dist/browser/seo/rules/images.d.ts +2 -0
  204. package/dist/browser/seo/rules/images.js +255 -0
  205. package/dist/browser/seo/rules/index.d.ts +52 -0
  206. package/dist/browser/seo/rules/index.js +159 -0
  207. package/dist/browser/seo/rules/internal-linking.d.ts +2 -0
  208. package/dist/browser/seo/rules/internal-linking.js +394 -0
  209. package/dist/browser/seo/rules/links.d.ts +2 -0
  210. package/dist/browser/seo/rules/links.js +498 -0
  211. package/dist/browser/seo/rules/local.d.ts +2 -0
  212. package/dist/browser/seo/rules/local.js +289 -0
  213. package/dist/browser/seo/rules/meta.d.ts +2 -0
  214. package/dist/browser/seo/rules/meta.js +805 -0
  215. package/dist/browser/seo/rules/mobile.d.ts +2 -0
  216. package/dist/browser/seo/rules/mobile.js +161 -0
  217. package/dist/browser/seo/rules/performance.d.ts +2 -0
  218. package/dist/browser/seo/rules/performance.js +738 -0
  219. package/dist/browser/seo/rules/pwa.d.ts +2 -0
  220. package/dist/browser/seo/rules/pwa.js +299 -0
  221. package/dist/browser/seo/rules/readability.d.ts +2 -0
  222. package/dist/browser/seo/rules/readability.js +264 -0
  223. package/dist/browser/seo/rules/redirects.d.ts +16 -0
  224. package/dist/browser/seo/rules/redirects.js +199 -0
  225. package/dist/browser/seo/rules/resources.d.ts +2 -0
  226. package/dist/browser/seo/rules/resources.js +390 -0
  227. package/dist/browser/seo/rules/schema.d.ts +2 -0
  228. package/dist/browser/seo/rules/schema.js +379 -0
  229. package/dist/browser/seo/rules/security.d.ts +2 -0
  230. package/dist/browser/seo/rules/security.js +877 -0
  231. package/dist/browser/seo/rules/social.d.ts +2 -0
  232. package/dist/browser/seo/rules/social.js +603 -0
  233. package/dist/browser/seo/rules/structural.d.ts +2 -0
  234. package/dist/browser/seo/rules/structural.js +223 -0
  235. package/dist/browser/seo/rules/technical-advanced.d.ts +10 -0
  236. package/dist/browser/seo/rules/technical-advanced.js +289 -0
  237. package/dist/browser/seo/rules/technical.d.ts +2 -0
  238. package/dist/browser/seo/rules/technical.js +480 -0
  239. package/dist/browser/seo/rules/thresholds.d.ts +196 -0
  240. package/dist/browser/seo/rules/thresholds.js +118 -0
  241. package/dist/browser/seo/rules/types.d.ts +498 -0
  242. package/dist/browser/seo/rules/types.js +11 -0
  243. package/dist/browser/seo/types.d.ts +211 -0
  244. package/dist/browser/seo/types.js +1 -0
  245. package/dist/browser/transport/curl.d.ts +4 -0
  246. package/dist/browser/transport/curl.js +101 -0
  247. package/dist/browser/transport/undici.js +1 -2
  248. package/dist/browser/transport/worker.d.ts +18 -0
  249. package/dist/browser/transport/worker.js +278 -0
  250. package/dist/browser/types/index.d.ts +4 -1
  251. package/dist/browser/utils/binary-manager.d.ts +4 -0
  252. package/dist/browser/utils/binary-manager.js +72 -0
  253. package/dist/browser/utils/user-agent.js +2 -13
  254. package/dist/cache/indexed-db.d.ts +10 -0
  255. package/dist/cache/indexed-db.js +88 -0
  256. package/dist/cache/service-worker-cache.d.ts +18 -0
  257. package/dist/cache/service-worker-cache.js +103 -0
  258. package/dist/cli/commands/ai.d.ts +2 -0
  259. package/dist/cli/commands/ai.js +162 -0
  260. package/dist/cli/commands/bench.d.ts +2 -0
  261. package/dist/cli/commands/bench.js +51 -0
  262. package/dist/cli/commands/dns.d.ts +2 -0
  263. package/dist/cli/commands/dns.js +295 -0
  264. package/dist/cli/commands/har.d.ts +2 -0
  265. package/dist/cli/commands/har.js +171 -0
  266. package/dist/cli/commands/hls.d.ts +2 -0
  267. package/dist/cli/commands/hls.js +192 -0
  268. package/dist/cli/commands/network.d.ts +2 -0
  269. package/dist/cli/commands/network.js +288 -0
  270. package/dist/cli/commands/protocols.d.ts +2 -0
  271. package/dist/cli/commands/protocols.js +344 -0
  272. package/dist/cli/commands/scrape.d.ts +2 -0
  273. package/dist/cli/commands/scrape.js +176 -0
  274. package/dist/cli/commands/security.d.ts +2 -0
  275. package/dist/cli/commands/security.js +57 -0
  276. package/dist/cli/commands/seo.d.ts +2 -0
  277. package/dist/cli/commands/seo.js +125 -0
  278. package/dist/cli/commands/serve.d.ts +2 -0
  279. package/dist/cli/commands/serve.js +531 -0
  280. package/dist/cli/commands/spider.d.ts +3 -0
  281. package/dist/cli/commands/spider.js +456 -0
  282. package/dist/cli/commands/utils.d.ts +2 -0
  283. package/dist/cli/commands/utils.js +176 -0
  284. package/dist/cli/commands/vector.d.ts +2 -0
  285. package/dist/cli/commands/vector.js +158 -0
  286. package/dist/cli/handler.d.ts +2 -2
  287. package/dist/cli/handler.js +6 -6
  288. package/dist/cli/helpers.d.ts +7 -0
  289. package/dist/cli/helpers.js +128 -0
  290. package/dist/cli/index.js +96 -5228
  291. package/dist/cli/parser/help.d.ts +2 -0
  292. package/dist/cli/parser/help.js +52 -0
  293. package/dist/cli/parser/index.d.ts +3 -0
  294. package/dist/cli/parser/index.js +3 -0
  295. package/dist/cli/parser/parser.d.ts +4 -0
  296. package/dist/cli/parser/parser.js +146 -0
  297. package/dist/cli/parser/types.d.ts +41 -0
  298. package/dist/cli/parser/types.js +1 -0
  299. package/dist/cli/presets.d.ts +1 -1
  300. package/dist/cli/presets.js +1 -1
  301. package/dist/cli/router.d.ts +36 -0
  302. package/dist/cli/router.js +195 -0
  303. package/dist/cli/tui/ai-chat.js +1 -1
  304. package/dist/cli/tui/commands/context.d.ts +9 -0
  305. package/dist/cli/tui/commands/context.js +1 -0
  306. package/dist/cli/tui/commands/dns.d.ts +10 -0
  307. package/dist/cli/tui/commands/dns.js +461 -0
  308. package/dist/cli/tui/commands/hls.d.ts +2 -0
  309. package/dist/cli/tui/commands/hls.js +162 -0
  310. package/dist/cli/tui/commands/ip.d.ts +2 -0
  311. package/dist/cli/tui/commands/ip.js +45 -0
  312. package/dist/cli/tui/commands/network.d.ts +3 -0
  313. package/dist/cli/tui/commands/network.js +81 -0
  314. package/dist/cli/tui/commands/protocols.d.ts +6 -0
  315. package/dist/cli/tui/commands/protocols.js +531 -0
  316. package/dist/cli/tui/commands/security.d.ts +2 -0
  317. package/dist/cli/tui/commands/security.js +48 -0
  318. package/dist/cli/tui/commands/seo.d.ts +2 -0
  319. package/dist/cli/tui/commands/seo.js +74 -0
  320. package/dist/cli/tui/context.d.ts +12 -0
  321. package/dist/cli/tui/context.js +1 -0
  322. package/dist/cli/tui/shell.d.ts +11 -20
  323. package/dist/cli/tui/shell.js +216 -1873
  324. package/dist/constants/user-agents.d.ts +7 -0
  325. package/dist/constants/user-agents.js +7 -0
  326. package/dist/core/client.d.ts +2 -0
  327. package/dist/core/client.js +19 -1
  328. package/dist/index.d.ts +1 -0
  329. package/dist/index.js +1 -0
  330. package/dist/mcp/cli.js +2 -3
  331. package/dist/mcp/data/embeddings.json +1 -1
  332. package/dist/mcp/tools/network.js +298 -158
  333. package/dist/plugins/har-player.d.ts +23 -0
  334. package/dist/plugins/har-player.js +49 -0
  335. package/dist/plugins/har-recorder.d.ts +37 -3
  336. package/dist/plugins/har-recorder.js +116 -63
  337. package/dist/plugins/network-simulation.d.ts +7 -0
  338. package/dist/plugins/network-simulation.js +13 -0
  339. package/dist/presets/android.d.ts +2 -0
  340. package/dist/presets/android.js +16 -0
  341. package/dist/presets/chaturbate.d.ts +2 -0
  342. package/dist/presets/chaturbate.js +17 -0
  343. package/dist/presets/elevenlabs.d.ts +6 -0
  344. package/dist/presets/elevenlabs.js +20 -0
  345. package/dist/presets/enhancers.d.ts +20 -0
  346. package/dist/presets/enhancers.js +85 -0
  347. package/dist/presets/hubspot.d.ts +9 -0
  348. package/dist/presets/hubspot.js +28 -0
  349. package/dist/presets/index.d.ts +10 -0
  350. package/dist/presets/index.js +10 -0
  351. package/dist/presets/ios.d.ts +2 -0
  352. package/dist/presets/ios.js +13 -0
  353. package/dist/presets/pinecone.d.ts +8 -0
  354. package/dist/presets/pinecone.js +42 -0
  355. package/dist/presets/registry.js +60 -0
  356. package/dist/presets/sendgrid.d.ts +6 -0
  357. package/dist/presets/sendgrid.js +20 -0
  358. package/dist/presets/sentry.d.ts +11 -0
  359. package/dist/presets/sentry.js +48 -0
  360. package/dist/presets/square.d.ts +10 -0
  361. package/dist/presets/square.js +33 -0
  362. package/dist/recker.d.ts +3 -0
  363. package/dist/recker.js +4 -0
  364. package/dist/scrape/document.d.ts +5 -4
  365. package/dist/scrape/document.js +89 -76
  366. package/dist/scrape/element.d.ts +10 -8
  367. package/dist/scrape/element.js +295 -81
  368. package/dist/scrape/extractors.d.ts +11 -11
  369. package/dist/scrape/extractors.js +145 -113
  370. package/dist/scrape/index.d.ts +2 -0
  371. package/dist/scrape/index.js +1 -0
  372. package/dist/scrape/parser/back.d.ts +1 -0
  373. package/dist/scrape/parser/back.js +3 -0
  374. package/dist/scrape/parser/index.d.ts +20 -0
  375. package/dist/scrape/parser/index.js +19 -0
  376. package/dist/scrape/parser/matcher.d.ts +30 -0
  377. package/dist/scrape/parser/matcher.js +99 -0
  378. package/dist/scrape/parser/nodes/comment.d.ts +12 -0
  379. package/dist/scrape/parser/nodes/comment.js +21 -0
  380. package/dist/scrape/parser/nodes/html.d.ts +110 -0
  381. package/dist/scrape/parser/nodes/html.js +978 -0
  382. package/dist/scrape/parser/nodes/node.d.ts +18 -0
  383. package/dist/scrape/parser/nodes/node.js +31 -0
  384. package/dist/scrape/parser/nodes/text.d.ts +14 -0
  385. package/dist/scrape/parser/nodes/text.js +30 -0
  386. package/dist/scrape/parser/nodes/type.d.ts +6 -0
  387. package/dist/scrape/parser/nodes/type.js +7 -0
  388. package/dist/scrape/parser/parse.d.ts +1 -0
  389. package/dist/scrape/parser/parse.js +1 -0
  390. package/dist/scrape/parser/valid.d.ts +2 -0
  391. package/dist/scrape/parser/valid.js +5 -0
  392. package/dist/scrape/parser/void-tag.d.ts +7 -0
  393. package/dist/scrape/parser/void-tag.js +43 -0
  394. package/dist/scrape/spider.d.ts +19 -0
  395. package/dist/scrape/spider.js +28 -3
  396. package/dist/scrape/types.d.ts +7 -0
  397. package/dist/seo/analyzer.d.ts +15 -5
  398. package/dist/seo/analyzer.js +636 -175
  399. package/dist/seo/formatter.d.ts +16 -0
  400. package/dist/seo/formatter.js +228 -0
  401. package/dist/seo/index.d.ts +2 -0
  402. package/dist/seo/index.js +1 -0
  403. package/dist/seo/keywords.d.ts +16 -0
  404. package/dist/seo/keywords.js +55 -0
  405. package/dist/seo/rules/accessibility.js +96 -57
  406. package/dist/seo/rules/ai-search.js +44 -31
  407. package/dist/seo/rules/analytics.d.ts +2 -0
  408. package/dist/seo/rules/analytics.js +306 -0
  409. package/dist/seo/rules/best-practices.js +21 -14
  410. package/dist/seo/rules/canonical.js +53 -32
  411. package/dist/seo/rules/content.js +317 -31
  412. package/dist/seo/rules/crawl.js +55 -40
  413. package/dist/seo/rules/cwv.js +21 -15
  414. package/dist/seo/rules/ecommerce.js +82 -22
  415. package/dist/seo/rules/i18n.js +75 -36
  416. package/dist/seo/rules/images.js +109 -30
  417. package/dist/seo/rules/index.js +2 -0
  418. package/dist/seo/rules/internal-linking.js +58 -39
  419. package/dist/seo/rules/links.js +79 -52
  420. package/dist/seo/rules/local.js +49 -25
  421. package/dist/seo/rules/meta.js +339 -81
  422. package/dist/seo/rules/mobile.js +112 -2
  423. package/dist/seo/rules/performance.js +434 -66
  424. package/dist/seo/rules/pwa.js +36 -39
  425. package/dist/seo/rules/readability.js +31 -22
  426. package/dist/seo/rules/redirects.js +21 -15
  427. package/dist/seo/rules/resources.js +59 -42
  428. package/dist/seo/rules/schema.js +333 -8
  429. package/dist/seo/rules/security.js +142 -80
  430. package/dist/seo/rules/social.js +277 -47
  431. package/dist/seo/rules/structural.js +87 -19
  432. package/dist/seo/rules/technical-advanced.js +30 -24
  433. package/dist/seo/rules/technical.js +243 -42
  434. package/dist/seo/rules/types.d.ts +53 -1
  435. package/dist/seo/seo-spider.d.ts +22 -0
  436. package/dist/seo/seo-spider.js +77 -13
  437. package/dist/seo/types.d.ts +8 -1
  438. package/dist/seo/validators/llms-txt.js +19 -0
  439. package/dist/seo/validators/rss.d.ts +11 -0
  440. package/dist/seo/validators/rss.js +93 -0
  441. package/dist/seo/validators/sitemap.js +36 -26
  442. package/dist/transport/curl.d.ts +4 -0
  443. package/dist/transport/curl.js +101 -0
  444. package/dist/transport/udp.js +0 -1
  445. package/dist/transport/undici.js +1 -2
  446. package/dist/transport/worker.d.ts +18 -0
  447. package/dist/transport/worker.js +278 -0
  448. package/dist/types/index.d.ts +4 -1
  449. package/dist/utils/binary-manager.d.ts +4 -0
  450. package/dist/utils/binary-manager.js +72 -0
  451. package/dist/utils/optional-require.d.ts +7 -8
  452. package/dist/utils/optional-require.js +2 -21
  453. package/dist/utils/upload.d.ts +6 -0
  454. package/dist/utils/upload.js +11 -0
  455. package/dist/utils/user-agent.js +2 -13
  456. package/dist/version.js +1 -1
  457. package/package.json +12 -6
  458. package/dist/browser/utils/optional-require.d.ts +0 -19
  459. package/dist/browser/utils/optional-require.js +0 -105
@@ -0,0 +1,733 @@
1
+ import { createResult } from './types.js';
2
+ export const accessibilityRules = [
3
+ {
4
+ id: 'a11y-buttons-accessible-name',
5
+ name: 'Buttons Accessible Name',
6
+ category: 'accessibility',
7
+ severity: 'error',
8
+ description: 'Buttons must have an accessible name (text content, aria-label, or title)',
9
+ check: (ctx) => {
10
+ if (ctx.buttonsWithoutAriaLabel === undefined) {
11
+ return createResult({ id: 'a11y-buttons-accessible-name', name: 'Buttons Accessible Name', category: 'accessibility', severity: 'error' }, 'info', 'Not applicable (no button data available)', { recommendation: 'This rule checks that all buttons have accessible names via text content, aria-label, or title attributes' });
12
+ }
13
+ const count = ctx.buttonsWithoutAriaLabel;
14
+ if (count > 0) {
15
+ return createResult({ id: 'a11y-buttons-accessible-name', name: 'Buttons Accessible Name', category: 'accessibility', severity: 'error' }, 'fail', `${count} button(s) do not have an accessible name`, {
16
+ value: count,
17
+ recommendation: 'Add text content, aria-label, aria-labelledby, or title to all buttons',
18
+ evidence: {
19
+ found: count,
20
+ expected: 0,
21
+ impact: 'Screen reader users cannot determine the button purpose',
22
+ learnMore: 'https://dequeuniversity.com/rules/axe/4.4/button-name',
23
+ },
24
+ });
25
+ }
26
+ return createResult({ id: 'a11y-buttons-accessible-name', name: 'Buttons Accessible Name', category: 'accessibility', severity: 'error' }, 'pass', 'All buttons have accessible names');
27
+ },
28
+ },
29
+ {
30
+ id: 'a11y-images-alt',
31
+ name: 'Image Alt Attributes',
32
+ category: 'accessibility',
33
+ severity: 'error',
34
+ description: 'Image elements must have [alt] attributes',
35
+ check: (ctx) => {
36
+ if (ctx.imagesWithoutAlt === undefined) {
37
+ return createResult({ id: 'a11y-images-alt', name: 'Image Alt Attributes', category: 'accessibility', severity: 'error' }, 'info', 'Not applicable (no image data available)', { recommendation: 'This rule checks that all images have alt attributes for screen readers' });
38
+ }
39
+ const count = ctx.imagesWithoutAlt;
40
+ if (count > 0) {
41
+ return createResult({ id: 'a11y-images-alt', name: 'Image Alt Attributes', category: 'accessibility', severity: 'error' }, 'fail', `${count} image(s) do not have [alt] attributes`, {
42
+ value: count,
43
+ recommendation: 'Add alt="" for decorative images or descriptive alt text for informative images',
44
+ evidence: {
45
+ found: count,
46
+ expected: 0,
47
+ impact: 'Screen readers cannot convey image content to blind users',
48
+ learnMore: 'https://dequeuniversity.com/rules/axe/4.4/image-alt',
49
+ },
50
+ });
51
+ }
52
+ return createResult({ id: 'a11y-images-alt', name: 'Image Alt Attributes', category: 'accessibility', severity: 'error' }, 'pass', 'All images have [alt] attributes');
53
+ },
54
+ },
55
+ {
56
+ id: 'a11y-links-discernible-name',
57
+ name: 'Links Discernible Name',
58
+ category: 'accessibility',
59
+ severity: 'error',
60
+ description: 'Links must have a discernible name',
61
+ check: (ctx) => {
62
+ if (ctx.linksWithoutText === undefined && ctx.linksWithoutAriaLabel === undefined) {
63
+ return createResult({ id: 'a11y-links-discernible-name', name: 'Links Discernible Name', category: 'accessibility', severity: 'error' }, 'info', 'Not applicable (no link data available)', { recommendation: 'This rule checks that all links have discernible names via text content, aria-label, or title' });
64
+ }
65
+ const linksNoText = ctx.linksWithoutText ?? 0;
66
+ const linksNoAria = ctx.linksWithoutAriaLabel ?? 0;
67
+ const count = Math.max(linksNoText, linksNoAria);
68
+ if (count > 0) {
69
+ return createResult({ id: 'a11y-links-discernible-name', name: 'Links Discernible Name', category: 'accessibility', severity: 'error' }, 'fail', `${count} link(s) do not have a discernible name`, {
70
+ value: count,
71
+ recommendation: 'Add text content, aria-label, aria-labelledby, or title to links',
72
+ evidence: {
73
+ found: count,
74
+ expected: 0,
75
+ impact: 'Screen reader users cannot understand link destinations',
76
+ learnMore: 'https://dequeuniversity.com/rules/axe/4.4/link-name',
77
+ },
78
+ });
79
+ }
80
+ return createResult({ id: 'a11y-links-discernible-name', name: 'Links Discernible Name', category: 'accessibility', severity: 'error' }, 'pass', 'All links have discernible names');
81
+ },
82
+ },
83
+ {
84
+ id: 'a11y-form-labels',
85
+ name: 'Form Input Labels',
86
+ category: 'accessibility',
87
+ severity: 'error',
88
+ description: 'Form elements must have associated labels',
89
+ check: (ctx) => {
90
+ if (ctx.inputsWithoutLabel === undefined) {
91
+ return createResult({ id: 'a11y-form-labels', name: 'Form Input Labels', category: 'accessibility', severity: 'error' }, 'info', 'Not applicable (no form input data available)', { recommendation: 'This rule checks that all form inputs have associated labels for accessibility' });
92
+ }
93
+ const count = ctx.inputsWithoutLabel;
94
+ if (count > 0) {
95
+ return createResult({ id: 'a11y-form-labels', name: 'Form Input Labels', category: 'accessibility', severity: 'error' }, 'fail', `${count} form input(s) without associated label`, {
96
+ value: count,
97
+ recommendation: 'Add <label for="id">, aria-label, or aria-labelledby to all form inputs',
98
+ evidence: {
99
+ found: count,
100
+ expected: 0,
101
+ impact: 'Users cannot identify input purpose, critical for screen reader users',
102
+ learnMore: 'https://dequeuniversity.com/rules/axe/4.4/label',
103
+ },
104
+ });
105
+ }
106
+ return createResult({ id: 'a11y-form-labels', name: 'Form Input Labels', category: 'accessibility', severity: 'error' }, 'pass', 'All form inputs have associated labels');
107
+ },
108
+ },
109
+ {
110
+ id: 'a11y-heading-order',
111
+ name: 'Heading Order',
112
+ category: 'accessibility',
113
+ severity: 'warning',
114
+ description: 'Heading elements should be in sequentially-descending order',
115
+ check: (ctx) => {
116
+ if (ctx.headingHierarchyValid === undefined) {
117
+ return createResult({ id: 'a11y-heading-order', name: 'Heading Order', category: 'accessibility', severity: 'warning' }, 'info', 'Not applicable (no heading data available)', { recommendation: 'This rule checks that headings follow a logical order (H1 → H2 → H3) without skipping levels' });
118
+ }
119
+ if (!ctx.headingHierarchyValid) {
120
+ return createResult({ id: 'a11y-heading-order', name: 'Heading Order', category: 'accessibility', severity: 'warning' }, 'warn', 'Heading elements are not in sequentially-descending order', {
121
+ recommendation: 'Ensure headings follow a logical order (H1 → H2 → H3) without skipping levels',
122
+ evidence: {
123
+ found: ctx.headingSkippedLevels?.join(', ') || 'Skipped levels detected',
124
+ expected: 'Sequential heading hierarchy',
125
+ impact: 'Impacts keyboard navigation for screen reader users',
126
+ learnMore: 'https://dequeuniversity.com/rules/axe/4.4/heading-order',
127
+ },
128
+ });
129
+ }
130
+ return createResult({ id: 'a11y-heading-order', name: 'Heading Order', category: 'accessibility', severity: 'warning' }, 'pass', 'Headings are in sequentially-descending order');
131
+ },
132
+ },
133
+ {
134
+ id: 'a11y-tabindex',
135
+ name: 'Tabindex Values',
136
+ category: 'accessibility',
137
+ severity: 'warning',
138
+ description: 'No element should have a [tabindex] value greater than 0',
139
+ check: (ctx) => {
140
+ if (ctx.elementsWithHighTabindex === undefined) {
141
+ return createResult({ id: 'a11y-tabindex', name: 'Tabindex Values', category: 'accessibility', severity: 'warning' }, 'info', 'Not applicable (no tabindex data available)', { recommendation: 'This rule checks that no element has a tabindex value greater than 0, which can confuse keyboard users' });
142
+ }
143
+ const count = ctx.elementsWithHighTabindex;
144
+ if (count > 0) {
145
+ return createResult({ id: 'a11y-tabindex', name: 'Tabindex Values', category: 'accessibility', severity: 'warning' }, 'warn', `${count} element(s) have tabindex > 0`, {
146
+ value: count,
147
+ recommendation: 'Remove positive tabindex values; use tabindex="0" or "-1" instead',
148
+ evidence: {
149
+ found: count,
150
+ expected: 0,
151
+ impact: 'Creates confusing focus order for keyboard users',
152
+ learnMore: 'https://dequeuniversity.com/rules/axe/4.4/tabindex',
153
+ },
154
+ });
155
+ }
156
+ return createResult({ id: 'a11y-tabindex', name: 'Tabindex Values', category: 'accessibility', severity: 'warning' }, 'pass', 'No elements have tabindex > 0');
157
+ },
158
+ },
159
+ {
160
+ id: 'a11y-aria-valid-attrs',
161
+ name: 'Valid ARIA Attributes',
162
+ category: 'accessibility',
163
+ severity: 'error',
164
+ description: '[aria-*] attributes must be valid and not misspelled',
165
+ check: (ctx) => {
166
+ if (ctx.invalidAriaAttributes === undefined) {
167
+ return createResult({ id: 'a11y-aria-valid-attrs', name: 'Valid ARIA Attributes', category: 'accessibility', severity: 'error' }, 'info', 'Not applicable (no ARIA attribute data available)', { recommendation: 'This rule checks that all aria-* attributes are valid and not misspelled' });
168
+ }
169
+ const count = ctx.invalidAriaAttributes;
170
+ if (count > 0) {
171
+ return createResult({ id: 'a11y-aria-valid-attrs', name: 'Valid ARIA Attributes', category: 'accessibility', severity: 'error' }, 'fail', `${count} invalid or misspelled aria-* attribute(s) found`, {
172
+ value: count,
173
+ recommendation: 'Verify ARIA attributes are spelled correctly and valid',
174
+ evidence: {
175
+ found: count,
176
+ expected: 0,
177
+ impact: 'Invalid ARIA provides no accessibility benefit',
178
+ learnMore: 'https://dequeuniversity.com/rules/axe/4.4/aria-valid-attr',
179
+ },
180
+ });
181
+ }
182
+ return createResult({ id: 'a11y-aria-valid-attrs', name: 'Valid ARIA Attributes', category: 'accessibility', severity: 'error' }, 'pass', 'All ARIA attributes are valid');
183
+ },
184
+ },
185
+ {
186
+ id: 'a11y-aria-valid-values',
187
+ name: 'ARIA Attribute Values',
188
+ category: 'accessibility',
189
+ severity: 'error',
190
+ description: '[aria-*] attributes must have valid values',
191
+ check: (ctx) => {
192
+ if (ctx.invalidAriaValues === undefined) {
193
+ return createResult({ id: 'a11y-aria-valid-values', name: 'ARIA Attribute Values', category: 'accessibility', severity: 'error' }, 'info', 'Not applicable (no ARIA value data available)', { recommendation: 'This rule checks that all aria-* attributes have valid values' });
194
+ }
195
+ const count = ctx.invalidAriaValues;
196
+ if (count > 0) {
197
+ return createResult({ id: 'a11y-aria-valid-values', name: 'ARIA Attribute Values', category: 'accessibility', severity: 'error' }, 'fail', `${count} aria-* attribute(s) have invalid values`, {
198
+ value: count,
199
+ recommendation: 'Use valid values for ARIA attributes (e.g., aria-live="polite")',
200
+ evidence: {
201
+ found: count,
202
+ expected: 0,
203
+ impact: 'Invalid values prevent assistive technologies from working correctly',
204
+ learnMore: 'https://dequeuniversity.com/rules/axe/4.4/aria-valid-attr-value',
205
+ },
206
+ });
207
+ }
208
+ return createResult({ id: 'a11y-aria-valid-values', name: 'ARIA Attribute Values', category: 'accessibility', severity: 'error' }, 'pass', 'All ARIA attribute values are valid');
209
+ },
210
+ },
211
+ {
212
+ id: 'a11y-aria-roles',
213
+ name: 'Valid ARIA Roles',
214
+ category: 'accessibility',
215
+ severity: 'error',
216
+ description: '[role] values must be valid',
217
+ check: (ctx) => {
218
+ if (ctx.invalidAriaRoles === undefined) {
219
+ return createResult({ id: 'a11y-aria-roles', name: 'Valid ARIA Roles', category: 'accessibility', severity: 'error' }, 'info', 'Not applicable (no ARIA role data available)', { recommendation: 'This rule checks that all role values are valid ARIA roles' });
220
+ }
221
+ const count = ctx.invalidAriaRoles;
222
+ if (count > 0) {
223
+ return createResult({ id: 'a11y-aria-roles', name: 'Valid ARIA Roles', category: 'accessibility', severity: 'error' }, 'fail', `${count} invalid [role] value(s) found`, {
224
+ value: count,
225
+ recommendation: 'Use valid ARIA role values (e.g., button, dialog, navigation)',
226
+ evidence: {
227
+ found: count,
228
+ expected: 0,
229
+ impact: 'Invalid roles confuse assistive technologies',
230
+ learnMore: 'https://dequeuniversity.com/rules/axe/4.4/aria-roles',
231
+ },
232
+ });
233
+ }
234
+ return createResult({ id: 'a11y-aria-roles', name: 'Valid ARIA Roles', category: 'accessibility', severity: 'error' }, 'pass', 'All [role] values are valid');
235
+ },
236
+ },
237
+ {
238
+ id: 'a11y-aria-required-attrs',
239
+ name: 'Required ARIA Attributes',
240
+ category: 'accessibility',
241
+ severity: 'error',
242
+ description: '[role]s must have all required [aria-*] attributes',
243
+ check: (ctx) => {
244
+ if (ctx.missingRequiredAriaAttrs === undefined) {
245
+ return createResult({ id: 'a11y-aria-required-attrs', name: 'Required ARIA Attributes', category: 'accessibility', severity: 'error' }, 'info', 'Not applicable (no required ARIA attribute data available)', { recommendation: 'This rule checks that elements with ARIA roles have all required aria-* attributes' });
246
+ }
247
+ const count = ctx.missingRequiredAriaAttrs;
248
+ if (count > 0) {
249
+ return createResult({ id: 'a11y-aria-required-attrs', name: 'Required ARIA Attributes', category: 'accessibility', severity: 'error' }, 'fail', `${count} element(s) with roles missing required aria-* attributes`, {
250
+ value: count,
251
+ recommendation: 'Add required ARIA attributes for each role (e.g., role="slider" requires aria-valuenow)',
252
+ evidence: {
253
+ found: count,
254
+ expected: 0,
255
+ impact: 'Missing attributes break assistive technology functionality',
256
+ learnMore: 'https://dequeuniversity.com/rules/axe/4.4/aria-required-attr',
257
+ },
258
+ });
259
+ }
260
+ return createResult({ id: 'a11y-aria-required-attrs', name: 'Required ARIA Attributes', category: 'accessibility', severity: 'error' }, 'pass', 'All roles have required ARIA attributes');
261
+ },
262
+ },
263
+ {
264
+ id: 'a11y-aria-hidden-body',
265
+ name: 'ARIA Hidden Body',
266
+ category: 'accessibility',
267
+ severity: 'error',
268
+ description: '[aria-hidden="true"] must not be present on the document <body>',
269
+ check: (ctx) => {
270
+ if (ctx.hasAriaHiddenBody === undefined) {
271
+ return createResult({ id: 'a11y-aria-hidden-body', name: 'ARIA Hidden Body', category: 'accessibility', severity: 'error' }, 'info', 'Not applicable (no body element data available)', { recommendation: 'This rule checks that aria-hidden is not present on the document body' });
272
+ }
273
+ if (ctx.hasAriaHiddenBody) {
274
+ return createResult({ id: 'a11y-aria-hidden-body', name: 'ARIA Hidden Body', category: 'accessibility', severity: 'error' }, 'fail', 'aria-hidden="true" is present on document <body>', {
275
+ recommendation: 'Remove aria-hidden from <body> element',
276
+ evidence: {
277
+ found: 'aria-hidden="true" on body',
278
+ expected: 'No aria-hidden on body',
279
+ impact: 'Entire page content hidden from assistive technologies',
280
+ learnMore: 'https://dequeuniversity.com/rules/axe/4.4/aria-hidden-body',
281
+ },
282
+ });
283
+ }
284
+ return createResult({ id: 'a11y-aria-hidden-body', name: 'ARIA Hidden Body', category: 'accessibility', severity: 'error' }, 'pass', 'aria-hidden is not present on <body>');
285
+ },
286
+ },
287
+ {
288
+ id: 'a11y-aria-hidden-focus',
289
+ name: 'ARIA Hidden Focusable',
290
+ category: 'accessibility',
291
+ severity: 'error',
292
+ description: '[aria-hidden="true"] elements must not contain focusable descendants',
293
+ check: (ctx) => {
294
+ if (ctx.ariaHiddenFocusableCount === undefined) {
295
+ return createResult({ id: 'a11y-aria-hidden-focus', name: 'ARIA Hidden Focusable', category: 'accessibility', severity: 'error' }, 'info', 'Not applicable (no aria-hidden focusable data available)', { recommendation: 'This rule checks that aria-hidden elements do not contain focusable descendants' });
296
+ }
297
+ const count = ctx.ariaHiddenFocusableCount;
298
+ if (count > 0) {
299
+ return createResult({ id: 'a11y-aria-hidden-focus', name: 'ARIA Hidden Focusable', category: 'accessibility', severity: 'error' }, 'fail', `${count} aria-hidden element(s) contain focusable descendants`, {
300
+ value: count,
301
+ recommendation: 'Remove focusable elements from aria-hidden containers or remove aria-hidden',
302
+ evidence: {
303
+ found: count,
304
+ expected: 0,
305
+ impact: 'Focus can move to invisible elements, confusing users',
306
+ learnMore: 'https://dequeuniversity.com/rules/axe/4.4/aria-hidden-focus',
307
+ },
308
+ });
309
+ }
310
+ return createResult({ id: 'a11y-aria-hidden-focus', name: 'ARIA Hidden Focusable', category: 'accessibility', severity: 'error' }, 'pass', 'No focusable elements inside aria-hidden containers');
311
+ },
312
+ },
313
+ {
314
+ id: 'a11y-aria-deprecated',
315
+ name: 'Deprecated ARIA Roles',
316
+ category: 'accessibility',
317
+ severity: 'warning',
318
+ description: 'Deprecated ARIA roles should not be used',
319
+ check: (ctx) => {
320
+ if (ctx.deprecatedAriaRoles === undefined) {
321
+ return createResult({ id: 'a11y-aria-deprecated', name: 'Deprecated ARIA Roles', category: 'accessibility', severity: 'warning' }, 'info', 'Not applicable (no deprecated ARIA role data available)', { recommendation: 'This rule checks that deprecated ARIA roles are not used' });
322
+ }
323
+ const count = ctx.deprecatedAriaRoles;
324
+ if (count > 0) {
325
+ return createResult({ id: 'a11y-aria-deprecated', name: 'Deprecated ARIA Roles', category: 'accessibility', severity: 'warning' }, 'warn', `${count} deprecated ARIA role(s) found`, {
326
+ value: count,
327
+ recommendation: 'Replace deprecated roles with current alternatives',
328
+ evidence: {
329
+ found: count,
330
+ expected: 0,
331
+ impact: 'Deprecated roles may not work in future browsers',
332
+ learnMore: 'https://www.w3.org/TR/wai-aria-1.2/',
333
+ },
334
+ });
335
+ }
336
+ return createResult({ id: 'a11y-aria-deprecated', name: 'Deprecated ARIA Roles', category: 'accessibility', severity: 'warning' }, 'pass', 'No deprecated ARIA roles used');
337
+ },
338
+ },
339
+ {
340
+ id: 'a11y-aria-ids-unique',
341
+ name: 'ARIA IDs Unique',
342
+ category: 'accessibility',
343
+ severity: 'error',
344
+ description: 'ARIA IDs must be unique',
345
+ check: (ctx) => {
346
+ if (ctx.duplicateAriaIds === undefined) {
347
+ return createResult({ id: 'a11y-aria-ids-unique', name: 'ARIA IDs Unique', category: 'accessibility', severity: 'error' }, 'info', 'Not applicable (no ARIA ID data available)', { recommendation: 'This rule checks that all IDs referenced by aria-labelledby, aria-describedby, etc. are unique' });
348
+ }
349
+ const count = ctx.duplicateAriaIds;
350
+ if (count > 0) {
351
+ return createResult({ id: 'a11y-aria-ids-unique', name: 'ARIA IDs Unique', category: 'accessibility', severity: 'error' }, 'fail', `${count} duplicate ARIA ID(s) found`, {
352
+ value: count,
353
+ recommendation: 'Ensure all IDs referenced by aria-labelledby, aria-describedby, etc. are unique',
354
+ evidence: {
355
+ found: count,
356
+ expected: 0,
357
+ impact: 'References to duplicate IDs produce unpredictable behavior',
358
+ learnMore: 'https://dequeuniversity.com/rules/axe/4.4/duplicate-id-aria',
359
+ },
360
+ });
361
+ }
362
+ return createResult({ id: 'a11y-aria-ids-unique', name: 'ARIA IDs Unique', category: 'accessibility', severity: 'error' }, 'pass', 'All ARIA IDs are unique');
363
+ },
364
+ },
365
+ {
366
+ id: 'a11y-dialog-name',
367
+ name: 'Dialog Accessible Name',
368
+ category: 'accessibility',
369
+ severity: 'error',
370
+ description: 'Elements with role="dialog" or role="alertdialog" must have accessible names',
371
+ check: (ctx) => {
372
+ if (ctx.dialogsWithoutName === undefined) {
373
+ return createResult({ id: 'a11y-dialog-name', name: 'Dialog Accessible Name', category: 'accessibility', severity: 'error' }, 'info', 'Not applicable (no dialog data available)', { recommendation: 'This rule checks that elements with role="dialog" or role="alertdialog" have accessible names' });
374
+ }
375
+ const count = ctx.dialogsWithoutName;
376
+ if (count > 0) {
377
+ return createResult({ id: 'a11y-dialog-name', name: 'Dialog Accessible Name', category: 'accessibility', severity: 'error' }, 'fail', `${count} dialog(s) without accessible name`, {
378
+ value: count,
379
+ recommendation: 'Add aria-label or aria-labelledby to dialog elements',
380
+ evidence: {
381
+ found: count,
382
+ expected: 0,
383
+ impact: 'Screen reader users cannot identify dialog purpose',
384
+ learnMore: 'https://dequeuniversity.com/rules/axe/4.4/aria-dialog-name',
385
+ },
386
+ });
387
+ }
388
+ return createResult({ id: 'a11y-dialog-name', name: 'Dialog Accessible Name', category: 'accessibility', severity: 'error' }, 'pass', 'All dialogs have accessible names');
389
+ },
390
+ },
391
+ {
392
+ id: 'a11y-main-landmark',
393
+ name: 'Main Landmark',
394
+ category: 'accessibility',
395
+ severity: 'warning',
396
+ description: 'Document should have a main landmark',
397
+ check: (ctx) => {
398
+ if (ctx.hasMain === undefined) {
399
+ return createResult({ id: 'a11y-main-landmark', name: 'Main Landmark', category: 'accessibility', severity: 'warning' }, 'info', 'Not applicable (no main landmark data available)', { recommendation: 'This rule checks that document has a <main> element or role="main" for screen reader navigation' });
400
+ }
401
+ if (!ctx.hasMain) {
402
+ return createResult({ id: 'a11y-main-landmark', name: 'Main Landmark', category: 'accessibility', severity: 'warning' }, 'warn', 'Document does not have a <main> landmark', {
403
+ recommendation: 'Add a <main> element or role="main" to identify the main content area',
404
+ evidence: {
405
+ found: 'No <main> element or role="main"',
406
+ expected: '<main> or role="main"',
407
+ impact: 'Screen reader users cannot quickly navigate to main content',
408
+ example: '<main>\n <!-- Main content here -->\n</main>',
409
+ learnMore: 'https://dequeuniversity.com/rules/axe/4.4/landmark-one-main',
410
+ },
411
+ });
412
+ }
413
+ return createResult({ id: 'a11y-main-landmark', name: 'Main Landmark', category: 'accessibility', severity: 'warning' }, 'pass', 'Document has a main landmark');
414
+ },
415
+ },
416
+ {
417
+ id: 'a11y-skip-link',
418
+ name: 'Skip Link',
419
+ category: 'accessibility',
420
+ severity: 'info',
421
+ description: 'Page should contain a heading, skip link, or landmark region',
422
+ check: (ctx) => {
423
+ if (ctx.hasSkipLink === undefined && ctx.hasMain === undefined && ctx.h1Count === undefined) {
424
+ return createResult({ id: 'a11y-skip-link', name: 'Skip Link', category: 'accessibility', severity: 'info' }, 'info', 'Not applicable (no navigation bypass data available)', { recommendation: 'This rule checks that page contains a skip link, main landmark, or heading for keyboard navigation' });
425
+ }
426
+ const hasSkip = ctx.hasSkipLink ?? false;
427
+ const hasMain = ctx.hasMain ?? false;
428
+ const hasH1 = (ctx.h1Count ?? 0) > 0;
429
+ if (!hasSkip && !hasMain && !hasH1) {
430
+ return createResult({ id: 'a11y-skip-link', name: 'Skip Link', category: 'accessibility', severity: 'info' }, 'info', 'No skip link, main landmark, or heading found', {
431
+ recommendation: 'Add a skip link, <main> landmark, or at least one heading for navigation',
432
+ evidence: {
433
+ found: 'No skip link, <main>, or heading',
434
+ expected: 'Skip link, <main>, or heading',
435
+ impact: 'Keyboard users must tab through all navigation to reach content',
436
+ example: '<a href="#main-content" class="skip-link">Skip to main content</a>',
437
+ learnMore: 'https://dequeuniversity.com/rules/axe/4.4/bypass',
438
+ },
439
+ });
440
+ }
441
+ return createResult({ id: 'a11y-skip-link', name: 'Skip Link', category: 'accessibility', severity: 'info' }, 'pass', 'Page has navigation bypass mechanism');
442
+ },
443
+ },
444
+ {
445
+ id: 'a11y-table-caption',
446
+ name: 'Table Caption',
447
+ category: 'accessibility',
448
+ severity: 'info',
449
+ description: 'Data tables should have caption or aria-label',
450
+ check: (ctx) => {
451
+ if (ctx.tablesWithoutCaption === undefined) {
452
+ return createResult({ id: 'a11y-table-caption', name: 'Table Caption', category: 'accessibility', severity: 'info' }, 'info', 'Not applicable (no table data available)', { recommendation: 'This rule checks that data tables have captions or aria-labels for screen readers' });
453
+ }
454
+ const count = ctx.tablesWithoutCaption;
455
+ if (count > 0) {
456
+ return createResult({ id: 'a11y-table-caption', name: 'Table Caption', category: 'accessibility', severity: 'info' }, 'info', `${count} table(s) without caption or aria-label`, {
457
+ value: count,
458
+ recommendation: 'Add <caption> or aria-label to data tables',
459
+ evidence: {
460
+ found: count,
461
+ expected: 0,
462
+ impact: 'Screen reader users cannot understand table purpose',
463
+ },
464
+ });
465
+ }
466
+ return createResult({ id: 'a11y-table-caption', name: 'Table Caption', category: 'accessibility', severity: 'info' }, 'pass', 'All tables have captions or labels');
467
+ },
468
+ },
469
+ {
470
+ id: 'a11y-iframe-title',
471
+ name: 'Iframe Title',
472
+ category: 'accessibility',
473
+ severity: 'warning',
474
+ description: '<frame> or <iframe> elements must have a title',
475
+ check: (ctx) => {
476
+ if (ctx.iframesWithoutTitle === undefined) {
477
+ return createResult({ id: 'a11y-iframe-title', name: 'Iframe Title', category: 'accessibility', severity: 'warning' }, 'info', 'Not applicable (no iframe data available)', { recommendation: 'This rule checks that iframes have title attributes to describe their content' });
478
+ }
479
+ const count = ctx.iframesWithoutTitle;
480
+ if (count > 0) {
481
+ return createResult({ id: 'a11y-iframe-title', name: 'Iframe Title', category: 'accessibility', severity: 'warning' }, 'warn', `${count} iframe(s) without title attribute`, {
482
+ value: count,
483
+ recommendation: 'Add title attribute to describe iframe content',
484
+ evidence: {
485
+ found: count,
486
+ expected: 0,
487
+ impact: 'Screen reader users cannot identify iframe purpose',
488
+ learnMore: 'https://dequeuniversity.com/rules/axe/4.4/frame-title',
489
+ },
490
+ });
491
+ }
492
+ return createResult({ id: 'a11y-iframe-title', name: 'Iframe Title', category: 'accessibility', severity: 'warning' }, 'pass', 'All iframes have title attributes');
493
+ },
494
+ },
495
+ {
496
+ id: 'a11y-svg-title',
497
+ name: 'SVG Title',
498
+ category: 'accessibility',
499
+ severity: 'warning',
500
+ description: 'SVGs should have <title> or aria-label for accessibility',
501
+ check: (ctx) => {
502
+ if (ctx.svgsWithoutTitle === undefined) {
503
+ return createResult({ id: 'a11y-svg-title', name: 'SVG Title', category: 'accessibility', severity: 'warning' }, 'info', 'Not applicable (no SVG data available)', { recommendation: 'This rule checks that SVGs have <title> elements or aria-label for accessibility' });
504
+ }
505
+ const count = ctx.svgsWithoutTitle;
506
+ if (count > 0) {
507
+ return createResult({ id: 'a11y-svg-title', name: 'SVG Title', category: 'accessibility', severity: 'warning' }, 'warn', `${count} SVG(s) without accessible title`, {
508
+ value: count,
509
+ recommendation: 'Add <title> element inside SVG or aria-label attribute',
510
+ evidence: {
511
+ found: count,
512
+ expected: 0,
513
+ impact: 'Screen readers cannot describe SVG content',
514
+ example: '<svg aria-label="Descriptive label">\n <title>Icon description</title>\n <!-- SVG content -->\n</svg>',
515
+ },
516
+ });
517
+ }
518
+ return createResult({ id: 'a11y-svg-title', name: 'SVG Title', category: 'accessibility', severity: 'warning' }, 'pass', 'All SVGs have accessible titles');
519
+ },
520
+ },
521
+ {
522
+ id: 'a11y-viewport-zoom',
523
+ name: 'Viewport Zoom',
524
+ category: 'accessibility',
525
+ severity: 'error',
526
+ description: '[user-scalable="no"] should not be used and maximum-scale should not be less than 5',
527
+ check: (ctx) => {
528
+ if (ctx.viewportContent === undefined) {
529
+ return createResult({ id: 'a11y-viewport-zoom', name: 'Viewport Zoom', category: 'accessibility', severity: 'error' }, 'info', 'Not applicable (no viewport meta tag data available)', { recommendation: 'This rule checks that viewport meta tag allows zooming for users with low vision' });
530
+ }
531
+ const viewport = ctx.viewportContent.toLowerCase();
532
+ const hasUserScalableNo = viewport.includes('user-scalable=no') || viewport.includes('user-scalable=0');
533
+ const maxScaleMatch = viewport.match(/maximum-scale\s*=\s*([\d.]+)/);
534
+ const maxScale = maxScaleMatch ? parseFloat(maxScaleMatch[1]) : null;
535
+ if (hasUserScalableNo || (maxScale !== null && maxScale < 5)) {
536
+ return createResult({ id: 'a11y-viewport-zoom', name: 'Viewport Zoom', category: 'accessibility', severity: 'error' }, 'fail', 'Viewport prevents zooming', {
537
+ recommendation: 'Remove user-scalable=no and ensure maximum-scale is at least 5',
538
+ evidence: {
539
+ found: ctx.viewportContent,
540
+ expected: 'No user-scalable=no, maximum-scale >= 5',
541
+ impact: 'Users with low vision cannot zoom to read content',
542
+ learnMore: 'https://dequeuniversity.com/rules/axe/4.4/meta-viewport',
543
+ },
544
+ });
545
+ }
546
+ return createResult({ id: 'a11y-viewport-zoom', name: 'Viewport Zoom', category: 'accessibility', severity: 'error' }, 'pass', 'Viewport allows zooming');
547
+ },
548
+ },
549
+ {
550
+ id: 'a11y-document-title',
551
+ name: 'Document Title',
552
+ category: 'accessibility',
553
+ severity: 'error',
554
+ description: 'Document must have a <title> element',
555
+ check: (ctx) => {
556
+ if (ctx.title === undefined) {
557
+ return createResult({ id: 'a11y-document-title', name: 'Document Title', category: 'accessibility', severity: 'error' }, 'info', 'Not applicable (no title data available)', { recommendation: 'This rule checks that document has a <title> element for screen readers' });
558
+ }
559
+ if (!ctx.title || ctx.title.trim().length === 0) {
560
+ return createResult({ id: 'a11y-document-title', name: 'Document Title', category: 'accessibility', severity: 'error' }, 'fail', 'Document does not have a <title> element', {
561
+ recommendation: 'Add a descriptive <title> element to the document',
562
+ evidence: {
563
+ found: ctx.title || 'Empty or missing <title>',
564
+ expected: '<title>Page Title</title>',
565
+ impact: 'Screen reader users cannot identify the page',
566
+ example: '<head>\n <title>My Page - Site Name</title>\n</head>',
567
+ learnMore: 'https://dequeuniversity.com/rules/axe/4.4/document-title',
568
+ },
569
+ });
570
+ }
571
+ return createResult({ id: 'a11y-document-title', name: 'Document Title', category: 'accessibility', severity: 'error' }, 'pass', 'Document has a title element');
572
+ },
573
+ },
574
+ {
575
+ id: 'a11y-html-lang',
576
+ name: 'HTML Lang Attribute',
577
+ category: 'accessibility',
578
+ severity: 'error',
579
+ description: '<html> element must have a [lang] attribute',
580
+ check: (ctx) => {
581
+ if (ctx.hasLang === undefined) {
582
+ return createResult({ id: 'a11y-html-lang', name: 'HTML Lang Attribute', category: 'accessibility', severity: 'error' }, 'info', 'Not applicable (no HTML lang attribute data available)', { recommendation: 'This rule checks that HTML element has a lang attribute for screen readers' });
583
+ }
584
+ if (!ctx.hasLang) {
585
+ return createResult({ id: 'a11y-html-lang', name: 'HTML Lang Attribute', category: 'accessibility', severity: 'error' }, 'fail', '<html> element does not have a [lang] attribute', {
586
+ recommendation: 'Add lang attribute to html element (e.g., <html lang="en">)',
587
+ evidence: {
588
+ found: '<html> without lang attribute',
589
+ expected: '<html lang="en">',
590
+ impact: 'Screen readers may pronounce content incorrectly',
591
+ example: '<html lang="en">\n <!-- page content -->\n</html>',
592
+ learnMore: 'https://dequeuniversity.com/rules/axe/4.4/html-has-lang',
593
+ },
594
+ });
595
+ }
596
+ return createResult({ id: 'a11y-html-lang', name: 'HTML Lang Attribute', category: 'accessibility', severity: 'error' }, 'pass', 'HTML element has lang attribute');
597
+ },
598
+ },
599
+ {
600
+ id: 'a11y-html-lang-valid',
601
+ name: 'Valid Lang Attribute',
602
+ category: 'accessibility',
603
+ severity: 'error',
604
+ description: '<html> element must have a valid value for its [lang] attribute',
605
+ check: (ctx) => {
606
+ if (!ctx.hasLang || !ctx.langValue) {
607
+ return createResult({ id: 'a11y-html-lang-valid', name: 'Valid Lang Attribute', category: 'accessibility', severity: 'error' }, 'info', 'Not applicable (no lang attribute present to validate)', { recommendation: 'This rule checks that HTML lang attribute has a valid BCP 47 language tag' });
608
+ }
609
+ const validLangPattern = /^[a-z]{2,3}(-[A-Za-z]{2,4})?(-[A-Za-z0-9]{2,})?$/i;
610
+ if (!validLangPattern.test(ctx.langValue)) {
611
+ return createResult({ id: 'a11y-html-lang-valid', name: 'Valid Lang Attribute', category: 'accessibility', severity: 'error' }, 'fail', `Invalid lang attribute value: ${ctx.langValue}`, {
612
+ value: ctx.langValue,
613
+ recommendation: 'Use a valid BCP 47 language tag (e.g., "en", "en-US", "pt-BR")',
614
+ evidence: {
615
+ found: ctx.langValue,
616
+ expected: 'Valid BCP 47 language tag',
617
+ impact: 'Invalid language tags prevent screen readers from selecting correct voice/pronunciation',
618
+ example: '<html lang="en"> or <html lang="pt-BR">',
619
+ learnMore: 'https://dequeuniversity.com/rules/axe/4.4/html-lang-valid',
620
+ },
621
+ });
622
+ }
623
+ return createResult({ id: 'a11y-html-lang-valid', name: 'Valid Lang Attribute', category: 'accessibility', severity: 'error' }, 'pass', `Valid lang attribute: ${ctx.langValue}`);
624
+ },
625
+ },
626
+ {
627
+ id: 'a11y-video-captions',
628
+ name: 'Video Captions',
629
+ category: 'accessibility',
630
+ severity: 'warning',
631
+ description: '<video> elements should contain a <track> element with [kind="captions"]',
632
+ check: (ctx) => {
633
+ if (ctx.videoCount === undefined || ctx.videosWithCaptions === undefined) {
634
+ return createResult({ id: 'a11y-video-captions', name: 'Video Captions', category: 'accessibility', severity: 'warning' }, 'info', 'Not applicable (no video data available)', { recommendation: 'This rule checks that video elements have caption tracks for deaf users' });
635
+ }
636
+ const videos = ctx.videoCount;
637
+ const withCaptions = ctx.videosWithCaptions;
638
+ if (videos > 0 && withCaptions < videos) {
639
+ return createResult({ id: 'a11y-video-captions', name: 'Video Captions', category: 'accessibility', severity: 'warning' }, 'warn', `${videos - withCaptions} of ${videos} video(s) missing captions track`, {
640
+ value: videos - withCaptions,
641
+ recommendation: 'Add <track kind="captions" src="..."> to video elements',
642
+ evidence: {
643
+ found: `${withCaptions} with captions`,
644
+ expected: `${videos} with captions`,
645
+ impact: 'Deaf users cannot access video content',
646
+ learnMore: 'https://dequeuniversity.com/rules/axe/4.4/video-caption',
647
+ },
648
+ });
649
+ }
650
+ if (videos > 0) {
651
+ return createResult({ id: 'a11y-video-captions', name: 'Video Captions', category: 'accessibility', severity: 'warning' }, 'pass', `All ${videos} video(s) have captions`);
652
+ }
653
+ return createResult({ id: 'a11y-video-captions', name: 'Video Captions', category: 'accessibility', severity: 'warning' }, 'info', 'Not applicable (no video elements detected)', { recommendation: 'This rule checks that video elements have caption tracks for deaf users' });
654
+ },
655
+ },
656
+ {
657
+ id: 'a11y-list-structure',
658
+ name: 'List Structure',
659
+ category: 'accessibility',
660
+ severity: 'warning',
661
+ description: 'Lists must contain only <li> elements and script supporting elements',
662
+ check: (ctx) => {
663
+ if (ctx.invalidListStructure === undefined) {
664
+ return createResult({ id: 'a11y-list-structure', name: 'List Structure', category: 'accessibility', severity: 'warning' }, 'info', 'Not applicable (no list structure data available)', { recommendation: 'This rule checks that lists only contain valid children (li, script, template)' });
665
+ }
666
+ const count = ctx.invalidListStructure;
667
+ if (count > 0) {
668
+ return createResult({ id: 'a11y-list-structure', name: 'List Structure', category: 'accessibility', severity: 'warning' }, 'warn', `${count} list(s) have invalid structure`, {
669
+ value: count,
670
+ recommendation: 'Ensure lists (<ul>, <ol>) only contain <li>, <script>, or <template> children',
671
+ evidence: {
672
+ found: count,
673
+ expected: 0,
674
+ impact: 'Screen readers may not announce lists correctly',
675
+ learnMore: 'https://dequeuniversity.com/rules/axe/4.4/list',
676
+ },
677
+ });
678
+ }
679
+ return createResult({ id: 'a11y-list-structure', name: 'List Structure', category: 'accessibility', severity: 'warning' }, 'pass', 'All lists have valid structure');
680
+ },
681
+ },
682
+ {
683
+ id: 'a11y-headings-content',
684
+ name: 'Heading Content',
685
+ category: 'accessibility',
686
+ severity: 'warning',
687
+ description: 'All heading elements must contain content',
688
+ check: (ctx) => {
689
+ if (ctx.emptyHeadings === undefined) {
690
+ return createResult({ id: 'a11y-headings-content', name: 'Heading Content', category: 'accessibility', severity: 'warning' }, 'info', 'Not applicable (no heading content data available)', { recommendation: 'This rule checks that all heading elements contain text content' });
691
+ }
692
+ const count = ctx.emptyHeadings;
693
+ if (count > 0) {
694
+ return createResult({ id: 'a11y-headings-content', name: 'Heading Content', category: 'accessibility', severity: 'warning' }, 'warn', `${count} empty heading element(s) found`, {
695
+ value: count,
696
+ recommendation: 'Add text content to all heading elements or remove empty headings',
697
+ evidence: {
698
+ found: count,
699
+ expected: 0,
700
+ impact: 'Empty headings confuse screen reader navigation',
701
+ learnMore: 'https://dequeuniversity.com/rules/axe/4.4/empty-heading',
702
+ },
703
+ });
704
+ }
705
+ return createResult({ id: 'a11y-headings-content', name: 'Heading Content', category: 'accessibility', severity: 'warning' }, 'pass', 'All headings contain content');
706
+ },
707
+ },
708
+ {
709
+ id: 'a11y-images-redundant-alt',
710
+ name: 'Redundant Alt Text',
711
+ category: 'accessibility',
712
+ severity: 'info',
713
+ description: 'Image alt attributes should not be redundant',
714
+ check: (ctx) => {
715
+ if (ctx.imagesWithRedundantAlt === undefined) {
716
+ return createResult({ id: 'a11y-images-redundant-alt', name: 'Redundant Alt Text', category: 'accessibility', severity: 'info' }, 'info', 'Not applicable (no image alt text data available)', { recommendation: 'This rule checks that image alt text does not contain redundant phrases like "image of" or "picture of"' });
717
+ }
718
+ const count = ctx.imagesWithRedundantAlt;
719
+ if (count > 0) {
720
+ return createResult({ id: 'a11y-images-redundant-alt', name: 'Redundant Alt Text', category: 'accessibility', severity: 'info' }, 'info', `${count} image(s) have redundant alt text (e.g., "image of", "picture of")`, {
721
+ value: count,
722
+ recommendation: 'Remove redundant phrases like "image of" or "picture of" from alt text',
723
+ evidence: {
724
+ found: count,
725
+ expected: 0,
726
+ impact: 'Screen readers already announce images; redundant text is repetitive',
727
+ },
728
+ });
729
+ }
730
+ return createResult({ id: 'a11y-images-redundant-alt', name: 'Redundant Alt Text', category: 'accessibility', severity: 'info' }, 'pass', 'No redundant alt text found');
731
+ },
732
+ },
733
+ ];