recker 1.0.42 → 1.0.43-next.7a08602

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 (458) hide show
  1. package/dist/bin/recker-linux-x64 +0 -0
  2. package/dist/bin/recker-macos-x64 +0 -0
  3. package/dist/bin/recker-win-x64.exe +0 -0
  4. package/dist/bin/rek.cjs +93958 -0
  5. package/dist/browser/ai/adaptive-timeout.d.ts +50 -0
  6. package/dist/browser/ai/adaptive-timeout.js +208 -0
  7. package/dist/browser/ai/client.d.ts +22 -0
  8. package/dist/browser/ai/client.js +294 -0
  9. package/dist/browser/ai/index.d.ts +14 -0
  10. package/dist/browser/ai/index.js +11 -0
  11. package/dist/browser/ai/providers/anthropic.d.ts +63 -0
  12. package/dist/browser/ai/providers/anthropic.js +370 -0
  13. package/dist/browser/ai/providers/base.d.ts +48 -0
  14. package/dist/browser/ai/providers/base.js +150 -0
  15. package/dist/browser/ai/providers/google.d.ts +59 -0
  16. package/dist/browser/ai/providers/google.js +305 -0
  17. package/dist/browser/ai/providers/ollama.d.ts +44 -0
  18. package/dist/browser/ai/providers/ollama.js +240 -0
  19. package/dist/browser/ai/providers/openai.d.ts +64 -0
  20. package/dist/browser/ai/providers/openai.js +298 -0
  21. package/dist/browser/ai/rate-limiter.d.ts +43 -0
  22. package/dist/browser/ai/rate-limiter.js +215 -0
  23. package/dist/browser/ai/vector/index.d.ts +2 -0
  24. package/dist/browser/ai/vector/index.js +2 -0
  25. package/dist/browser/ai/vector/similarity.d.ts +2 -0
  26. package/dist/browser/ai/vector/similarity.js +27 -0
  27. package/dist/browser/ai/vector/store.d.ts +27 -0
  28. package/dist/browser/ai/vector/store.js +82 -0
  29. package/dist/browser/browser/cache.d.ts +2 -40
  30. package/dist/browser/browser/cache.js +2 -199
  31. package/dist/browser/browser/index.d.ts +8 -0
  32. package/dist/browser/browser/index.js +8 -0
  33. package/dist/browser/browser/recker.d.ts +8 -1
  34. package/dist/browser/browser/recker.js +8 -2
  35. package/dist/browser/cache/indexed-db.d.ts +10 -0
  36. package/dist/browser/cache/indexed-db.js +88 -0
  37. package/dist/browser/cache/service-worker-cache.d.ts +18 -0
  38. package/dist/browser/cache/service-worker-cache.js +103 -0
  39. package/dist/browser/cache.d.ts +2 -40
  40. package/dist/browser/cache.js +2 -199
  41. package/dist/browser/constants/user-agents.d.ts +7 -0
  42. package/dist/browser/constants/user-agents.js +7 -0
  43. package/dist/browser/core/client.d.ts +2 -0
  44. package/dist/browser/core/client.js +19 -1
  45. package/dist/browser/index.d.ts +8 -0
  46. package/dist/browser/index.js +8 -0
  47. package/dist/browser/plugins/har-recorder.d.ts +40 -0
  48. package/dist/browser/plugins/har-recorder.js +120 -0
  49. package/dist/browser/plugins/network-simulation.d.ts +7 -0
  50. package/dist/browser/plugins/network-simulation.js +13 -0
  51. package/dist/browser/presets/android.d.ts +2 -0
  52. package/dist/browser/presets/android.js +16 -0
  53. package/dist/browser/presets/anthropic.d.ts +8 -0
  54. package/dist/browser/presets/anthropic.js +27 -0
  55. package/dist/browser/presets/aws.d.ts +19 -0
  56. package/dist/browser/presets/aws.js +68 -0
  57. package/dist/browser/presets/azure-openai.d.ts +10 -0
  58. package/dist/browser/presets/azure-openai.js +35 -0
  59. package/dist/browser/presets/azure.d.ts +41 -0
  60. package/dist/browser/presets/azure.js +104 -0
  61. package/dist/browser/presets/chaturbate.d.ts +2 -0
  62. package/dist/browser/presets/chaturbate.js +17 -0
  63. package/dist/browser/presets/cloudflare.d.ts +12 -0
  64. package/dist/browser/presets/cloudflare.js +39 -0
  65. package/dist/browser/presets/cohere.d.ts +7 -0
  66. package/dist/browser/presets/cohere.js +22 -0
  67. package/dist/browser/presets/deepseek.d.ts +7 -0
  68. package/dist/browser/presets/deepseek.js +22 -0
  69. package/dist/browser/presets/digitalocean.d.ts +5 -0
  70. package/dist/browser/presets/digitalocean.js +16 -0
  71. package/dist/browser/presets/discord.d.ts +6 -0
  72. package/dist/browser/presets/discord.js +17 -0
  73. package/dist/browser/presets/elevenlabs.d.ts +6 -0
  74. package/dist/browser/presets/elevenlabs.js +20 -0
  75. package/dist/browser/presets/enhancers.d.ts +20 -0
  76. package/dist/browser/presets/enhancers.js +85 -0
  77. package/dist/browser/presets/fireworks.d.ts +7 -0
  78. package/dist/browser/presets/fireworks.js +22 -0
  79. package/dist/browser/presets/gcp.d.ts +34 -0
  80. package/dist/browser/presets/gcp.js +91 -0
  81. package/dist/browser/presets/gemini.d.ts +7 -0
  82. package/dist/browser/presets/gemini.js +23 -0
  83. package/dist/browser/presets/github.d.ts +6 -0
  84. package/dist/browser/presets/github.js +17 -0
  85. package/dist/browser/presets/gitlab.d.ts +6 -0
  86. package/dist/browser/presets/gitlab.js +16 -0
  87. package/dist/browser/presets/groq.d.ts +7 -0
  88. package/dist/browser/presets/groq.js +22 -0
  89. package/dist/browser/presets/hubspot.d.ts +9 -0
  90. package/dist/browser/presets/hubspot.js +28 -0
  91. package/dist/browser/presets/huggingface.d.ts +7 -0
  92. package/dist/browser/presets/huggingface.js +23 -0
  93. package/dist/browser/presets/index.d.ts +47 -0
  94. package/dist/browser/presets/index.js +47 -0
  95. package/dist/browser/presets/ios.d.ts +2 -0
  96. package/dist/browser/presets/ios.js +13 -0
  97. package/dist/browser/presets/linear.d.ts +5 -0
  98. package/dist/browser/presets/linear.js +16 -0
  99. package/dist/browser/presets/mailgun.d.ts +7 -0
  100. package/dist/browser/presets/mailgun.js +20 -0
  101. package/dist/browser/presets/meta.d.ts +10 -0
  102. package/dist/browser/presets/meta.js +33 -0
  103. package/dist/browser/presets/mistral.d.ts +7 -0
  104. package/dist/browser/presets/mistral.js +22 -0
  105. package/dist/browser/presets/notion.d.ts +6 -0
  106. package/dist/browser/presets/notion.js +17 -0
  107. package/dist/browser/presets/openai.d.ts +9 -0
  108. package/dist/browser/presets/openai.js +30 -0
  109. package/dist/browser/presets/oracle.d.ts +19 -0
  110. package/dist/browser/presets/oracle.js +117 -0
  111. package/dist/browser/presets/perplexity.d.ts +7 -0
  112. package/dist/browser/presets/perplexity.js +22 -0
  113. package/dist/browser/presets/pinecone.d.ts +8 -0
  114. package/dist/browser/presets/pinecone.js +42 -0
  115. package/dist/browser/presets/registry.d.ts +23 -0
  116. package/dist/browser/presets/registry.js +519 -0
  117. package/dist/browser/presets/replicate.d.ts +7 -0
  118. package/dist/browser/presets/replicate.js +23 -0
  119. package/dist/browser/presets/sendgrid.d.ts +6 -0
  120. package/dist/browser/presets/sendgrid.js +20 -0
  121. package/dist/browser/presets/sentry.d.ts +11 -0
  122. package/dist/browser/presets/sentry.js +48 -0
  123. package/dist/browser/presets/sinch.d.ts +9 -0
  124. package/dist/browser/presets/sinch.js +39 -0
  125. package/dist/browser/presets/slack.d.ts +5 -0
  126. package/dist/browser/presets/slack.js +16 -0
  127. package/dist/browser/presets/square.d.ts +10 -0
  128. package/dist/browser/presets/square.js +33 -0
  129. package/dist/browser/presets/stripe.d.ts +7 -0
  130. package/dist/browser/presets/stripe.js +23 -0
  131. package/dist/browser/presets/supabase.d.ts +6 -0
  132. package/dist/browser/presets/supabase.js +18 -0
  133. package/dist/browser/presets/tiktok.d.ts +10 -0
  134. package/dist/browser/presets/tiktok.js +38 -0
  135. package/dist/browser/presets/together.d.ts +7 -0
  136. package/dist/browser/presets/together.js +22 -0
  137. package/dist/browser/presets/twilio.d.ts +6 -0
  138. package/dist/browser/presets/twilio.js +17 -0
  139. package/dist/browser/presets/vercel.d.ts +6 -0
  140. package/dist/browser/presets/vercel.js +23 -0
  141. package/dist/browser/presets/vultr.d.ts +5 -0
  142. package/dist/browser/presets/vultr.js +16 -0
  143. package/dist/browser/presets/xai.d.ts +8 -0
  144. package/dist/browser/presets/xai.js +23 -0
  145. package/dist/browser/presets/youtube.d.ts +5 -0
  146. package/dist/browser/presets/youtube.js +20 -0
  147. package/dist/browser/recker.d.ts +8 -1
  148. package/dist/browser/recker.js +8 -2
  149. package/dist/browser/scrape/document.d.ts +5 -4
  150. package/dist/browser/scrape/document.js +89 -76
  151. package/dist/browser/scrape/element.d.ts +10 -8
  152. package/dist/browser/scrape/element.js +295 -81
  153. package/dist/browser/scrape/extractors.d.ts +11 -11
  154. package/dist/browser/scrape/extractors.js +145 -113
  155. package/dist/browser/scrape/parser/back.d.ts +1 -0
  156. package/dist/browser/scrape/parser/back.js +3 -0
  157. package/dist/browser/scrape/parser/index.d.ts +20 -0
  158. package/dist/browser/scrape/parser/index.js +19 -0
  159. package/dist/browser/scrape/parser/matcher.d.ts +30 -0
  160. package/dist/browser/scrape/parser/matcher.js +99 -0
  161. package/dist/browser/scrape/parser/nodes/comment.d.ts +12 -0
  162. package/dist/browser/scrape/parser/nodes/comment.js +21 -0
  163. package/dist/browser/scrape/parser/nodes/html.d.ts +110 -0
  164. package/dist/browser/scrape/parser/nodes/html.js +978 -0
  165. package/dist/browser/scrape/parser/nodes/node.d.ts +18 -0
  166. package/dist/browser/scrape/parser/nodes/node.js +31 -0
  167. package/dist/browser/scrape/parser/nodes/text.d.ts +14 -0
  168. package/dist/browser/scrape/parser/nodes/text.js +30 -0
  169. package/dist/browser/scrape/parser/nodes/type.d.ts +6 -0
  170. package/dist/browser/scrape/parser/nodes/type.js +7 -0
  171. package/dist/browser/scrape/parser/parse.d.ts +1 -0
  172. package/dist/browser/scrape/parser/parse.js +1 -0
  173. package/dist/browser/scrape/parser/valid.d.ts +2 -0
  174. package/dist/browser/scrape/parser/valid.js +5 -0
  175. package/dist/browser/scrape/parser/void-tag.d.ts +7 -0
  176. package/dist/browser/scrape/parser/void-tag.js +43 -0
  177. package/dist/browser/scrape/types.d.ts +7 -0
  178. package/dist/browser/seo/analyzer.d.ts +59 -0
  179. package/dist/browser/seo/analyzer.js +1399 -0
  180. package/dist/browser/seo/keywords.d.ts +16 -0
  181. package/dist/browser/seo/keywords.js +55 -0
  182. package/dist/browser/seo/rules/accessibility.d.ts +2 -0
  183. package/dist/browser/seo/rules/accessibility.js +722 -0
  184. package/dist/browser/seo/rules/ai-search.d.ts +2 -0
  185. package/dist/browser/seo/rules/ai-search.js +436 -0
  186. package/dist/browser/seo/rules/analytics.d.ts +2 -0
  187. package/dist/browser/seo/rules/analytics.js +306 -0
  188. package/dist/browser/seo/rules/best-practices.d.ts +2 -0
  189. package/dist/browser/seo/rules/best-practices.js +195 -0
  190. package/dist/browser/seo/rules/canonical.d.ts +12 -0
  191. package/dist/browser/seo/rules/canonical.js +258 -0
  192. package/dist/browser/seo/rules/content.d.ts +2 -0
  193. package/dist/browser/seo/rules/content.js +424 -0
  194. package/dist/browser/seo/rules/crawl.d.ts +2 -0
  195. package/dist/browser/seo/rules/crawl.js +435 -0
  196. package/dist/browser/seo/rules/cwv.d.ts +2 -0
  197. package/dist/browser/seo/rules/cwv.js +248 -0
  198. package/dist/browser/seo/rules/ecommerce.d.ts +2 -0
  199. package/dist/browser/seo/rules/ecommerce.js +283 -0
  200. package/dist/browser/seo/rules/i18n.d.ts +2 -0
  201. package/dist/browser/seo/rules/i18n.js +277 -0
  202. package/dist/browser/seo/rules/images.d.ts +2 -0
  203. package/dist/browser/seo/rules/images.js +235 -0
  204. package/dist/browser/seo/rules/index.d.ts +52 -0
  205. package/dist/browser/seo/rules/index.js +159 -0
  206. package/dist/browser/seo/rules/internal-linking.d.ts +2 -0
  207. package/dist/browser/seo/rules/internal-linking.js +394 -0
  208. package/dist/browser/seo/rules/links.d.ts +2 -0
  209. package/dist/browser/seo/rules/links.js +490 -0
  210. package/dist/browser/seo/rules/local.d.ts +2 -0
  211. package/dist/browser/seo/rules/local.js +289 -0
  212. package/dist/browser/seo/rules/meta.d.ts +2 -0
  213. package/dist/browser/seo/rules/meta.js +646 -0
  214. package/dist/browser/seo/rules/mobile.d.ts +2 -0
  215. package/dist/browser/seo/rules/mobile.js +161 -0
  216. package/dist/browser/seo/rules/performance.d.ts +2 -0
  217. package/dist/browser/seo/rules/performance.js +625 -0
  218. package/dist/browser/seo/rules/pwa.d.ts +2 -0
  219. package/dist/browser/seo/rules/pwa.js +299 -0
  220. package/dist/browser/seo/rules/readability.d.ts +2 -0
  221. package/dist/browser/seo/rules/readability.js +264 -0
  222. package/dist/browser/seo/rules/redirects.d.ts +16 -0
  223. package/dist/browser/seo/rules/redirects.js +199 -0
  224. package/dist/browser/seo/rules/resources.d.ts +2 -0
  225. package/dist/browser/seo/rules/resources.js +390 -0
  226. package/dist/browser/seo/rules/schema.d.ts +2 -0
  227. package/dist/browser/seo/rules/schema.js +379 -0
  228. package/dist/browser/seo/rules/security.d.ts +2 -0
  229. package/dist/browser/seo/rules/security.js +877 -0
  230. package/dist/browser/seo/rules/social.d.ts +2 -0
  231. package/dist/browser/seo/rules/social.js +603 -0
  232. package/dist/browser/seo/rules/structural.d.ts +2 -0
  233. package/dist/browser/seo/rules/structural.js +179 -0
  234. package/dist/browser/seo/rules/technical-advanced.d.ts +10 -0
  235. package/dist/browser/seo/rules/technical-advanced.js +288 -0
  236. package/dist/browser/seo/rules/technical.d.ts +2 -0
  237. package/dist/browser/seo/rules/technical.js +480 -0
  238. package/dist/browser/seo/rules/thresholds.d.ts +196 -0
  239. package/dist/browser/seo/rules/thresholds.js +118 -0
  240. package/dist/browser/seo/rules/types.d.ts +498 -0
  241. package/dist/browser/seo/rules/types.js +11 -0
  242. package/dist/browser/seo/types.d.ts +211 -0
  243. package/dist/browser/seo/types.js +1 -0
  244. package/dist/browser/transport/curl.d.ts +4 -0
  245. package/dist/browser/transport/curl.js +101 -0
  246. package/dist/browser/transport/undici.js +1 -2
  247. package/dist/browser/transport/worker.d.ts +18 -0
  248. package/dist/browser/transport/worker.js +278 -0
  249. package/dist/browser/types/index.d.ts +4 -1
  250. package/dist/browser/utils/binary-manager.d.ts +4 -0
  251. package/dist/browser/utils/binary-manager.js +72 -0
  252. package/dist/browser/utils/user-agent.js +2 -13
  253. package/dist/cache/indexed-db.d.ts +10 -0
  254. package/dist/cache/indexed-db.js +88 -0
  255. package/dist/cache/service-worker-cache.d.ts +18 -0
  256. package/dist/cache/service-worker-cache.js +103 -0
  257. package/dist/cli/commands/ai.d.ts +2 -0
  258. package/dist/cli/commands/ai.js +162 -0
  259. package/dist/cli/commands/bench.d.ts +2 -0
  260. package/dist/cli/commands/bench.js +51 -0
  261. package/dist/cli/commands/dns.d.ts +2 -0
  262. package/dist/cli/commands/dns.js +295 -0
  263. package/dist/cli/commands/har.d.ts +2 -0
  264. package/dist/cli/commands/har.js +171 -0
  265. package/dist/cli/commands/hls.d.ts +2 -0
  266. package/dist/cli/commands/hls.js +192 -0
  267. package/dist/cli/commands/network.d.ts +2 -0
  268. package/dist/cli/commands/network.js +288 -0
  269. package/dist/cli/commands/protocols.d.ts +2 -0
  270. package/dist/cli/commands/protocols.js +344 -0
  271. package/dist/cli/commands/scrape.d.ts +2 -0
  272. package/dist/cli/commands/scrape.js +176 -0
  273. package/dist/cli/commands/security.d.ts +2 -0
  274. package/dist/cli/commands/security.js +57 -0
  275. package/dist/cli/commands/seo.d.ts +2 -0
  276. package/dist/cli/commands/seo.js +125 -0
  277. package/dist/cli/commands/serve.d.ts +2 -0
  278. package/dist/cli/commands/serve.js +531 -0
  279. package/dist/cli/commands/spider.d.ts +3 -0
  280. package/dist/cli/commands/spider.js +456 -0
  281. package/dist/cli/commands/utils.d.ts +2 -0
  282. package/dist/cli/commands/utils.js +176 -0
  283. package/dist/cli/commands/vector.d.ts +2 -0
  284. package/dist/cli/commands/vector.js +158 -0
  285. package/dist/cli/handler.d.ts +2 -2
  286. package/dist/cli/handler.js +6 -6
  287. package/dist/cli/helpers.d.ts +7 -0
  288. package/dist/cli/helpers.js +128 -0
  289. package/dist/cli/index.js +96 -5228
  290. package/dist/cli/parser/help.d.ts +2 -0
  291. package/dist/cli/parser/help.js +52 -0
  292. package/dist/cli/parser/index.d.ts +3 -0
  293. package/dist/cli/parser/index.js +3 -0
  294. package/dist/cli/parser/parser.d.ts +4 -0
  295. package/dist/cli/parser/parser.js +146 -0
  296. package/dist/cli/parser/types.d.ts +41 -0
  297. package/dist/cli/parser/types.js +1 -0
  298. package/dist/cli/presets.d.ts +1 -1
  299. package/dist/cli/presets.js +1 -1
  300. package/dist/cli/router.d.ts +36 -0
  301. package/dist/cli/router.js +195 -0
  302. package/dist/cli/tui/ai-chat.js +1 -1
  303. package/dist/cli/tui/commands/context.d.ts +9 -0
  304. package/dist/cli/tui/commands/context.js +1 -0
  305. package/dist/cli/tui/commands/dns.d.ts +10 -0
  306. package/dist/cli/tui/commands/dns.js +461 -0
  307. package/dist/cli/tui/commands/hls.d.ts +2 -0
  308. package/dist/cli/tui/commands/hls.js +162 -0
  309. package/dist/cli/tui/commands/ip.d.ts +2 -0
  310. package/dist/cli/tui/commands/ip.js +45 -0
  311. package/dist/cli/tui/commands/network.d.ts +3 -0
  312. package/dist/cli/tui/commands/network.js +81 -0
  313. package/dist/cli/tui/commands/protocols.d.ts +6 -0
  314. package/dist/cli/tui/commands/protocols.js +531 -0
  315. package/dist/cli/tui/commands/security.d.ts +2 -0
  316. package/dist/cli/tui/commands/security.js +48 -0
  317. package/dist/cli/tui/commands/seo.d.ts +2 -0
  318. package/dist/cli/tui/commands/seo.js +74 -0
  319. package/dist/cli/tui/context.d.ts +12 -0
  320. package/dist/cli/tui/context.js +1 -0
  321. package/dist/cli/tui/shell.d.ts +11 -20
  322. package/dist/cli/tui/shell.js +216 -1873
  323. package/dist/constants/user-agents.d.ts +7 -0
  324. package/dist/constants/user-agents.js +7 -0
  325. package/dist/core/client.d.ts +2 -0
  326. package/dist/core/client.js +19 -1
  327. package/dist/index.d.ts +1 -0
  328. package/dist/index.js +1 -0
  329. package/dist/mcp/cli.js +2 -3
  330. package/dist/mcp/data/embeddings.json +1 -0
  331. package/dist/mcp/tools/network.js +298 -158
  332. package/dist/plugins/har-player.d.ts +23 -0
  333. package/dist/plugins/har-player.js +49 -0
  334. package/dist/plugins/har-recorder.d.ts +37 -3
  335. package/dist/plugins/har-recorder.js +116 -63
  336. package/dist/plugins/network-simulation.d.ts +7 -0
  337. package/dist/plugins/network-simulation.js +13 -0
  338. package/dist/presets/android.d.ts +2 -0
  339. package/dist/presets/android.js +16 -0
  340. package/dist/presets/chaturbate.d.ts +2 -0
  341. package/dist/presets/chaturbate.js +17 -0
  342. package/dist/presets/elevenlabs.d.ts +6 -0
  343. package/dist/presets/elevenlabs.js +20 -0
  344. package/dist/presets/enhancers.d.ts +20 -0
  345. package/dist/presets/enhancers.js +85 -0
  346. package/dist/presets/hubspot.d.ts +9 -0
  347. package/dist/presets/hubspot.js +28 -0
  348. package/dist/presets/index.d.ts +10 -0
  349. package/dist/presets/index.js +10 -0
  350. package/dist/presets/ios.d.ts +2 -0
  351. package/dist/presets/ios.js +13 -0
  352. package/dist/presets/pinecone.d.ts +8 -0
  353. package/dist/presets/pinecone.js +42 -0
  354. package/dist/presets/registry.js +60 -0
  355. package/dist/presets/sendgrid.d.ts +6 -0
  356. package/dist/presets/sendgrid.js +20 -0
  357. package/dist/presets/sentry.d.ts +11 -0
  358. package/dist/presets/sentry.js +48 -0
  359. package/dist/presets/square.d.ts +10 -0
  360. package/dist/presets/square.js +33 -0
  361. package/dist/recker.d.ts +3 -0
  362. package/dist/recker.js +4 -0
  363. package/dist/scrape/document.d.ts +5 -4
  364. package/dist/scrape/document.js +89 -76
  365. package/dist/scrape/element.d.ts +10 -8
  366. package/dist/scrape/element.js +295 -81
  367. package/dist/scrape/extractors.d.ts +11 -11
  368. package/dist/scrape/extractors.js +145 -113
  369. package/dist/scrape/index.d.ts +2 -0
  370. package/dist/scrape/index.js +1 -0
  371. package/dist/scrape/parser/back.d.ts +1 -0
  372. package/dist/scrape/parser/back.js +3 -0
  373. package/dist/scrape/parser/index.d.ts +20 -0
  374. package/dist/scrape/parser/index.js +19 -0
  375. package/dist/scrape/parser/matcher.d.ts +30 -0
  376. package/dist/scrape/parser/matcher.js +99 -0
  377. package/dist/scrape/parser/nodes/comment.d.ts +12 -0
  378. package/dist/scrape/parser/nodes/comment.js +21 -0
  379. package/dist/scrape/parser/nodes/html.d.ts +110 -0
  380. package/dist/scrape/parser/nodes/html.js +978 -0
  381. package/dist/scrape/parser/nodes/node.d.ts +18 -0
  382. package/dist/scrape/parser/nodes/node.js +31 -0
  383. package/dist/scrape/parser/nodes/text.d.ts +14 -0
  384. package/dist/scrape/parser/nodes/text.js +30 -0
  385. package/dist/scrape/parser/nodes/type.d.ts +6 -0
  386. package/dist/scrape/parser/nodes/type.js +7 -0
  387. package/dist/scrape/parser/parse.d.ts +1 -0
  388. package/dist/scrape/parser/parse.js +1 -0
  389. package/dist/scrape/parser/valid.d.ts +2 -0
  390. package/dist/scrape/parser/valid.js +5 -0
  391. package/dist/scrape/parser/void-tag.d.ts +7 -0
  392. package/dist/scrape/parser/void-tag.js +43 -0
  393. package/dist/scrape/spider.d.ts +19 -0
  394. package/dist/scrape/spider.js +28 -3
  395. package/dist/scrape/types.d.ts +7 -0
  396. package/dist/seo/analyzer.d.ts +15 -5
  397. package/dist/seo/analyzer.js +636 -175
  398. package/dist/seo/formatter.d.ts +16 -0
  399. package/dist/seo/formatter.js +228 -0
  400. package/dist/seo/index.d.ts +2 -0
  401. package/dist/seo/index.js +1 -0
  402. package/dist/seo/keywords.d.ts +16 -0
  403. package/dist/seo/keywords.js +55 -0
  404. package/dist/seo/rules/accessibility.js +85 -57
  405. package/dist/seo/rules/ai-search.js +44 -31
  406. package/dist/seo/rules/analytics.d.ts +2 -0
  407. package/dist/seo/rules/analytics.js +306 -0
  408. package/dist/seo/rules/best-practices.js +21 -14
  409. package/dist/seo/rules/canonical.js +31 -22
  410. package/dist/seo/rules/content.js +207 -19
  411. package/dist/seo/rules/crawl.js +55 -40
  412. package/dist/seo/rules/cwv.js +21 -15
  413. package/dist/seo/rules/ecommerce.js +51 -20
  414. package/dist/seo/rules/i18n.js +61 -33
  415. package/dist/seo/rules/images.js +87 -28
  416. package/dist/seo/rules/index.js +2 -0
  417. package/dist/seo/rules/internal-linking.js +58 -39
  418. package/dist/seo/rules/links.js +70 -51
  419. package/dist/seo/rules/local.js +49 -25
  420. package/dist/seo/rules/meta.js +161 -62
  421. package/dist/seo/rules/mobile.js +112 -2
  422. package/dist/seo/rules/performance.js +309 -54
  423. package/dist/seo/rules/pwa.js +36 -39
  424. package/dist/seo/rules/readability.js +31 -22
  425. package/dist/seo/rules/redirects.js +21 -15
  426. package/dist/seo/rules/resources.js +59 -42
  427. package/dist/seo/rules/schema.js +333 -8
  428. package/dist/seo/rules/security.js +142 -80
  429. package/dist/seo/rules/social.js +277 -47
  430. package/dist/seo/rules/structural.js +40 -16
  431. package/dist/seo/rules/technical-advanced.js +27 -22
  432. package/dist/seo/rules/technical.js +243 -42
  433. package/dist/seo/rules/types.d.ts +53 -1
  434. package/dist/seo/seo-spider.d.ts +22 -0
  435. package/dist/seo/seo-spider.js +77 -13
  436. package/dist/seo/types.d.ts +8 -1
  437. package/dist/seo/validators/llms-txt.js +19 -0
  438. package/dist/seo/validators/rss.d.ts +11 -0
  439. package/dist/seo/validators/rss.js +93 -0
  440. package/dist/seo/validators/sitemap.js +36 -26
  441. package/dist/transport/curl.d.ts +4 -0
  442. package/dist/transport/curl.js +101 -0
  443. package/dist/transport/udp.js +0 -1
  444. package/dist/transport/undici.js +1 -2
  445. package/dist/transport/worker.d.ts +18 -0
  446. package/dist/transport/worker.js +278 -0
  447. package/dist/types/index.d.ts +4 -1
  448. package/dist/utils/binary-manager.d.ts +4 -0
  449. package/dist/utils/binary-manager.js +72 -0
  450. package/dist/utils/optional-require.d.ts +7 -8
  451. package/dist/utils/optional-require.js +2 -21
  452. package/dist/utils/upload.d.ts +6 -0
  453. package/dist/utils/upload.js +11 -0
  454. package/dist/utils/user-agent.js +2 -13
  455. package/dist/version.js +1 -1
  456. package/package.json +14 -5
  457. package/dist/browser/utils/optional-require.d.ts +0 -19
  458. 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,8 +395,9 @@ 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',
@@ -402,8 +418,9 @@ export const accessibilityRules = [
402
418
  severity: 'info',
403
419
  description: 'Page should contain a heading, skip link, or landmark region',
404
420
  check: (ctx) => {
405
- if (ctx.hasSkipLink === undefined && ctx.hasMain === undefined && ctx.h1Count === undefined)
406
- return null;
421
+ if (ctx.hasSkipLink === undefined && ctx.hasMain === undefined && ctx.h1Count === undefined) {
422
+ 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' });
423
+ }
407
424
  const hasSkip = ctx.hasSkipLink ?? false;
408
425
  const hasMain = ctx.hasMain ?? false;
409
426
  const hasH1 = (ctx.h1Count ?? 0) > 0;
@@ -427,8 +444,9 @@ export const accessibilityRules = [
427
444
  severity: 'info',
428
445
  description: 'Data tables should have caption or aria-label',
429
446
  check: (ctx) => {
430
- if (ctx.tablesWithoutCaption === undefined)
431
- return null;
447
+ if (ctx.tablesWithoutCaption === undefined) {
448
+ 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' });
449
+ }
432
450
  const count = ctx.tablesWithoutCaption;
433
451
  if (count > 0) {
434
452
  return createResult({ id: 'a11y-table-caption', name: 'Table Caption', category: 'accessibility', severity: 'info' }, 'info', `${count} table(s) without caption or aria-label`, {
@@ -451,8 +469,9 @@ export const accessibilityRules = [
451
469
  severity: 'warning',
452
470
  description: '<frame> or <iframe> elements must have a title',
453
471
  check: (ctx) => {
454
- if (ctx.iframesWithoutTitle === undefined)
455
- return null;
472
+ if (ctx.iframesWithoutTitle === undefined) {
473
+ 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' });
474
+ }
456
475
  const count = ctx.iframesWithoutTitle;
457
476
  if (count > 0) {
458
477
  return createResult({ id: 'a11y-iframe-title', name: 'Iframe Title', category: 'accessibility', severity: 'warning' }, 'warn', `${count} iframe(s) without title attribute`, {
@@ -476,8 +495,9 @@ export const accessibilityRules = [
476
495
  severity: 'warning',
477
496
  description: 'SVGs should have <title> or aria-label for accessibility',
478
497
  check: (ctx) => {
479
- if (ctx.svgsWithoutTitle === undefined)
480
- return null;
498
+ if (ctx.svgsWithoutTitle === undefined) {
499
+ 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' });
500
+ }
481
501
  const count = ctx.svgsWithoutTitle;
482
502
  if (count > 0) {
483
503
  return createResult({ id: 'a11y-svg-title', name: 'SVG Title', category: 'accessibility', severity: 'warning' }, 'warn', `${count} SVG(s) without accessible title`, {
@@ -500,8 +520,9 @@ export const accessibilityRules = [
500
520
  severity: 'error',
501
521
  description: '[user-scalable="no"] should not be used and maximum-scale should not be less than 5',
502
522
  check: (ctx) => {
503
- if (ctx.viewportContent === undefined)
504
- return null;
523
+ if (ctx.viewportContent === undefined) {
524
+ 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' });
525
+ }
505
526
  const viewport = ctx.viewportContent.toLowerCase();
506
527
  const hasUserScalableNo = viewport.includes('user-scalable=no') || viewport.includes('user-scalable=0');
507
528
  const maxScaleMatch = viewport.match(/maximum-scale\s*=\s*([\d.]+)/);
@@ -527,8 +548,9 @@ export const accessibilityRules = [
527
548
  severity: 'error',
528
549
  description: 'Document must have a <title> element',
529
550
  check: (ctx) => {
530
- if (ctx.title === undefined)
531
- return null;
551
+ if (ctx.title === undefined) {
552
+ 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' });
553
+ }
532
554
  if (!ctx.title || ctx.title.trim().length === 0) {
533
555
  return createResult({ id: 'a11y-document-title', name: 'Document Title', category: 'accessibility', severity: 'error' }, 'fail', 'Document does not have a <title> element', {
534
556
  recommendation: 'Add a descriptive <title> element to the document',
@@ -549,8 +571,9 @@ export const accessibilityRules = [
549
571
  severity: 'error',
550
572
  description: '<html> element must have a [lang] attribute',
551
573
  check: (ctx) => {
552
- if (ctx.hasLang === undefined)
553
- return null;
574
+ if (ctx.hasLang === undefined) {
575
+ 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' });
576
+ }
554
577
  if (!ctx.hasLang) {
555
578
  return createResult({ id: 'a11y-html-lang', name: 'HTML Lang Attribute', category: 'accessibility', severity: 'error' }, 'fail', '<html> element does not have a [lang] attribute', {
556
579
  recommendation: 'Add lang attribute to html element (e.g., <html lang="en">)',
@@ -571,8 +594,9 @@ export const accessibilityRules = [
571
594
  severity: 'error',
572
595
  description: '<html> element must have a valid value for its [lang] attribute',
573
596
  check: (ctx) => {
574
- if (!ctx.hasLang || !ctx.langValue)
575
- return null;
597
+ if (!ctx.hasLang || !ctx.langValue) {
598
+ 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' });
599
+ }
576
600
  const validLangPattern = /^[a-z]{2,3}(-[A-Za-z]{2,4})?(-[A-Za-z0-9]{2,})?$/i;
577
601
  if (!validLangPattern.test(ctx.langValue)) {
578
602
  return createResult({ id: 'a11y-html-lang-valid', name: 'Valid Lang Attribute', category: 'accessibility', severity: 'error' }, 'fail', `Invalid lang attribute value: ${ctx.langValue}`, {
@@ -595,8 +619,9 @@ export const accessibilityRules = [
595
619
  severity: 'warning',
596
620
  description: '<video> elements should contain a <track> element with [kind="captions"]',
597
621
  check: (ctx) => {
598
- if (ctx.videoCount === undefined || ctx.videosWithCaptions === undefined)
599
- return null;
622
+ if (ctx.videoCount === undefined || ctx.videosWithCaptions === undefined) {
623
+ 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' });
624
+ }
600
625
  const videos = ctx.videoCount;
601
626
  const withCaptions = ctx.videosWithCaptions;
602
627
  if (videos > 0 && withCaptions < videos) {
@@ -614,7 +639,7 @@ export const accessibilityRules = [
614
639
  if (videos > 0) {
615
640
  return createResult({ id: 'a11y-video-captions', name: 'Video Captions', category: 'accessibility', severity: 'warning' }, 'pass', `All ${videos} video(s) have captions`);
616
641
  }
617
- return null;
642
+ 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
643
  },
619
644
  },
620
645
  {
@@ -624,8 +649,9 @@ export const accessibilityRules = [
624
649
  severity: 'warning',
625
650
  description: 'Lists must contain only <li> elements and script supporting elements',
626
651
  check: (ctx) => {
627
- if (ctx.invalidListStructure === undefined)
628
- return null;
652
+ if (ctx.invalidListStructure === undefined) {
653
+ 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)' });
654
+ }
629
655
  const count = ctx.invalidListStructure;
630
656
  if (count > 0) {
631
657
  return createResult({ id: 'a11y-list-structure', name: 'List Structure', category: 'accessibility', severity: 'warning' }, 'warn', `${count} list(s) have invalid structure`, {
@@ -649,8 +675,9 @@ export const accessibilityRules = [
649
675
  severity: 'warning',
650
676
  description: 'All heading elements must contain content',
651
677
  check: (ctx) => {
652
- if (ctx.emptyHeadings === undefined)
653
- return null;
678
+ if (ctx.emptyHeadings === undefined) {
679
+ 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' });
680
+ }
654
681
  const count = ctx.emptyHeadings;
655
682
  if (count > 0) {
656
683
  return createResult({ id: 'a11y-headings-content', name: 'Heading Content', category: 'accessibility', severity: 'warning' }, 'warn', `${count} empty heading element(s) found`, {
@@ -674,8 +701,9 @@ export const accessibilityRules = [
674
701
  severity: 'info',
675
702
  description: 'Image alt attributes should not be redundant',
676
703
  check: (ctx) => {
677
- if (ctx.imagesWithRedundantAlt === undefined)
678
- return null;
704
+ if (ctx.imagesWithRedundantAlt === undefined) {
705
+ 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"' });
706
+ }
679
707
  const count = ctx.imagesWithRedundantAlt;
680
708
  if (count > 0) {
681
709
  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[];