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
@@ -19,7 +19,9 @@ export const i18nRules = [
19
19
  },
20
20
  });
21
21
  }
22
- return null;
22
+ return createResult({ id: 'i18n-hreflang-exists', name: 'Hreflang Tags', category: 'technical', severity: 'warning' }, 'info', 'No hreflang tags found', {
23
+ recommendation: 'Consider adding hreflang tags if you have multi-language versions of this page',
24
+ });
23
25
  }
24
26
  return createResult({ id: 'i18n-hreflang-exists', name: 'Hreflang Tags', category: 'technical', severity: 'warning' }, 'pass', `${ctx.hreflangTags.length} hreflang tag(s) found`, { value: ctx.hreflangTags.length });
25
27
  },
@@ -31,10 +33,14 @@ export const i18nRules = [
31
33
  severity: 'warning',
32
34
  description: 'Hreflang tags should include a self-referencing tag for the current page',
33
35
  check: (ctx) => {
34
- if (!ctx.hreflangTags || ctx.hreflangTags.length === 0)
35
- return null;
36
- if (!ctx.url)
37
- return null;
36
+ if (!ctx.hreflangTags || ctx.hreflangTags.length === 0) {
37
+ return createResult({ id: 'i18n-hreflang-self', name: 'Hreflang Self-Reference', category: 'technical', severity: 'warning' }, 'info', 'Not applicable (no hreflang tags)', {
38
+ recommendation: 'Add hreflang tags first, then ensure self-reference is included',
39
+ });
40
+ }
41
+ if (!ctx.url) {
42
+ return createResult({ id: 'i18n-hreflang-self', name: 'Hreflang Self-Reference', category: 'technical', severity: 'warning' }, 'info', 'Cannot verify (URL not available)');
43
+ }
38
44
  const currentUrl = ctx.url.toLowerCase().replace(/\/$/, '');
39
45
  const hasSelfRef = ctx.hreflangTags.some((tag) => {
40
46
  const href = tag.href?.toLowerCase().replace(/\/$/, '');
@@ -47,10 +53,11 @@ export const i18nRules = [
47
53
  found: `Current URL: ${ctx.url}`,
48
54
  expected: `<link rel="alternate" hreflang="${ctx.langValue || 'en'}" href="${ctx.url}">`,
49
55
  impact: 'Google recommends including a self-referencing hreflang tag for clarity',
56
+ example: `<link rel="alternate" hreflang="en" href="${ctx.url}">\n<link rel="alternate" hreflang="es" href="${ctx.url.replace(/\/en\//, '/es/')}">`,
50
57
  },
51
58
  });
52
59
  }
53
- return null;
60
+ return createResult({ id: 'i18n-hreflang-self', name: 'Hreflang Self-Reference', category: 'technical', severity: 'warning' }, 'pass', 'Self-referencing hreflang tag present');
54
61
  },
55
62
  },
56
63
  {
@@ -60,8 +67,11 @@ export const i18nRules = [
60
67
  severity: 'info',
61
68
  description: 'Include x-default hreflang for users outside defined regions',
62
69
  check: (ctx) => {
63
- if (!ctx.hreflangTags || ctx.hreflangTags.length < 2)
64
- return null;
70
+ if (!ctx.hreflangTags || ctx.hreflangTags.length < 2) {
71
+ return createResult({ id: 'i18n-hreflang-x-default', name: 'Hreflang X-Default', category: 'technical', severity: 'info' }, 'info', 'Not applicable (need 2+ hreflang tags for x-default)', {
72
+ recommendation: 'Add multiple hreflang tags to support international visitors',
73
+ });
74
+ }
65
75
  const hasXDefault = ctx.hreflangTags.some((tag) => tag.lang === 'x-default');
66
76
  if (!hasXDefault) {
67
77
  return createResult({ id: 'i18n-hreflang-x-default', name: 'Hreflang X-Default', category: 'technical', severity: 'info' }, 'info', 'No x-default hreflang tag found', {
@@ -82,8 +92,9 @@ export const i18nRules = [
82
92
  severity: 'warning',
83
93
  description: 'Hreflang language codes must be valid ISO 639-1 codes',
84
94
  check: (ctx) => {
85
- if (!ctx.hreflangTags || ctx.hreflangTags.length === 0)
86
- return null;
95
+ if (!ctx.hreflangTags || ctx.hreflangTags.length === 0) {
96
+ return createResult({ id: 'i18n-hreflang-valid-codes', name: 'Hreflang Valid Codes', category: 'technical', severity: 'warning' }, 'info', 'Not applicable (no hreflang tags)');
97
+ }
87
98
  const validLanguageCodes = new Set([
88
99
  'aa', 'ab', 'af', 'ak', 'sq', 'am', 'ar', 'an', 'hy', 'as', 'av', 'ae', 'ay', 'az',
89
100
  'ba', 'bm', 'eu', 'be', 'bn', 'bh', 'bi', 'bo', 'bs', 'br', 'bg', 'my', 'ca', 'cs',
@@ -113,11 +124,13 @@ export const i18nRules = [
113
124
  evidence: {
114
125
  found: invalidTags,
115
126
  expected: 'Valid ISO 639-1 codes like: en, es, fr, de, pt-BR, zh-CN',
127
+ impact: 'Invalid language codes will be ignored by search engines, breaking international targeting',
128
+ example: '<link rel="alternate" hreflang="en-US" href="https://example.com/en/">\n<link rel="alternate" hreflang="es-ES" href="https://example.com/es/">',
116
129
  learnMore: 'https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes',
117
130
  },
118
131
  });
119
132
  }
120
- return null;
133
+ return createResult({ id: 'i18n-hreflang-valid-codes', name: 'Hreflang Valid Codes', category: 'technical', severity: 'warning' }, 'pass', 'All hreflang codes are valid ISO 639-1');
121
134
  },
122
135
  },
123
136
  {
@@ -127,8 +140,11 @@ export const i18nRules = [
127
140
  severity: 'warning',
128
141
  description: 'All hreflang URLs should return links back to this page (bidirectional)',
129
142
  check: (ctx) => {
130
- if (!ctx.hreflangTags || ctx.hreflangTags.length < 2)
131
- return null;
143
+ if (!ctx.hreflangTags || ctx.hreflangTags.length < 2) {
144
+ return createResult({ id: 'i18n-hreflang-return-links', name: 'Hreflang Return Links', category: 'technical', severity: 'warning' }, 'info', 'Not applicable (need 2+ hreflang tags)', {
145
+ recommendation: 'Add hreflang tags to enable return link verification',
146
+ });
147
+ }
132
148
  return createResult({ id: 'i18n-hreflang-return-links', name: 'Hreflang Return Links', category: 'technical', severity: 'warning' }, 'info', 'Hreflang return links cannot be verified from HTML alone', {
133
149
  recommendation: 'Ensure all alternate pages link back to this page with matching hreflang tags',
134
150
  evidence: {
@@ -145,19 +161,17 @@ export const i18nRules = [
145
161
  severity: 'info',
146
162
  description: 'Content-Language header can indicate the language of the document',
147
163
  check: (ctx) => {
148
- if (!ctx.responseHeaders)
149
- return null;
164
+ if (!ctx.responseHeaders) {
165
+ return createResult({ id: 'i18n-content-language', name: 'Content-Language Header', category: 'technical', severity: 'info' }, 'info', 'Cannot verify (response headers not available)');
166
+ }
150
167
  const contentLang = ctx.responseHeaders['content-language'] || ctx.responseHeaders['Content-Language'];
151
168
  if (!contentLang) {
152
- if (ctx.hasLang) {
153
- return createResult({ id: 'i18n-content-language', name: 'Content-Language Header', category: 'technical', severity: 'info' }, 'info', 'Content-Language header not set', {
154
- recommendation: `Consider adding Content-Language: ${ctx.langValue || 'en'} header`,
155
- evidence: {
156
- impact: 'While not critical for SEO, it helps with content negotiation',
157
- },
158
- });
159
- }
160
- return null;
169
+ return createResult({ id: 'i18n-content-language', name: 'Content-Language Header', category: 'technical', severity: 'info' }, 'info', 'Content-Language header not set', {
170
+ recommendation: `Consider adding Content-Language: ${ctx.langValue || 'en'} header`,
171
+ evidence: {
172
+ impact: 'While not critical for SEO, it helps with content negotiation',
173
+ },
174
+ });
161
175
  }
162
176
  if (ctx.langValue) {
163
177
  const headerLang = Array.isArray(contentLang) ? contentLang[0] : contentLang;
@@ -166,6 +180,11 @@ export const i18nRules = [
166
180
  if (headerLangPrimary !== htmlLangPrimary) {
167
181
  return createResult({ id: 'i18n-content-language', name: 'Content-Language Header', category: 'technical', severity: 'info' }, 'warn', `Content-Language (${headerLang}) doesn't match html lang (${ctx.langValue})`, {
168
182
  recommendation: 'Ensure Content-Language header matches the html lang attribute',
183
+ evidence: {
184
+ found: `Content-Language: ${headerLang}, html lang="${ctx.langValue}"`,
185
+ expected: 'Both should declare the same language',
186
+ impact: 'Mismatched language declarations can confuse browsers and search engines',
187
+ },
169
188
  });
170
189
  }
171
190
  }
@@ -179,8 +198,18 @@ export const i18nRules = [
179
198
  severity: 'warning',
180
199
  description: 'HTML lang attribute should match the og:locale if present',
181
200
  check: (ctx) => {
182
- if (!ctx.hasLang || !ctx.ogLocale)
183
- return null;
201
+ if (!ctx.hasLang && !ctx.ogLocale) {
202
+ return createResult({ id: 'i18n-lang-consistency', name: 'Language Consistency', category: 'technical', severity: 'warning' }, 'info', 'No lang attribute or og:locale to compare', {
203
+ recommendation: 'Add html lang attribute and og:locale for language consistency',
204
+ });
205
+ }
206
+ if (!ctx.hasLang || !ctx.ogLocale) {
207
+ return createResult({ id: 'i18n-lang-consistency', name: 'Language Consistency', category: 'technical', severity: 'warning' }, 'info', ctx.hasLang ? 'No og:locale to compare with html lang' : 'No html lang to compare with og:locale', {
208
+ recommendation: ctx.hasLang
209
+ ? 'Add og:locale meta tag for social platforms'
210
+ : 'Add html lang attribute for language declaration',
211
+ });
212
+ }
184
213
  const htmlLang = ctx.langValue?.toLowerCase().split('-')[0];
185
214
  const ogLocaleLang = ctx.ogLocale.toLowerCase().split('_')[0];
186
215
  if (htmlLang !== ogLocaleLang) {
@@ -188,11 +217,13 @@ export const i18nRules = [
188
217
  recommendation: 'Ensure html lang and og:locale represent the same language',
189
218
  evidence: {
190
219
  found: [`html lang="${ctx.langValue}"`, `og:locale="${ctx.ogLocale}"`],
220
+ expected: 'Both attributes should use the same language code',
191
221
  impact: 'Inconsistent language signals can confuse search engines and social platforms',
222
+ example: '<html lang="en">\n<meta property="og:locale" content="en_US">',
192
223
  },
193
224
  });
194
225
  }
195
- return null;
226
+ return createResult({ id: 'i18n-lang-consistency', name: 'Language Consistency', category: 'technical', severity: 'warning' }, 'pass', `Language consistent: html lang="${ctx.langValue}", og:locale="${ctx.ogLocale}"`);
196
227
  },
197
228
  },
198
229
  {
@@ -202,8 +233,11 @@ export const i18nRules = [
202
233
  severity: 'info',
203
234
  description: 'Consider using region-specific language codes for better targeting',
204
235
  check: (ctx) => {
205
- if (!ctx.hasLang || !ctx.langValue)
206
- return null;
236
+ if (!ctx.hasLang || !ctx.langValue) {
237
+ return createResult({ id: 'i18n-lang-region', name: 'Language Region Specificity', category: 'technical', severity: 'info' }, 'info', 'No lang attribute to analyze', {
238
+ recommendation: 'Add html lang attribute first',
239
+ });
240
+ }
207
241
  const multiRegionalLangs = ['en', 'es', 'pt', 'zh', 'fr', 'de', 'ar'];
208
242
  const langPrimary = ctx.langValue.toLowerCase().split('-')[0];
209
243
  if (multiRegionalLangs.includes(langPrimary) && !ctx.langValue.includes('-')) {
@@ -216,7 +250,7 @@ export const i18nRules = [
216
250
  },
217
251
  });
218
252
  }
219
- return null;
253
+ return createResult({ id: 'i18n-lang-region', name: 'Language Region Specificity', category: 'technical', severity: 'info' }, 'pass', `Lang attribute: ${ctx.langValue}`);
220
254
  },
221
255
  },
222
256
  {
@@ -226,8 +260,12 @@ export const i18nRules = [
226
260
  severity: 'warning',
227
261
  description: 'Hreflang language should match page content language',
228
262
  check: (ctx) => {
229
- if (!ctx.hreflangTags || !ctx.detectedLanguage)
230
- return null;
263
+ if (!ctx.hreflangTags || ctx.hreflangTags.length === 0) {
264
+ return createResult({ id: 'hreflang-language-mismatch', name: 'Hreflang Language Mismatch', category: 'technical', severity: 'warning' }, 'info', 'Not applicable (no hreflang tags)');
265
+ }
266
+ if (!ctx.detectedLanguage) {
267
+ return createResult({ id: 'hreflang-language-mismatch', name: 'Hreflang Language Mismatch', category: 'technical', severity: 'warning' }, 'info', 'Cannot verify (language detection not available)');
268
+ }
231
269
  const selfHreflang = ctx.hreflangTags.find(tag => tag.href === ctx.url || tag.href === ctx.canonicalUrl);
232
270
  if (selfHreflang && ctx.detectedLanguage) {
233
271
  const hreflangLang = selfHreflang.lang.split('-')[0].toLowerCase();
@@ -236,14 +274,15 @@ export const i18nRules = [
236
274
  return createResult({ id: 'hreflang-language-mismatch', name: 'Hreflang Language Mismatch', category: 'technical', severity: 'warning' }, 'warn', `Hreflang declares "${selfHreflang.lang}" but content appears to be "${ctx.detectedLanguage}"`, {
237
275
  recommendation: 'Verify the hreflang attribute matches the actual page language',
238
276
  evidence: {
239
- found: `hreflang="${selfHreflang.lang}"`,
240
- expected: `Content language: ${ctx.detectedLanguage}`,
241
- impact: 'Language mismatch may confuse search engines and affect international SEO'
277
+ found: `hreflang="${selfHreflang.lang}", detected content language: "${ctx.detectedLanguage}"`,
278
+ expected: `hreflang="${ctx.detectedLanguage}"`,
279
+ impact: 'Language mismatch may confuse search engines and affect international SEO',
280
+ example: `<link rel="alternate" hreflang="${ctx.detectedLanguage}" href="${ctx.url}">`,
242
281
  }
243
282
  });
244
283
  }
245
284
  }
246
- return null;
285
+ return createResult({ id: 'hreflang-language-mismatch', name: 'Hreflang Language Mismatch', category: 'technical', severity: 'warning' }, 'pass', 'Hreflang language matches detected content language');
247
286
  },
248
287
  },
249
288
  ];
@@ -8,13 +8,24 @@ export const imageRules = [
8
8
  severity: 'error',
9
9
  description: 'All images must have alt text',
10
10
  check: (ctx) => {
11
- if (ctx.totalImages === undefined || ctx.totalImages === 0)
12
- return null;
11
+ if (ctx.totalImages === undefined || ctx.totalImages === 0) {
12
+ return createResult({ id: 'images-alt-text', name: 'Image Alt Text', category: 'images', severity: 'error' }, 'info', 'Not applicable (no images detected)', { recommendation: 'This rule ensures all images have descriptive alt text for accessibility and SEO' });
13
+ }
13
14
  const withoutAlt = ctx.imagesWithoutAlt ?? 0;
14
15
  if (withoutAlt > 0) {
15
16
  const percentage = Math.round((withoutAlt / ctx.totalImages) * 100);
16
17
  const severity = withoutAlt > ctx.totalImages / 2 ? 'fail' : 'warn';
17
- return createResult({ id: 'images-alt-text', name: 'Image Alt Text', category: 'images', severity: 'error' }, severity, `${withoutAlt} of ${ctx.totalImages} images missing alt text (${percentage}%)`, { value: withoutAlt, recommendation: 'Add descriptive alt text to all images' });
18
+ return createResult({ id: 'images-alt-text', name: 'Image Alt Text', category: 'images', severity: 'error' }, severity, `${withoutAlt} of ${ctx.totalImages} images missing alt text (${percentage}%)`, {
19
+ value: withoutAlt,
20
+ recommendation: 'Add descriptive alt text to all images. Describe what the image shows and its relevance to the content.',
21
+ evidence: {
22
+ found: `${withoutAlt} images without alt attribute`,
23
+ expected: 'All images should have meaningful alt text',
24
+ impact: 'Missing alt text hurts accessibility (screen readers) and prevents images from appearing in Google Image Search. Alt text is also used as anchor text when images are linked.',
25
+ example: '<img src="product.jpg" alt="Red leather wallet with zipper closure - front view">',
26
+ learnMore: 'https://developers.google.com/search/docs/appearance/google-images#use-descriptive-alt-text'
27
+ }
28
+ });
18
29
  }
19
30
  return createResult({ id: 'images-alt-text', name: 'Image Alt Text', category: 'images', severity: 'error' }, 'pass', 'All images have alt text');
20
31
  },
@@ -26,11 +37,23 @@ export const imageRules = [
26
37
  severity: 'warning',
27
38
  description: 'Images should have width and height attributes to prevent CLS',
28
39
  check: (ctx) => {
29
- if (ctx.totalImages === undefined || ctx.totalImages === 0)
30
- return null;
40
+ if (ctx.totalImages === undefined || ctx.totalImages === 0) {
41
+ return createResult({ id: 'images-dimensions', name: 'Image Dimensions', category: 'images', severity: 'warning' }, 'info', 'Not applicable (no images detected)', { recommendation: 'This rule checks that images have width and height attributes to prevent layout shifts' });
42
+ }
31
43
  const missing = ctx.imagesMissingDimensions ?? 0;
32
44
  if (missing > 0) {
33
- return createResult({ id: 'images-dimensions', name: 'Image Dimensions', category: 'images', severity: 'warning' }, 'warn', `${missing} images missing width/height attributes`, { value: missing, recommendation: 'Add width and height to prevent layout shifts (CLS)' });
45
+ const percentage = Math.round((missing / ctx.totalImages) * 100);
46
+ return createResult({ id: 'images-dimensions', name: 'Image Dimensions', category: 'images', severity: 'warning' }, 'warn', `${missing} of ${ctx.totalImages} images missing width/height (${percentage}%)`, {
47
+ value: missing,
48
+ recommendation: 'Add explicit width and height attributes to all images to reserve space and prevent layout shifts.',
49
+ evidence: {
50
+ found: `${missing} images without dimensions`,
51
+ expected: 'All images should have width and height attributes',
52
+ impact: 'Images without dimensions cause Cumulative Layout Shift (CLS), which negatively affects Core Web Vitals and user experience.',
53
+ example: '<img src="photo.jpg" width="800" height="600" alt="Description">',
54
+ learnMore: 'https://web.dev/optimize-cls/#images-without-dimensions'
55
+ }
56
+ });
34
57
  }
35
58
  return createResult({ id: 'images-dimensions', name: 'Image Dimensions', category: 'images', severity: 'warning' }, 'pass', 'All images have dimensions defined');
36
59
  },
@@ -42,11 +65,21 @@ export const imageRules = [
42
65
  severity: 'info',
43
66
  description: 'Below-the-fold images should use lazy loading',
44
67
  check: (ctx) => {
45
- if (ctx.totalImages === undefined || ctx.totalImages <= 3)
46
- return null;
68
+ if (ctx.totalImages === undefined || ctx.totalImages <= 3) {
69
+ return createResult({ id: 'images-lazy-loading', name: 'Lazy Loading', category: 'images', severity: 'info' }, 'info', 'Not applicable (too few images to require lazy loading)', { recommendation: 'This rule checks for lazy loading on pages with multiple images' });
70
+ }
47
71
  const lazy = ctx.imagesWithLazyLoad ?? 0;
48
72
  if (lazy === 0) {
49
- return createResult({ id: 'images-lazy-loading', name: 'Lazy Loading', category: 'images', severity: 'info' }, 'info', 'No images use lazy loading', { recommendation: 'Add loading="lazy" to below-the-fold images' });
73
+ return createResult({ id: 'images-lazy-loading', name: 'Lazy Loading', category: 'images', severity: 'info' }, 'info', 'No images use lazy loading', {
74
+ recommendation: 'Add loading="lazy" to below-the-fold images to defer loading until they are near the viewport.',
75
+ evidence: {
76
+ found: 'No images with loading="lazy"',
77
+ expected: 'Below-the-fold images should use lazy loading',
78
+ impact: 'Lazy loading reduces initial page load time, saves bandwidth, and improves Core Web Vitals (LCP).',
79
+ example: '<img src="photo.jpg" loading="lazy" alt="Description">',
80
+ learnMore: 'https://web.dev/browser-level-image-lazy-loading/'
81
+ }
82
+ });
50
83
  }
51
84
  return createResult({ id: 'images-lazy-loading', name: 'Lazy Loading', category: 'images', severity: 'info' }, 'pass', `${lazy} images use lazy loading`);
52
85
  },
@@ -58,11 +91,22 @@ export const imageRules = [
58
91
  severity: 'info',
59
92
  description: 'Images should use modern formats like WebP or AVIF',
60
93
  check: (ctx) => {
61
- if (ctx.totalImages === undefined || ctx.totalImages === 0)
62
- return null;
94
+ if (ctx.totalImages === undefined || ctx.totalImages === 0) {
95
+ return createResult({ id: 'images-format-modern', name: 'Modern Image Formats', category: 'images', severity: 'info' }, 'info', 'Not applicable (no images detected)', { recommendation: 'This rule checks for modern image formats like WebP and AVIF for better performance' });
96
+ }
63
97
  const modern = ctx.imagesUsingModernFormats ?? 0;
64
98
  if (ctx.totalImages > 0 && modern === 0) {
65
- return createResult({ id: 'images-format-modern', name: 'Modern Image Formats', category: 'images', severity: 'info' }, 'info', 'No images using modern formats (WebP/AVIF)', { value: modern, recommendation: 'Serve images in WebP or AVIF format for better compression' });
99
+ return createResult({ id: 'images-format-modern', name: 'Modern Image Formats', category: 'images', severity: 'info' }, 'info', 'No images using modern formats (WebP/AVIF)', {
100
+ value: modern,
101
+ recommendation: 'Convert images to WebP or AVIF format for 25-50% smaller file sizes with same quality.',
102
+ evidence: {
103
+ found: 'Only traditional formats (JPEG, PNG, GIF)',
104
+ expected: 'At least some images in WebP or AVIF format',
105
+ impact: 'Modern formats significantly reduce page weight and improve load times. WebP has 94% browser support.',
106
+ example: '<picture>\n <source srcset="image.avif" type="image/avif">\n <source srcset="image.webp" type="image/webp">\n <img src="image.jpg" alt="Description">\n</picture>',
107
+ learnMore: 'https://web.dev/uses-webp-images/'
108
+ }
109
+ });
66
110
  }
67
111
  return createResult({ id: 'images-format-modern', name: 'Modern Image Formats', category: 'images', severity: 'info' }, 'pass', `${modern} images using modern formats`, { value: modern });
68
112
  },
@@ -78,7 +122,7 @@ export const imageRules = [
78
122
  if (emptyAlt > 0) {
79
123
  return createResult({ id: 'images-empty-alt', name: 'Empty Alt Text', category: 'images', severity: 'info' }, 'info', `${emptyAlt} image(s) with empty alt="" (decorative)`, { value: emptyAlt, recommendation: 'Ensure these images are truly decorative' });
80
124
  }
81
- return null;
125
+ return createResult({ id: 'images-empty-alt', name: 'Empty Alt Text', category: 'images', severity: 'info' }, 'pass', 'No images with empty alt attributes');
82
126
  },
83
127
  },
84
128
  {
@@ -88,8 +132,9 @@ export const imageRules = [
88
132
  severity: 'warning',
89
133
  description: 'Alt text should be descriptive (ideal 80-120, max 150 chars)',
90
134
  check: (ctx) => {
91
- if (!ctx.altTextLengths || ctx.altTextLengths.length === 0)
92
- return null;
135
+ if (!ctx.altTextLengths || ctx.altTextLengths.length === 0) {
136
+ return createResult({ id: 'images-alt-length', name: 'Alt Text Length', category: 'images', severity: 'warning' }, 'info', 'Not applicable (no alt text data available)', { recommendation: 'This rule checks alt text length to ensure descriptions are meaningful' });
137
+ }
93
138
  const { minLength, idealLength, maxLength } = SEO_THRESHOLDS.images.alt;
94
139
  let shortAlts = 0;
95
140
  let longAlts = 0;
@@ -103,15 +148,35 @@ export const imageRules = [
103
148
  nonIdealAlts++;
104
149
  });
105
150
  if (shortAlts > 0) {
106
- return createResult({ id: 'images-alt-length', name: 'Alt Text Length', category: 'images', severity: 'warning' }, 'warn', `${shortAlts} alt text(s) are too short (min: ${minLength} chars)`, { value: shortAlts, recommendation: `Make alt texts more descriptive, at least ${minLength} characters.` });
151
+ return createResult({ id: 'images-alt-length', name: 'Alt Text Length', category: 'images', severity: 'warning' }, 'warn', `${shortAlts} alt text(s) are too short (min: ${minLength} chars)`, {
152
+ value: shortAlts,
153
+ recommendation: `Make alt texts more descriptive, at least ${minLength} characters.`,
154
+ evidence: {
155
+ found: `${shortAlts} alt text(s) with less than ${minLength} characters`,
156
+ expected: `Alt text should be at least ${minLength} characters to be meaningful`,
157
+ impact: 'Very short alt text (like "logo" or "pic") doesn\'t provide enough context for screen readers or search engines, reducing accessibility and SEO value.',
158
+ example: 'Instead of alt="logo", use alt="Acme Corp logo - handshake symbol representing trust"',
159
+ learnMore: 'https://www.w3.org/WAI/tutorials/images/'
160
+ }
161
+ });
107
162
  }
108
163
  if (longAlts > 0) {
109
- return createResult({ id: 'images-alt-length', name: 'Alt Text Length', category: 'images', severity: 'warning' }, 'warn', `${longAlts} alt text(s) are too long (max: ${maxLength} chars)`, { value: longAlts, recommendation: `Shorten alt texts to be concise, under ${maxLength} characters.` });
164
+ return createResult({ id: 'images-alt-length', name: 'Alt Text Length', category: 'images', severity: 'warning' }, 'warn', `${longAlts} alt text(s) are too long (max: ${maxLength} chars)`, {
165
+ value: longAlts,
166
+ recommendation: `Shorten alt texts to be concise, under ${maxLength} characters.`,
167
+ evidence: {
168
+ found: `${longAlts} alt text(s) exceeding ${maxLength} characters`,
169
+ expected: `Alt text should be under ${maxLength} characters for optimal accessibility`,
170
+ impact: 'Excessively long alt text can be tedious for screen reader users and may get truncated in search results. Keep it concise while still being descriptive.',
171
+ example: 'Instead of a long paragraph, use concise descriptions like: alt="Professional headshot of Jane Smith, CEO, wearing navy blue suit"',
172
+ learnMore: 'https://www.w3.org/WAI/tutorials/images/'
173
+ }
174
+ });
110
175
  }
111
176
  if (nonIdealAlts > 0) {
112
177
  return createResult({ id: 'images-alt-length', name: 'Alt Text Length', category: 'images', severity: 'info' }, 'info', `${nonIdealAlts} alt text(s) are not in the ideal length range (${idealLength.min}-${idealLength.max} chars)`, { value: nonIdealAlts, recommendation: `Aim for alt texts between ${idealLength.min} and ${idealLength.max} characters for best results.` });
113
178
  }
114
- return null;
179
+ return createResult({ id: 'images-alt-length', name: 'Alt Text Length', category: 'images', severity: 'warning' }, 'pass', 'All alt text lengths are within ideal range');
115
180
  },
116
181
  },
117
182
  {
@@ -121,15 +186,26 @@ export const imageRules = [
121
186
  severity: 'info',
122
187
  description: 'Image filenames should be descriptive and use keywords, not generic names.',
123
188
  check: (ctx) => {
124
- if (!ctx.imageFilenames || ctx.imageFilenames.length === 0)
125
- return null;
189
+ if (!ctx.imageFilenames || ctx.imageFilenames.length === 0) {
190
+ return createResult({ id: 'images-clean-filenames', name: 'Image Filenames', category: 'images', severity: 'info' }, 'info', 'Not applicable (no image filename data available)', { recommendation: 'This rule checks for descriptive image filenames with keywords' });
191
+ }
126
192
  const genericFilenames = ctx.imageFilenames.filter(name => /^(img|image|photo|pic)\d*\.(jpg|jpeg|png|webp|avif|gif)$/i.test(name) ||
127
193
  /^screenshot_\d*\.(jpg|jpeg|png)$/i.test(name) ||
128
194
  /^untitled-\d*\.(jpg|jpeg|png)$/i.test(name));
129
195
  if (genericFilenames.length > 0) {
130
- return createResult({ id: 'images-clean-filenames', name: 'Image Filenames', category: 'images', severity: 'warning' }, 'warn', `${genericFilenames.length} image(s) have generic filenames (e.g., IMG_1234.jpg)`, { value: genericFilenames.length, recommendation: 'Rename image files to be descriptive and include keywords (e.g., open-graph-example.jpg).' });
196
+ return createResult({ id: 'images-clean-filenames', name: 'Image Filenames', category: 'images', severity: 'warning' }, 'warn', `${genericFilenames.length} image(s) have generic filenames`, {
197
+ value: genericFilenames.length,
198
+ recommendation: 'Rename image files to be descriptive and include relevant keywords.',
199
+ evidence: {
200
+ found: genericFilenames.slice(0, 5),
201
+ expected: 'Descriptive filenames with keywords (e.g., red-leather-wallet-front.jpg)',
202
+ impact: 'Image filenames are used by search engines to understand image content and can appear in Google Image Search.',
203
+ example: 'Instead of "IMG_1234.jpg", use "blue-running-shoes-nike-air.jpg"',
204
+ learnMore: 'https://developers.google.com/search/docs/appearance/google-images#descriptive-titles-captions-filenames'
205
+ }
206
+ });
131
207
  }
132
- return null;
208
+ return createResult({ id: 'images-clean-filenames', name: 'Image Filenames', category: 'images', severity: 'info' }, 'pass', 'All image filenames are descriptive');
133
209
  },
134
210
  },
135
211
  {
@@ -139,15 +215,17 @@ export const imageRules = [
139
215
  severity: 'info',
140
216
  description: 'Use decoding="async" for non-critical images to improve rendering performance.',
141
217
  check: (ctx) => {
142
- if (ctx.totalImages === undefined || ctx.totalImages === 0)
143
- return null;
144
- if (ctx.imagesWithAsyncDecoding === undefined)
145
- return null;
218
+ if (ctx.totalImages === undefined || ctx.totalImages === 0) {
219
+ return createResult({ id: 'images-decoding-async', name: 'Image Decoding Async', category: 'images', severity: 'info' }, 'info', 'Not applicable (no images detected)', { recommendation: 'This rule checks for async decoding on images for better rendering performance' });
220
+ }
221
+ if (ctx.imagesWithAsyncDecoding === undefined) {
222
+ return createResult({ id: 'images-decoding-async', name: 'Image Decoding Async', category: 'images', severity: 'info' }, 'info', 'Not applicable (async decoding data unavailable)', { recommendation: 'This rule checks for async decoding on images for better rendering performance' });
223
+ }
146
224
  const nonAsync = ctx.totalImages - ctx.imagesWithAsyncDecoding;
147
225
  if (nonAsync > 0 && ctx.totalImages > 3) {
148
226
  return createResult({ id: 'images-decoding-async', name: 'Image Decoding Async', category: 'images', severity: 'info' }, 'info', `${nonAsync} image(s) do not use decoding="async"`, { value: nonAsync, recommendation: 'Consider adding decoding="async" to non-critical images for performance benefits.' });
149
227
  }
150
- return null;
228
+ return createResult({ id: 'images-decoding-async', name: 'Image Decoding Async', category: 'images', severity: 'info' }, 'pass', 'All images use async decoding or too few images to require it');
151
229
  },
152
230
  },
153
231
  {
@@ -157,8 +235,9 @@ export const imageRules = [
157
235
  severity: 'warning',
158
236
  description: 'External images should be accessible',
159
237
  check: (ctx) => {
160
- if (ctx.brokenExternalImages === undefined)
161
- return null;
238
+ if (ctx.brokenExternalImages === undefined) {
239
+ return createResult({ id: 'broken-external-images', name: 'Broken External Images', category: 'images', severity: 'warning' }, 'info', 'Not applicable (broken external images data unavailable)', { recommendation: 'This rule checks for broken external image references' });
240
+ }
162
241
  if (ctx.brokenExternalImages > 0) {
163
242
  return createResult({ id: 'broken-external-images', name: 'Broken External Images', category: 'images', severity: 'warning' }, 'warn', `${ctx.brokenExternalImages} broken external images`, {
164
243
  value: ctx.brokenExternalImages,
@@ -170,7 +249,7 @@ export const imageRules = [
170
249
  }
171
250
  });
172
251
  }
173
- return null;
252
+ return createResult({ id: 'broken-external-images', name: 'Broken External Images', category: 'images', severity: 'warning' }, 'pass', 'No broken external images detected');
174
253
  },
175
254
  },
176
255
  ];
@@ -24,6 +24,7 @@ import { resourceRules } from './resources.js';
24
24
  import { technicalAdvancedRules } from './technical-advanced.js';
25
25
  import { redirectRules } from './redirects.js';
26
26
  import { canonicalRules } from './canonical.js';
27
+ import { analyticsRules } from './analytics.js';
27
28
  export * from './types.js';
28
29
  export * from './thresholds.js';
29
30
  export const ALL_SEO_RULES = [
@@ -53,6 +54,7 @@ export const ALL_SEO_RULES = [
53
54
  ...technicalAdvancedRules,
54
55
  ...redirectRules,
55
56
  ...canonicalRules,
57
+ ...analyticsRules,
56
58
  ];
57
59
  export const SCORING_WEIGHTS = {
58
60
  severity: {