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
@@ -7,8 +7,9 @@ export const accessibilityRules = [
7
7
  severity: 'error',
8
8
  description: 'Buttons must have an accessible name (text content, aria-label, or title)',
9
9
  check: (ctx) => {
10
- if (ctx.buttonsWithoutAriaLabel === undefined)
11
- return null;
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
+ }
12
13
  const count = ctx.buttonsWithoutAriaLabel;
13
14
  if (count > 0) {
14
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`, {
@@ -32,8 +33,9 @@ export const accessibilityRules = [
32
33
  severity: 'error',
33
34
  description: 'Image elements must have [alt] attributes',
34
35
  check: (ctx) => {
35
- if (ctx.imagesWithoutAlt === undefined)
36
- return null;
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
+ }
37
39
  const count = ctx.imagesWithoutAlt;
38
40
  if (count > 0) {
39
41
  return createResult({ id: 'a11y-images-alt', name: 'Image Alt Attributes', category: 'accessibility', severity: 'error' }, 'fail', `${count} image(s) do not have [alt] attributes`, {
@@ -57,8 +59,9 @@ export const accessibilityRules = [
57
59
  severity: 'error',
58
60
  description: 'Links must have a discernible name',
59
61
  check: (ctx) => {
60
- if (ctx.linksWithoutText === undefined && ctx.linksWithoutAriaLabel === undefined)
61
- return null;
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
+ }
62
65
  const linksNoText = ctx.linksWithoutText ?? 0;
63
66
  const linksNoAria = ctx.linksWithoutAriaLabel ?? 0;
64
67
  const count = Math.max(linksNoText, linksNoAria);
@@ -84,8 +87,9 @@ export const accessibilityRules = [
84
87
  severity: 'error',
85
88
  description: 'Form elements must have associated labels',
86
89
  check: (ctx) => {
87
- if (ctx.inputsWithoutLabel === undefined)
88
- return null;
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
+ }
89
93
  const count = ctx.inputsWithoutLabel;
90
94
  if (count > 0) {
91
95
  return createResult({ id: 'a11y-form-labels', name: 'Form Input Labels', category: 'accessibility', severity: 'error' }, 'fail', `${count} form input(s) without associated label`, {
@@ -109,8 +113,9 @@ export const accessibilityRules = [
109
113
  severity: 'warning',
110
114
  description: 'Heading elements should be in sequentially-descending order',
111
115
  check: (ctx) => {
112
- if (ctx.headingHierarchyValid === undefined)
113
- return null;
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
+ }
114
119
  if (!ctx.headingHierarchyValid) {
115
120
  return createResult({ id: 'a11y-heading-order', name: 'Heading Order', category: 'accessibility', severity: 'warning' }, 'warn', 'Heading elements are not in sequentially-descending order', {
116
121
  recommendation: 'Ensure headings follow a logical order (H1 → H2 → H3) without skipping levels',
@@ -132,8 +137,9 @@ export const accessibilityRules = [
132
137
  severity: 'warning',
133
138
  description: 'No element should have a [tabindex] value greater than 0',
134
139
  check: (ctx) => {
135
- if (ctx.elementsWithHighTabindex === undefined)
136
- return null;
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
+ }
137
143
  const count = ctx.elementsWithHighTabindex;
138
144
  if (count > 0) {
139
145
  return createResult({ id: 'a11y-tabindex', name: 'Tabindex Values', category: 'accessibility', severity: 'warning' }, 'warn', `${count} element(s) have tabindex > 0`, {
@@ -157,8 +163,9 @@ export const accessibilityRules = [
157
163
  severity: 'error',
158
164
  description: '[aria-*] attributes must be valid and not misspelled',
159
165
  check: (ctx) => {
160
- if (ctx.invalidAriaAttributes === undefined)
161
- return null;
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
+ }
162
169
  const count = ctx.invalidAriaAttributes;
163
170
  if (count > 0) {
164
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`, {
@@ -182,8 +189,9 @@ export const accessibilityRules = [
182
189
  severity: 'error',
183
190
  description: '[aria-*] attributes must have valid values',
184
191
  check: (ctx) => {
185
- if (ctx.invalidAriaValues === undefined)
186
- return null;
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
+ }
187
195
  const count = ctx.invalidAriaValues;
188
196
  if (count > 0) {
189
197
  return createResult({ id: 'a11y-aria-valid-values', name: 'ARIA Attribute Values', category: 'accessibility', severity: 'error' }, 'fail', `${count} aria-* attribute(s) have invalid values`, {
@@ -207,8 +215,9 @@ export const accessibilityRules = [
207
215
  severity: 'error',
208
216
  description: '[role] values must be valid',
209
217
  check: (ctx) => {
210
- if (ctx.invalidAriaRoles === undefined)
211
- return null;
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
+ }
212
221
  const count = ctx.invalidAriaRoles;
213
222
  if (count > 0) {
214
223
  return createResult({ id: 'a11y-aria-roles', name: 'Valid ARIA Roles', category: 'accessibility', severity: 'error' }, 'fail', `${count} invalid [role] value(s) found`, {
@@ -232,8 +241,9 @@ export const accessibilityRules = [
232
241
  severity: 'error',
233
242
  description: '[role]s must have all required [aria-*] attributes',
234
243
  check: (ctx) => {
235
- if (ctx.missingRequiredAriaAttrs === undefined)
236
- return null;
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
+ }
237
247
  const count = ctx.missingRequiredAriaAttrs;
238
248
  if (count > 0) {
239
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`, {
@@ -257,8 +267,9 @@ export const accessibilityRules = [
257
267
  severity: 'error',
258
268
  description: '[aria-hidden="true"] must not be present on the document <body>',
259
269
  check: (ctx) => {
260
- if (ctx.hasAriaHiddenBody === undefined)
261
- return null;
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
+ }
262
273
  if (ctx.hasAriaHiddenBody) {
263
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>', {
264
275
  recommendation: 'Remove aria-hidden from <body> element',
@@ -280,8 +291,9 @@ export const accessibilityRules = [
280
291
  severity: 'error',
281
292
  description: '[aria-hidden="true"] elements must not contain focusable descendants',
282
293
  check: (ctx) => {
283
- if (ctx.ariaHiddenFocusableCount === undefined)
284
- return null;
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
+ }
285
297
  const count = ctx.ariaHiddenFocusableCount;
286
298
  if (count > 0) {
287
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`, {
@@ -305,8 +317,9 @@ export const accessibilityRules = [
305
317
  severity: 'warning',
306
318
  description: 'Deprecated ARIA roles should not be used',
307
319
  check: (ctx) => {
308
- if (ctx.deprecatedAriaRoles === undefined)
309
- return null;
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
+ }
310
323
  const count = ctx.deprecatedAriaRoles;
311
324
  if (count > 0) {
312
325
  return createResult({ id: 'a11y-aria-deprecated', name: 'Deprecated ARIA Roles', category: 'accessibility', severity: 'warning' }, 'warn', `${count} deprecated ARIA role(s) found`, {
@@ -330,8 +343,9 @@ export const accessibilityRules = [
330
343
  severity: 'error',
331
344
  description: 'ARIA IDs must be unique',
332
345
  check: (ctx) => {
333
- if (ctx.duplicateAriaIds === undefined)
334
- return null;
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
+ }
335
349
  const count = ctx.duplicateAriaIds;
336
350
  if (count > 0) {
337
351
  return createResult({ id: 'a11y-aria-ids-unique', name: 'ARIA IDs Unique', category: 'accessibility', severity: 'error' }, 'fail', `${count} duplicate ARIA ID(s) found`, {
@@ -355,8 +369,9 @@ export const accessibilityRules = [
355
369
  severity: 'error',
356
370
  description: 'Elements with role="dialog" or role="alertdialog" must have accessible names',
357
371
  check: (ctx) => {
358
- if (ctx.dialogsWithoutName === undefined)
359
- return null;
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
+ }
360
375
  const count = ctx.dialogsWithoutName;
361
376
  if (count > 0) {
362
377
  return createResult({ id: 'a11y-dialog-name', name: 'Dialog Accessible Name', category: 'accessibility', severity: 'error' }, 'fail', `${count} dialog(s) without accessible name`, {
@@ -380,14 +395,17 @@ export const accessibilityRules = [
380
395
  severity: 'warning',
381
396
  description: 'Document should have a main landmark',
382
397
  check: (ctx) => {
383
- if (ctx.hasMain === undefined)
384
- return null;
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
+ }
385
401
  if (!ctx.hasMain) {
386
402
  return createResult({ id: 'a11y-main-landmark', name: 'Main Landmark', category: 'accessibility', severity: 'warning' }, 'warn', 'Document does not have a <main> landmark', {
387
403
  recommendation: 'Add a <main> element or role="main" to identify the main content area',
388
404
  evidence: {
405
+ found: 'No <main> element or role="main"',
389
406
  expected: '<main> or role="main"',
390
407
  impact: 'Screen reader users cannot quickly navigate to main content',
408
+ example: '<main>\n <!-- Main content here -->\n</main>',
391
409
  learnMore: 'https://dequeuniversity.com/rules/axe/4.4/landmark-one-main',
392
410
  },
393
411
  });
@@ -402,8 +420,9 @@ export const accessibilityRules = [
402
420
  severity: 'info',
403
421
  description: 'Page should contain a heading, skip link, or landmark region',
404
422
  check: (ctx) => {
405
- if (ctx.hasSkipLink === undefined && ctx.hasMain === undefined && ctx.h1Count === undefined)
406
- return null;
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
+ }
407
426
  const hasSkip = ctx.hasSkipLink ?? false;
408
427
  const hasMain = ctx.hasMain ?? false;
409
428
  const hasH1 = (ctx.h1Count ?? 0) > 0;
@@ -411,8 +430,10 @@ export const accessibilityRules = [
411
430
  return createResult({ id: 'a11y-skip-link', name: 'Skip Link', category: 'accessibility', severity: 'info' }, 'info', 'No skip link, main landmark, or heading found', {
412
431
  recommendation: 'Add a skip link, <main> landmark, or at least one heading for navigation',
413
432
  evidence: {
433
+ found: 'No skip link, <main>, or heading',
414
434
  expected: 'Skip link, <main>, or heading',
415
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>',
416
437
  learnMore: 'https://dequeuniversity.com/rules/axe/4.4/bypass',
417
438
  },
418
439
  });
@@ -427,8 +448,9 @@ export const accessibilityRules = [
427
448
  severity: 'info',
428
449
  description: 'Data tables should have caption or aria-label',
429
450
  check: (ctx) => {
430
- if (ctx.tablesWithoutCaption === undefined)
431
- return null;
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
+ }
432
454
  const count = ctx.tablesWithoutCaption;
433
455
  if (count > 0) {
434
456
  return createResult({ id: 'a11y-table-caption', name: 'Table Caption', category: 'accessibility', severity: 'info' }, 'info', `${count} table(s) without caption or aria-label`, {
@@ -451,8 +473,9 @@ export const accessibilityRules = [
451
473
  severity: 'warning',
452
474
  description: '<frame> or <iframe> elements must have a title',
453
475
  check: (ctx) => {
454
- if (ctx.iframesWithoutTitle === undefined)
455
- return null;
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
+ }
456
479
  const count = ctx.iframesWithoutTitle;
457
480
  if (count > 0) {
458
481
  return createResult({ id: 'a11y-iframe-title', name: 'Iframe Title', category: 'accessibility', severity: 'warning' }, 'warn', `${count} iframe(s) without title attribute`, {
@@ -476,8 +499,9 @@ export const accessibilityRules = [
476
499
  severity: 'warning',
477
500
  description: 'SVGs should have <title> or aria-label for accessibility',
478
501
  check: (ctx) => {
479
- if (ctx.svgsWithoutTitle === undefined)
480
- return null;
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
+ }
481
505
  const count = ctx.svgsWithoutTitle;
482
506
  if (count > 0) {
483
507
  return createResult({ id: 'a11y-svg-title', name: 'SVG Title', category: 'accessibility', severity: 'warning' }, 'warn', `${count} SVG(s) without accessible title`, {
@@ -487,6 +511,7 @@ export const accessibilityRules = [
487
511
  found: count,
488
512
  expected: 0,
489
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>',
490
515
  },
491
516
  });
492
517
  }
@@ -500,8 +525,9 @@ export const accessibilityRules = [
500
525
  severity: 'error',
501
526
  description: '[user-scalable="no"] should not be used and maximum-scale should not be less than 5',
502
527
  check: (ctx) => {
503
- if (ctx.viewportContent === undefined)
504
- return null;
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
+ }
505
531
  const viewport = ctx.viewportContent.toLowerCase();
506
532
  const hasUserScalableNo = viewport.includes('user-scalable=no') || viewport.includes('user-scalable=0');
507
533
  const maxScaleMatch = viewport.match(/maximum-scale\s*=\s*([\d.]+)/);
@@ -527,14 +553,17 @@ export const accessibilityRules = [
527
553
  severity: 'error',
528
554
  description: 'Document must have a <title> element',
529
555
  check: (ctx) => {
530
- if (ctx.title === undefined)
531
- return null;
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
+ }
532
559
  if (!ctx.title || ctx.title.trim().length === 0) {
533
560
  return createResult({ id: 'a11y-document-title', name: 'Document Title', category: 'accessibility', severity: 'error' }, 'fail', 'Document does not have a <title> element', {
534
561
  recommendation: 'Add a descriptive <title> element to the document',
535
562
  evidence: {
563
+ found: ctx.title || 'Empty or missing <title>',
536
564
  expected: '<title>Page Title</title>',
537
565
  impact: 'Screen reader users cannot identify the page',
566
+ example: '<head>\n <title>My Page - Site Name</title>\n</head>',
538
567
  learnMore: 'https://dequeuniversity.com/rules/axe/4.4/document-title',
539
568
  },
540
569
  });
@@ -549,14 +578,17 @@ export const accessibilityRules = [
549
578
  severity: 'error',
550
579
  description: '<html> element must have a [lang] attribute',
551
580
  check: (ctx) => {
552
- if (ctx.hasLang === undefined)
553
- return null;
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
+ }
554
584
  if (!ctx.hasLang) {
555
585
  return createResult({ id: 'a11y-html-lang', name: 'HTML Lang Attribute', category: 'accessibility', severity: 'error' }, 'fail', '<html> element does not have a [lang] attribute', {
556
586
  recommendation: 'Add lang attribute to html element (e.g., <html lang="en">)',
557
587
  evidence: {
588
+ found: '<html> without lang attribute',
558
589
  expected: '<html lang="en">',
559
590
  impact: 'Screen readers may pronounce content incorrectly',
591
+ example: '<html lang="en">\n <!-- page content -->\n</html>',
560
592
  learnMore: 'https://dequeuniversity.com/rules/axe/4.4/html-has-lang',
561
593
  },
562
594
  });
@@ -571,8 +603,9 @@ export const accessibilityRules = [
571
603
  severity: 'error',
572
604
  description: '<html> element must have a valid value for its [lang] attribute',
573
605
  check: (ctx) => {
574
- if (!ctx.hasLang || !ctx.langValue)
575
- return null;
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
+ }
576
609
  const validLangPattern = /^[a-z]{2,3}(-[A-Za-z]{2,4})?(-[A-Za-z0-9]{2,})?$/i;
577
610
  if (!validLangPattern.test(ctx.langValue)) {
578
611
  return createResult({ id: 'a11y-html-lang-valid', name: 'Valid Lang Attribute', category: 'accessibility', severity: 'error' }, 'fail', `Invalid lang attribute value: ${ctx.langValue}`, {
@@ -581,6 +614,8 @@ export const accessibilityRules = [
581
614
  evidence: {
582
615
  found: ctx.langValue,
583
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">',
584
619
  learnMore: 'https://dequeuniversity.com/rules/axe/4.4/html-lang-valid',
585
620
  },
586
621
  });
@@ -595,8 +630,9 @@ export const accessibilityRules = [
595
630
  severity: 'warning',
596
631
  description: '<video> elements should contain a <track> element with [kind="captions"]',
597
632
  check: (ctx) => {
598
- if (ctx.videoCount === undefined || ctx.videosWithCaptions === undefined)
599
- return null;
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
+ }
600
636
  const videos = ctx.videoCount;
601
637
  const withCaptions = ctx.videosWithCaptions;
602
638
  if (videos > 0 && withCaptions < videos) {
@@ -614,7 +650,7 @@ export const accessibilityRules = [
614
650
  if (videos > 0) {
615
651
  return createResult({ id: 'a11y-video-captions', name: 'Video Captions', category: 'accessibility', severity: 'warning' }, 'pass', `All ${videos} video(s) have captions`);
616
652
  }
617
- return null;
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' });
618
654
  },
619
655
  },
620
656
  {
@@ -624,8 +660,9 @@ export const accessibilityRules = [
624
660
  severity: 'warning',
625
661
  description: 'Lists must contain only <li> elements and script supporting elements',
626
662
  check: (ctx) => {
627
- if (ctx.invalidListStructure === undefined)
628
- return null;
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
+ }
629
666
  const count = ctx.invalidListStructure;
630
667
  if (count > 0) {
631
668
  return createResult({ id: 'a11y-list-structure', name: 'List Structure', category: 'accessibility', severity: 'warning' }, 'warn', `${count} list(s) have invalid structure`, {
@@ -649,8 +686,9 @@ export const accessibilityRules = [
649
686
  severity: 'warning',
650
687
  description: 'All heading elements must contain content',
651
688
  check: (ctx) => {
652
- if (ctx.emptyHeadings === undefined)
653
- return null;
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
+ }
654
692
  const count = ctx.emptyHeadings;
655
693
  if (count > 0) {
656
694
  return createResult({ id: 'a11y-headings-content', name: 'Heading Content', category: 'accessibility', severity: 'warning' }, 'warn', `${count} empty heading element(s) found`, {
@@ -674,8 +712,9 @@ export const accessibilityRules = [
674
712
  severity: 'info',
675
713
  description: 'Image alt attributes should not be redundant',
676
714
  check: (ctx) => {
677
- if (ctx.imagesWithRedundantAlt === undefined)
678
- return null;
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
+ }
679
718
  const count = ctx.imagesWithRedundantAlt;
680
719
  if (count > 0) {
681
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")`, {
@@ -7,8 +7,9 @@ export const aiSearchRules = [
7
7
  severity: 'info',
8
8
  description: 'llms.txt helps AI systems understand your site (llmstxt.org)',
9
9
  check: (ctx) => {
10
- if (ctx.llmsTxt === undefined)
11
- return null;
10
+ if (ctx.llmsTxt === undefined) {
11
+ return createResult({ id: 'ai-llms-txt-exists', name: 'llms.txt File', category: 'ai-search', severity: 'info' }, 'info', 'Not applicable (llms.txt data unavailable)', { recommendation: 'This rule checks for llms.txt file to optimize for AI/LLM discovery' });
12
+ }
12
13
  if (!ctx.llmsTxt.exists) {
13
14
  return createResult({ id: 'ai-llms-txt-exists', name: 'llms.txt File', category: 'ai-search', severity: 'info' }, 'info', 'llms.txt not found', {
14
15
  recommendation: 'Create llms.txt to optimize for AI/LLM discovery',
@@ -37,8 +38,9 @@ export const aiSearchRules = [
37
38
  severity: 'info',
38
39
  description: 'llms.txt should have proper structure with site name and description',
39
40
  check: (ctx) => {
40
- if (!ctx.llmsTxt?.exists || !ctx.llmsTxt.parseResult)
41
- return null;
41
+ if (!ctx.llmsTxt?.exists || !ctx.llmsTxt.parseResult) {
42
+ return createResult({ id: 'ai-llms-txt-structure', name: 'llms.txt Structure', category: 'ai-search', severity: 'info' }, 'info', 'Not applicable (llms.txt does not exist or parse data unavailable)', { recommendation: 'This rule validates llms.txt structure for AI comprehension' });
43
+ }
42
44
  const { siteName, siteDescription, sections, links } = ctx.llmsTxt.parseResult;
43
45
  const issues = [];
44
46
  if (!siteName) {
@@ -73,8 +75,9 @@ export const aiSearchRules = [
73
75
  severity: 'info',
74
76
  description: 'Content should be well-structured for AI comprehension',
75
77
  check: (ctx) => {
76
- if (!ctx.headings?.structure)
77
- return null;
78
+ if (!ctx.headings?.structure) {
79
+ return createResult({ id: 'ai-content-structure', name: 'AI-Friendly Content Structure', category: 'ai-search', severity: 'info' }, 'info', 'Not applicable (heading structure data unavailable)', { recommendation: 'This rule checks for well-structured content for AI comprehension' });
80
+ }
78
81
  const issues = [];
79
82
  const h1Count = ctx.headings.h1Count || 0;
80
83
  const hasH2 = ctx.headings.structure.some((h) => h.level === 2);
@@ -105,8 +108,9 @@ export const aiSearchRules = [
105
108
  severity: 'info',
106
109
  description: 'Question headings help with AI search featured snippets',
107
110
  check: (ctx) => {
108
- if (!ctx.headings?.structure)
109
- return null;
111
+ if (!ctx.headings?.structure) {
112
+ return createResult({ id: 'ai-question-headings', name: 'Question-Based Headings', category: 'ai-search', severity: 'info' }, 'info', 'Not applicable (heading structure data unavailable)', { recommendation: 'This rule checks for question-based headings that trigger featured snippets' });
113
+ }
110
114
  const questionPatterns = [
111
115
  /^what\s/i, /^how\s/i, /^why\s/i, /^when\s/i,
112
116
  /^where\s/i, /^who\s/i, /^which\s/i, /^can\s/i,
@@ -130,7 +134,7 @@ export const aiSearchRules = [
130
134
  },
131
135
  });
132
136
  }
133
- return null;
137
+ return createResult({ id: 'ai-question-headings', name: 'Question-Based Headings', category: 'ai-search', severity: 'info' }, 'pass', 'Content length does not require question headings');
134
138
  },
135
139
  },
136
140
  {
@@ -140,8 +144,9 @@ export const aiSearchRules = [
140
144
  severity: 'info',
141
145
  description: 'Schema.org structured data helps AI understand content',
142
146
  check: (ctx) => {
143
- if (!ctx.jsonLdTypes)
144
- return null;
147
+ if (!ctx.jsonLdTypes) {
148
+ return createResult({ id: 'ai-structured-data', name: 'Structured Data for AI', category: 'ai-search', severity: 'info' }, 'info', 'Not applicable (structured data unavailable)', { recommendation: 'This rule checks for Schema.org structured data for AI comprehension' });
149
+ }
145
150
  const aiHelpfulTypes = [
146
151
  'Article', 'BlogPosting', 'NewsArticle', 'TechArticle',
147
152
  'FAQPage', 'HowTo', 'QAPage',
@@ -184,8 +189,9 @@ export const aiSearchRules = [
184
189
  severity: 'info',
185
190
  description: 'FAQPage schema is highly valuable for AI search results',
186
191
  check: (ctx) => {
187
- if (!ctx.jsonLdTypes)
188
- return null;
192
+ if (!ctx.jsonLdTypes) {
193
+ return createResult({ id: 'ai-faq-schema', name: 'FAQ Schema for AI', category: 'ai-search', severity: 'info' }, 'info', 'Not applicable (structured data unavailable)', { recommendation: 'This rule checks for FAQ/HowTo schema for AI search rich results' });
194
+ }
189
195
  const hasFaqSchema = ctx.jsonLdTypes.some((t) => t.includes('FAQPage'));
190
196
  const hasHowToSchema = ctx.jsonLdTypes.some((t) => t.includes('HowTo'));
191
197
  const hasQASchema = ctx.jsonLdTypes.some((t) => t.includes('QAPage'));
@@ -214,7 +220,7 @@ export const aiSearchRules = [
214
220
  },
215
221
  });
216
222
  }
217
- return null;
223
+ return createResult({ id: 'ai-faq-schema', name: 'FAQ Schema for AI', category: 'ai-search', severity: 'info' }, 'pass', 'No Q&A structure detected requiring FAQ schema');
218
224
  },
219
225
  },
220
226
  {
@@ -224,8 +230,9 @@ export const aiSearchRules = [
224
230
  severity: 'info',
225
231
  description: 'AI systems prefer comprehensive, in-depth content',
226
232
  check: (ctx) => {
227
- if (ctx.wordCount === undefined)
228
- return null;
233
+ if (ctx.wordCount === undefined) {
234
+ return createResult({ id: 'ai-content-depth', name: 'Content Depth for AI', category: 'ai-search', severity: 'info' }, 'info', 'Not applicable (word count data unavailable)', { recommendation: 'This rule checks content depth for AI search visibility' });
235
+ }
229
236
  const minWords = 300;
230
237
  const goodWords = 1000;
231
238
  const excellentWords = 2000;
@@ -259,8 +266,9 @@ export const aiSearchRules = [
259
266
  severity: 'info',
260
267
  description: 'AI systems value fresh, updated content',
261
268
  check: (ctx) => {
262
- if (!ctx.jsonLdTypes)
263
- return null;
269
+ if (!ctx.jsonLdTypes) {
270
+ return createResult({ id: 'ai-content-freshness', name: 'Content Freshness Signals', category: 'ai-search', severity: 'info' }, 'info', 'Not applicable (structured data unavailable)', { recommendation: 'This rule checks for content freshness signals in structured data' });
271
+ }
264
272
  const hasDateSignals = ctx.jsonLdTypes.some((t) => t.includes('Article') || t.includes('BlogPosting') || t.includes('NewsArticle'));
265
273
  if (hasDateSignals) {
266
274
  return createResult({ id: 'ai-content-freshness', name: 'Content Freshness Signals', category: 'ai-search', severity: 'info' }, 'pass', 'Article schema with date signals found');
@@ -281,8 +289,9 @@ export const aiSearchRules = [
281
289
  severity: 'info',
282
290
  description: 'Check if GPTBot (OpenAI) is allowed or blocked',
283
291
  check: (ctx) => {
284
- if (!ctx.robotsTxt?.parseResult)
285
- return null;
292
+ if (!ctx.robotsTxt?.parseResult) {
293
+ return createResult({ id: 'ai-robots-gpt-bot', name: 'GPTBot Access', category: 'ai-search', severity: 'info' }, 'info', 'Not applicable (robots.txt data unavailable)', { recommendation: 'This rule checks if GPTBot is allowed to crawl for ChatGPT training' });
294
+ }
286
295
  const { userAgentBlocks } = ctx.robotsTxt.parseResult;
287
296
  const gptBotBlock = userAgentBlocks.find((b) => b.userAgents.some(ua => ua.toLowerCase().includes('gptbot')));
288
297
  if (gptBotBlock) {
@@ -307,8 +316,9 @@ export const aiSearchRules = [
307
316
  severity: 'info',
308
317
  description: 'Check if Anthropic/Claude crawlers are allowed or blocked',
309
318
  check: (ctx) => {
310
- if (!ctx.robotsTxt?.parseResult)
311
- return null;
319
+ if (!ctx.robotsTxt?.parseResult) {
320
+ return createResult({ id: 'ai-robots-anthropic', name: 'Anthropic Claude Access', category: 'ai-search', severity: 'info' }, 'info', 'Not applicable (robots.txt data unavailable)', { recommendation: 'This rule checks if Anthropic/Claude crawlers are allowed' });
321
+ }
312
322
  const { userAgentBlocks } = ctx.robotsTxt.parseResult;
313
323
  const anthropicBlock = userAgentBlocks.find((b) => b.userAgents.some(ua => ua.toLowerCase().includes('anthropic') ||
314
324
  ua.toLowerCase().includes('claude')));
@@ -323,7 +333,7 @@ export const aiSearchRules = [
323
333
  });
324
334
  }
325
335
  }
326
- return null;
336
+ return createResult({ id: 'ai-robots-anthropic', name: 'Anthropic Claude Access', category: 'ai-search', severity: 'info' }, 'pass', 'Anthropic/Claude crawler is allowed (default)');
327
337
  },
328
338
  },
329
339
  {
@@ -333,8 +343,9 @@ export const aiSearchRules = [
333
343
  severity: 'info',
334
344
  description: 'Content updated recently may perform better in AI search',
335
345
  check: (ctx) => {
336
- if (!ctx.lastModified)
337
- return null;
346
+ if (!ctx.lastModified) {
347
+ return createResult({ id: 'ai-content-last-modified', name: 'Content Last Modified', category: 'ai-search', severity: 'info' }, 'info', 'Not applicable (Last-Modified header unavailable)', { recommendation: 'This rule checks content freshness via Last-Modified header' });
348
+ }
338
349
  const lastModifiedDate = new Date(ctx.lastModified);
339
350
  const sixMonthsAgo = new Date();
340
351
  sixMonthsAgo.setMonth(sixMonthsAgo.getMonth() - 6);
@@ -350,7 +361,7 @@ export const aiSearchRules = [
350
361
  }
351
362
  });
352
363
  }
353
- return null;
364
+ return createResult({ id: 'ai-content-last-modified', name: 'Content Last Modified', category: 'ai-search', severity: 'info' }, 'pass', 'Content has been updated recently');
354
365
  },
355
366
  },
356
367
  {
@@ -360,8 +371,9 @@ export const aiSearchRules = [
360
371
  severity: 'info',
361
372
  description: 'Very long pages may be truncated by AI search engines',
362
373
  check: (ctx) => {
363
- if (ctx.wordCount === undefined)
364
- return null;
374
+ if (ctx.wordCount === undefined) {
375
+ return createResult({ id: 'ai-page-too-long', name: 'Page Too Long for AI', category: 'ai-search', severity: 'info' }, 'info', 'Not applicable (word count data unavailable)', { recommendation: 'This rule checks if page length may be too long for AI processing' });
376
+ }
365
377
  if (ctx.wordCount > 10000) {
366
378
  return createResult({ id: 'ai-page-too-long', name: 'Page Too Long for AI', category: 'ai-search', severity: 'info' }, 'info', `Page has ${ctx.wordCount.toLocaleString()} words (very long)`, {
367
379
  value: ctx.wordCount,
@@ -383,7 +395,7 @@ export const aiSearchRules = [
383
395
  }
384
396
  });
385
397
  }
386
- return null;
398
+ return createResult({ id: 'ai-page-too-long', name: 'Page Too Long for AI', category: 'ai-search', severity: 'info' }, 'pass', `Page length is optimal for AI processing (${ctx.wordCount.toLocaleString()} words)`);
387
399
  },
388
400
  },
389
401
  {
@@ -393,8 +405,9 @@ export const aiSearchRules = [
393
405
  severity: 'info',
394
406
  description: 'Pages should use semantic HTML for better AI understanding',
395
407
  check: (ctx) => {
396
- if (ctx.semanticHtmlRatio === undefined)
397
- return null;
408
+ if (ctx.semanticHtmlRatio === undefined) {
409
+ return createResult({ id: 'semantic-html-ratio', name: 'Semantic HTML Ratio', category: 'ai-search', severity: 'info' }, 'info', 'Not applicable (semantic HTML data unavailable)', { recommendation: 'This rule checks for semantic HTML usage for better AI understanding' });
410
+ }
398
411
  if (ctx.semanticHtmlRatio < 0.1) {
399
412
  return createResult({ id: 'semantic-html-ratio', name: 'Semantic HTML Ratio', category: 'ai-search', severity: 'info' }, 'warn', `Low semantic HTML usage (${(ctx.semanticHtmlRatio * 100).toFixed(1)}%)`, {
400
413
  value: ctx.semanticHtmlRatio,
@@ -0,0 +1,2 @@
1
+ import { SeoRule } from './types.js';
2
+ export declare const analyticsRules: SeoRule[];