recker 1.0.43 → 1.0.44

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (459) hide show
  1. package/README.md +47 -0
  2. package/dist/bin/recker-linux-x64 +0 -0
  3. package/dist/bin/recker-macos-x64 +0 -0
  4. package/dist/bin/recker-win-x64.exe +0 -0
  5. package/dist/bin/rek.cjs +85152 -100207
  6. package/dist/browser/ai/adaptive-timeout.d.ts +50 -0
  7. package/dist/browser/ai/adaptive-timeout.js +208 -0
  8. package/dist/browser/ai/client.d.ts +22 -0
  9. package/dist/browser/ai/client.js +294 -0
  10. package/dist/browser/ai/index.d.ts +14 -0
  11. package/dist/browser/ai/index.js +11 -0
  12. package/dist/browser/ai/providers/anthropic.d.ts +63 -0
  13. package/dist/browser/ai/providers/anthropic.js +370 -0
  14. package/dist/browser/ai/providers/base.d.ts +48 -0
  15. package/dist/browser/ai/providers/base.js +150 -0
  16. package/dist/browser/ai/providers/google.d.ts +59 -0
  17. package/dist/browser/ai/providers/google.js +305 -0
  18. package/dist/browser/ai/providers/ollama.d.ts +44 -0
  19. package/dist/browser/ai/providers/ollama.js +240 -0
  20. package/dist/browser/ai/providers/openai.d.ts +64 -0
  21. package/dist/browser/ai/providers/openai.js +298 -0
  22. package/dist/browser/ai/rate-limiter.d.ts +43 -0
  23. package/dist/browser/ai/rate-limiter.js +215 -0
  24. package/dist/browser/ai/vector/index.d.ts +2 -0
  25. package/dist/browser/ai/vector/index.js +2 -0
  26. package/dist/browser/ai/vector/similarity.d.ts +2 -0
  27. package/dist/browser/ai/vector/similarity.js +27 -0
  28. package/dist/browser/ai/vector/store.d.ts +27 -0
  29. package/dist/browser/ai/vector/store.js +82 -0
  30. package/dist/browser/browser/cache.d.ts +2 -40
  31. package/dist/browser/browser/cache.js +2 -199
  32. package/dist/browser/browser/index.d.ts +8 -0
  33. package/dist/browser/browser/index.js +8 -0
  34. package/dist/browser/browser/recker.d.ts +8 -1
  35. package/dist/browser/browser/recker.js +8 -2
  36. package/dist/browser/cache/indexed-db.d.ts +10 -0
  37. package/dist/browser/cache/indexed-db.js +88 -0
  38. package/dist/browser/cache/service-worker-cache.d.ts +18 -0
  39. package/dist/browser/cache/service-worker-cache.js +103 -0
  40. package/dist/browser/cache.d.ts +2 -40
  41. package/dist/browser/cache.js +2 -199
  42. package/dist/browser/constants/user-agents.d.ts +7 -0
  43. package/dist/browser/constants/user-agents.js +7 -0
  44. package/dist/browser/core/client.d.ts +2 -0
  45. package/dist/browser/core/client.js +19 -1
  46. package/dist/browser/index.d.ts +8 -0
  47. package/dist/browser/index.js +8 -0
  48. package/dist/browser/plugins/har-recorder.d.ts +40 -0
  49. package/dist/browser/plugins/har-recorder.js +120 -0
  50. package/dist/browser/plugins/network-simulation.d.ts +7 -0
  51. package/dist/browser/plugins/network-simulation.js +13 -0
  52. package/dist/browser/presets/android.d.ts +2 -0
  53. package/dist/browser/presets/android.js +16 -0
  54. package/dist/browser/presets/anthropic.d.ts +8 -0
  55. package/dist/browser/presets/anthropic.js +27 -0
  56. package/dist/browser/presets/aws.d.ts +19 -0
  57. package/dist/browser/presets/aws.js +68 -0
  58. package/dist/browser/presets/azure-openai.d.ts +10 -0
  59. package/dist/browser/presets/azure-openai.js +35 -0
  60. package/dist/browser/presets/azure.d.ts +41 -0
  61. package/dist/browser/presets/azure.js +104 -0
  62. package/dist/browser/presets/chaturbate.d.ts +2 -0
  63. package/dist/browser/presets/chaturbate.js +17 -0
  64. package/dist/browser/presets/cloudflare.d.ts +12 -0
  65. package/dist/browser/presets/cloudflare.js +39 -0
  66. package/dist/browser/presets/cohere.d.ts +7 -0
  67. package/dist/browser/presets/cohere.js +22 -0
  68. package/dist/browser/presets/deepseek.d.ts +7 -0
  69. package/dist/browser/presets/deepseek.js +22 -0
  70. package/dist/browser/presets/digitalocean.d.ts +5 -0
  71. package/dist/browser/presets/digitalocean.js +16 -0
  72. package/dist/browser/presets/discord.d.ts +6 -0
  73. package/dist/browser/presets/discord.js +17 -0
  74. package/dist/browser/presets/elevenlabs.d.ts +6 -0
  75. package/dist/browser/presets/elevenlabs.js +20 -0
  76. package/dist/browser/presets/enhancers.d.ts +20 -0
  77. package/dist/browser/presets/enhancers.js +85 -0
  78. package/dist/browser/presets/fireworks.d.ts +7 -0
  79. package/dist/browser/presets/fireworks.js +22 -0
  80. package/dist/browser/presets/gcp.d.ts +34 -0
  81. package/dist/browser/presets/gcp.js +91 -0
  82. package/dist/browser/presets/gemini.d.ts +7 -0
  83. package/dist/browser/presets/gemini.js +23 -0
  84. package/dist/browser/presets/github.d.ts +6 -0
  85. package/dist/browser/presets/github.js +17 -0
  86. package/dist/browser/presets/gitlab.d.ts +6 -0
  87. package/dist/browser/presets/gitlab.js +16 -0
  88. package/dist/browser/presets/groq.d.ts +7 -0
  89. package/dist/browser/presets/groq.js +22 -0
  90. package/dist/browser/presets/hubspot.d.ts +9 -0
  91. package/dist/browser/presets/hubspot.js +28 -0
  92. package/dist/browser/presets/huggingface.d.ts +7 -0
  93. package/dist/browser/presets/huggingface.js +23 -0
  94. package/dist/browser/presets/index.d.ts +47 -0
  95. package/dist/browser/presets/index.js +47 -0
  96. package/dist/browser/presets/ios.d.ts +2 -0
  97. package/dist/browser/presets/ios.js +13 -0
  98. package/dist/browser/presets/linear.d.ts +5 -0
  99. package/dist/browser/presets/linear.js +16 -0
  100. package/dist/browser/presets/mailgun.d.ts +7 -0
  101. package/dist/browser/presets/mailgun.js +20 -0
  102. package/dist/browser/presets/meta.d.ts +10 -0
  103. package/dist/browser/presets/meta.js +33 -0
  104. package/dist/browser/presets/mistral.d.ts +7 -0
  105. package/dist/browser/presets/mistral.js +22 -0
  106. package/dist/browser/presets/notion.d.ts +6 -0
  107. package/dist/browser/presets/notion.js +17 -0
  108. package/dist/browser/presets/openai.d.ts +9 -0
  109. package/dist/browser/presets/openai.js +30 -0
  110. package/dist/browser/presets/oracle.d.ts +19 -0
  111. package/dist/browser/presets/oracle.js +117 -0
  112. package/dist/browser/presets/perplexity.d.ts +7 -0
  113. package/dist/browser/presets/perplexity.js +22 -0
  114. package/dist/browser/presets/pinecone.d.ts +8 -0
  115. package/dist/browser/presets/pinecone.js +42 -0
  116. package/dist/browser/presets/registry.d.ts +23 -0
  117. package/dist/browser/presets/registry.js +519 -0
  118. package/dist/browser/presets/replicate.d.ts +7 -0
  119. package/dist/browser/presets/replicate.js +23 -0
  120. package/dist/browser/presets/sendgrid.d.ts +6 -0
  121. package/dist/browser/presets/sendgrid.js +20 -0
  122. package/dist/browser/presets/sentry.d.ts +11 -0
  123. package/dist/browser/presets/sentry.js +48 -0
  124. package/dist/browser/presets/sinch.d.ts +9 -0
  125. package/dist/browser/presets/sinch.js +39 -0
  126. package/dist/browser/presets/slack.d.ts +5 -0
  127. package/dist/browser/presets/slack.js +16 -0
  128. package/dist/browser/presets/square.d.ts +10 -0
  129. package/dist/browser/presets/square.js +33 -0
  130. package/dist/browser/presets/stripe.d.ts +7 -0
  131. package/dist/browser/presets/stripe.js +23 -0
  132. package/dist/browser/presets/supabase.d.ts +6 -0
  133. package/dist/browser/presets/supabase.js +18 -0
  134. package/dist/browser/presets/tiktok.d.ts +10 -0
  135. package/dist/browser/presets/tiktok.js +38 -0
  136. package/dist/browser/presets/together.d.ts +7 -0
  137. package/dist/browser/presets/together.js +22 -0
  138. package/dist/browser/presets/twilio.d.ts +6 -0
  139. package/dist/browser/presets/twilio.js +17 -0
  140. package/dist/browser/presets/vercel.d.ts +6 -0
  141. package/dist/browser/presets/vercel.js +23 -0
  142. package/dist/browser/presets/vultr.d.ts +5 -0
  143. package/dist/browser/presets/vultr.js +16 -0
  144. package/dist/browser/presets/xai.d.ts +8 -0
  145. package/dist/browser/presets/xai.js +23 -0
  146. package/dist/browser/presets/youtube.d.ts +5 -0
  147. package/dist/browser/presets/youtube.js +20 -0
  148. package/dist/browser/recker.d.ts +8 -1
  149. package/dist/browser/recker.js +8 -2
  150. package/dist/browser/scrape/document.d.ts +5 -4
  151. package/dist/browser/scrape/document.js +89 -76
  152. package/dist/browser/scrape/element.d.ts +10 -8
  153. package/dist/browser/scrape/element.js +295 -81
  154. package/dist/browser/scrape/extractors.d.ts +11 -11
  155. package/dist/browser/scrape/extractors.js +145 -113
  156. package/dist/browser/scrape/parser/back.d.ts +1 -0
  157. package/dist/browser/scrape/parser/back.js +3 -0
  158. package/dist/browser/scrape/parser/index.d.ts +20 -0
  159. package/dist/browser/scrape/parser/index.js +19 -0
  160. package/dist/browser/scrape/parser/matcher.d.ts +30 -0
  161. package/dist/browser/scrape/parser/matcher.js +99 -0
  162. package/dist/browser/scrape/parser/nodes/comment.d.ts +12 -0
  163. package/dist/browser/scrape/parser/nodes/comment.js +21 -0
  164. package/dist/browser/scrape/parser/nodes/html.d.ts +110 -0
  165. package/dist/browser/scrape/parser/nodes/html.js +978 -0
  166. package/dist/browser/scrape/parser/nodes/node.d.ts +18 -0
  167. package/dist/browser/scrape/parser/nodes/node.js +31 -0
  168. package/dist/browser/scrape/parser/nodes/text.d.ts +14 -0
  169. package/dist/browser/scrape/parser/nodes/text.js +30 -0
  170. package/dist/browser/scrape/parser/nodes/type.d.ts +6 -0
  171. package/dist/browser/scrape/parser/nodes/type.js +7 -0
  172. package/dist/browser/scrape/parser/parse.d.ts +1 -0
  173. package/dist/browser/scrape/parser/parse.js +1 -0
  174. package/dist/browser/scrape/parser/valid.d.ts +2 -0
  175. package/dist/browser/scrape/parser/valid.js +5 -0
  176. package/dist/browser/scrape/parser/void-tag.d.ts +7 -0
  177. package/dist/browser/scrape/parser/void-tag.js +43 -0
  178. package/dist/browser/scrape/types.d.ts +7 -0
  179. package/dist/browser/seo/analyzer.d.ts +59 -0
  180. package/dist/browser/seo/analyzer.js +1399 -0
  181. package/dist/browser/seo/keywords.d.ts +16 -0
  182. package/dist/browser/seo/keywords.js +55 -0
  183. package/dist/browser/seo/rules/accessibility.d.ts +2 -0
  184. package/dist/browser/seo/rules/accessibility.js +733 -0
  185. package/dist/browser/seo/rules/ai-search.d.ts +2 -0
  186. package/dist/browser/seo/rules/ai-search.js +436 -0
  187. package/dist/browser/seo/rules/analytics.d.ts +2 -0
  188. package/dist/browser/seo/rules/analytics.js +306 -0
  189. package/dist/browser/seo/rules/best-practices.d.ts +2 -0
  190. package/dist/browser/seo/rules/best-practices.js +195 -0
  191. package/dist/browser/seo/rules/canonical.d.ts +12 -0
  192. package/dist/browser/seo/rules/canonical.js +270 -0
  193. package/dist/browser/seo/rules/content.d.ts +2 -0
  194. package/dist/browser/seo/rules/content.js +522 -0
  195. package/dist/browser/seo/rules/crawl.d.ts +2 -0
  196. package/dist/browser/seo/rules/crawl.js +435 -0
  197. package/dist/browser/seo/rules/cwv.d.ts +2 -0
  198. package/dist/browser/seo/rules/cwv.js +248 -0
  199. package/dist/browser/seo/rules/ecommerce.d.ts +2 -0
  200. package/dist/browser/seo/rules/ecommerce.js +312 -0
  201. package/dist/browser/seo/rules/i18n.d.ts +2 -0
  202. package/dist/browser/seo/rules/i18n.js +288 -0
  203. package/dist/browser/seo/rules/images.d.ts +2 -0
  204. package/dist/browser/seo/rules/images.js +255 -0
  205. package/dist/browser/seo/rules/index.d.ts +52 -0
  206. package/dist/browser/seo/rules/index.js +159 -0
  207. package/dist/browser/seo/rules/internal-linking.d.ts +2 -0
  208. package/dist/browser/seo/rules/internal-linking.js +394 -0
  209. package/dist/browser/seo/rules/links.d.ts +2 -0
  210. package/dist/browser/seo/rules/links.js +498 -0
  211. package/dist/browser/seo/rules/local.d.ts +2 -0
  212. package/dist/browser/seo/rules/local.js +289 -0
  213. package/dist/browser/seo/rules/meta.d.ts +2 -0
  214. package/dist/browser/seo/rules/meta.js +805 -0
  215. package/dist/browser/seo/rules/mobile.d.ts +2 -0
  216. package/dist/browser/seo/rules/mobile.js +161 -0
  217. package/dist/browser/seo/rules/performance.d.ts +2 -0
  218. package/dist/browser/seo/rules/performance.js +738 -0
  219. package/dist/browser/seo/rules/pwa.d.ts +2 -0
  220. package/dist/browser/seo/rules/pwa.js +299 -0
  221. package/dist/browser/seo/rules/readability.d.ts +2 -0
  222. package/dist/browser/seo/rules/readability.js +264 -0
  223. package/dist/browser/seo/rules/redirects.d.ts +16 -0
  224. package/dist/browser/seo/rules/redirects.js +199 -0
  225. package/dist/browser/seo/rules/resources.d.ts +2 -0
  226. package/dist/browser/seo/rules/resources.js +390 -0
  227. package/dist/browser/seo/rules/schema.d.ts +2 -0
  228. package/dist/browser/seo/rules/schema.js +379 -0
  229. package/dist/browser/seo/rules/security.d.ts +2 -0
  230. package/dist/browser/seo/rules/security.js +877 -0
  231. package/dist/browser/seo/rules/social.d.ts +2 -0
  232. package/dist/browser/seo/rules/social.js +603 -0
  233. package/dist/browser/seo/rules/structural.d.ts +2 -0
  234. package/dist/browser/seo/rules/structural.js +223 -0
  235. package/dist/browser/seo/rules/technical-advanced.d.ts +10 -0
  236. package/dist/browser/seo/rules/technical-advanced.js +289 -0
  237. package/dist/browser/seo/rules/technical.d.ts +2 -0
  238. package/dist/browser/seo/rules/technical.js +480 -0
  239. package/dist/browser/seo/rules/thresholds.d.ts +196 -0
  240. package/dist/browser/seo/rules/thresholds.js +118 -0
  241. package/dist/browser/seo/rules/types.d.ts +498 -0
  242. package/dist/browser/seo/rules/types.js +11 -0
  243. package/dist/browser/seo/types.d.ts +211 -0
  244. package/dist/browser/seo/types.js +1 -0
  245. package/dist/browser/transport/curl.d.ts +4 -0
  246. package/dist/browser/transport/curl.js +101 -0
  247. package/dist/browser/transport/undici.js +1 -2
  248. package/dist/browser/transport/worker.d.ts +18 -0
  249. package/dist/browser/transport/worker.js +278 -0
  250. package/dist/browser/types/index.d.ts +4 -1
  251. package/dist/browser/utils/binary-manager.d.ts +4 -0
  252. package/dist/browser/utils/binary-manager.js +72 -0
  253. package/dist/browser/utils/user-agent.js +2 -13
  254. package/dist/cache/indexed-db.d.ts +10 -0
  255. package/dist/cache/indexed-db.js +88 -0
  256. package/dist/cache/service-worker-cache.d.ts +18 -0
  257. package/dist/cache/service-worker-cache.js +103 -0
  258. package/dist/cli/commands/ai.d.ts +2 -0
  259. package/dist/cli/commands/ai.js +162 -0
  260. package/dist/cli/commands/bench.d.ts +2 -0
  261. package/dist/cli/commands/bench.js +51 -0
  262. package/dist/cli/commands/dns.d.ts +2 -0
  263. package/dist/cli/commands/dns.js +295 -0
  264. package/dist/cli/commands/har.d.ts +2 -0
  265. package/dist/cli/commands/har.js +171 -0
  266. package/dist/cli/commands/hls.d.ts +2 -0
  267. package/dist/cli/commands/hls.js +192 -0
  268. package/dist/cli/commands/network.d.ts +2 -0
  269. package/dist/cli/commands/network.js +288 -0
  270. package/dist/cli/commands/protocols.d.ts +2 -0
  271. package/dist/cli/commands/protocols.js +344 -0
  272. package/dist/cli/commands/scrape.d.ts +2 -0
  273. package/dist/cli/commands/scrape.js +176 -0
  274. package/dist/cli/commands/security.d.ts +2 -0
  275. package/dist/cli/commands/security.js +57 -0
  276. package/dist/cli/commands/seo.d.ts +2 -0
  277. package/dist/cli/commands/seo.js +125 -0
  278. package/dist/cli/commands/serve.d.ts +2 -0
  279. package/dist/cli/commands/serve.js +531 -0
  280. package/dist/cli/commands/spider.d.ts +3 -0
  281. package/dist/cli/commands/spider.js +456 -0
  282. package/dist/cli/commands/utils.d.ts +2 -0
  283. package/dist/cli/commands/utils.js +176 -0
  284. package/dist/cli/commands/vector.d.ts +2 -0
  285. package/dist/cli/commands/vector.js +158 -0
  286. package/dist/cli/handler.d.ts +2 -2
  287. package/dist/cli/handler.js +6 -6
  288. package/dist/cli/helpers.d.ts +7 -0
  289. package/dist/cli/helpers.js +128 -0
  290. package/dist/cli/index.js +96 -5228
  291. package/dist/cli/parser/help.d.ts +2 -0
  292. package/dist/cli/parser/help.js +52 -0
  293. package/dist/cli/parser/index.d.ts +3 -0
  294. package/dist/cli/parser/index.js +3 -0
  295. package/dist/cli/parser/parser.d.ts +4 -0
  296. package/dist/cli/parser/parser.js +146 -0
  297. package/dist/cli/parser/types.d.ts +41 -0
  298. package/dist/cli/parser/types.js +1 -0
  299. package/dist/cli/presets.d.ts +1 -1
  300. package/dist/cli/presets.js +1 -1
  301. package/dist/cli/router.d.ts +36 -0
  302. package/dist/cli/router.js +195 -0
  303. package/dist/cli/tui/ai-chat.js +1 -1
  304. package/dist/cli/tui/commands/context.d.ts +9 -0
  305. package/dist/cli/tui/commands/context.js +1 -0
  306. package/dist/cli/tui/commands/dns.d.ts +10 -0
  307. package/dist/cli/tui/commands/dns.js +461 -0
  308. package/dist/cli/tui/commands/hls.d.ts +2 -0
  309. package/dist/cli/tui/commands/hls.js +162 -0
  310. package/dist/cli/tui/commands/ip.d.ts +2 -0
  311. package/dist/cli/tui/commands/ip.js +45 -0
  312. package/dist/cli/tui/commands/network.d.ts +3 -0
  313. package/dist/cli/tui/commands/network.js +81 -0
  314. package/dist/cli/tui/commands/protocols.d.ts +6 -0
  315. package/dist/cli/tui/commands/protocols.js +531 -0
  316. package/dist/cli/tui/commands/security.d.ts +2 -0
  317. package/dist/cli/tui/commands/security.js +48 -0
  318. package/dist/cli/tui/commands/seo.d.ts +2 -0
  319. package/dist/cli/tui/commands/seo.js +74 -0
  320. package/dist/cli/tui/context.d.ts +12 -0
  321. package/dist/cli/tui/context.js +1 -0
  322. package/dist/cli/tui/shell.d.ts +11 -20
  323. package/dist/cli/tui/shell.js +216 -1873
  324. package/dist/constants/user-agents.d.ts +7 -0
  325. package/dist/constants/user-agents.js +7 -0
  326. package/dist/core/client.d.ts +2 -0
  327. package/dist/core/client.js +19 -1
  328. package/dist/index.d.ts +1 -0
  329. package/dist/index.js +1 -0
  330. package/dist/mcp/cli.js +2 -3
  331. package/dist/mcp/data/embeddings.json +1 -1
  332. package/dist/mcp/tools/network.js +298 -158
  333. package/dist/plugins/har-player.d.ts +23 -0
  334. package/dist/plugins/har-player.js +49 -0
  335. package/dist/plugins/har-recorder.d.ts +37 -3
  336. package/dist/plugins/har-recorder.js +116 -63
  337. package/dist/plugins/network-simulation.d.ts +7 -0
  338. package/dist/plugins/network-simulation.js +13 -0
  339. package/dist/presets/android.d.ts +2 -0
  340. package/dist/presets/android.js +16 -0
  341. package/dist/presets/chaturbate.d.ts +2 -0
  342. package/dist/presets/chaturbate.js +17 -0
  343. package/dist/presets/elevenlabs.d.ts +6 -0
  344. package/dist/presets/elevenlabs.js +20 -0
  345. package/dist/presets/enhancers.d.ts +20 -0
  346. package/dist/presets/enhancers.js +85 -0
  347. package/dist/presets/hubspot.d.ts +9 -0
  348. package/dist/presets/hubspot.js +28 -0
  349. package/dist/presets/index.d.ts +10 -0
  350. package/dist/presets/index.js +10 -0
  351. package/dist/presets/ios.d.ts +2 -0
  352. package/dist/presets/ios.js +13 -0
  353. package/dist/presets/pinecone.d.ts +8 -0
  354. package/dist/presets/pinecone.js +42 -0
  355. package/dist/presets/registry.js +60 -0
  356. package/dist/presets/sendgrid.d.ts +6 -0
  357. package/dist/presets/sendgrid.js +20 -0
  358. package/dist/presets/sentry.d.ts +11 -0
  359. package/dist/presets/sentry.js +48 -0
  360. package/dist/presets/square.d.ts +10 -0
  361. package/dist/presets/square.js +33 -0
  362. package/dist/recker.d.ts +3 -0
  363. package/dist/recker.js +4 -0
  364. package/dist/scrape/document.d.ts +5 -4
  365. package/dist/scrape/document.js +89 -76
  366. package/dist/scrape/element.d.ts +10 -8
  367. package/dist/scrape/element.js +295 -81
  368. package/dist/scrape/extractors.d.ts +11 -11
  369. package/dist/scrape/extractors.js +145 -113
  370. package/dist/scrape/index.d.ts +2 -0
  371. package/dist/scrape/index.js +1 -0
  372. package/dist/scrape/parser/back.d.ts +1 -0
  373. package/dist/scrape/parser/back.js +3 -0
  374. package/dist/scrape/parser/index.d.ts +20 -0
  375. package/dist/scrape/parser/index.js +19 -0
  376. package/dist/scrape/parser/matcher.d.ts +30 -0
  377. package/dist/scrape/parser/matcher.js +99 -0
  378. package/dist/scrape/parser/nodes/comment.d.ts +12 -0
  379. package/dist/scrape/parser/nodes/comment.js +21 -0
  380. package/dist/scrape/parser/nodes/html.d.ts +110 -0
  381. package/dist/scrape/parser/nodes/html.js +978 -0
  382. package/dist/scrape/parser/nodes/node.d.ts +18 -0
  383. package/dist/scrape/parser/nodes/node.js +31 -0
  384. package/dist/scrape/parser/nodes/text.d.ts +14 -0
  385. package/dist/scrape/parser/nodes/text.js +30 -0
  386. package/dist/scrape/parser/nodes/type.d.ts +6 -0
  387. package/dist/scrape/parser/nodes/type.js +7 -0
  388. package/dist/scrape/parser/parse.d.ts +1 -0
  389. package/dist/scrape/parser/parse.js +1 -0
  390. package/dist/scrape/parser/valid.d.ts +2 -0
  391. package/dist/scrape/parser/valid.js +5 -0
  392. package/dist/scrape/parser/void-tag.d.ts +7 -0
  393. package/dist/scrape/parser/void-tag.js +43 -0
  394. package/dist/scrape/spider.d.ts +19 -0
  395. package/dist/scrape/spider.js +28 -3
  396. package/dist/scrape/types.d.ts +7 -0
  397. package/dist/seo/analyzer.d.ts +15 -5
  398. package/dist/seo/analyzer.js +636 -175
  399. package/dist/seo/formatter.d.ts +16 -0
  400. package/dist/seo/formatter.js +228 -0
  401. package/dist/seo/index.d.ts +2 -0
  402. package/dist/seo/index.js +1 -0
  403. package/dist/seo/keywords.d.ts +16 -0
  404. package/dist/seo/keywords.js +55 -0
  405. package/dist/seo/rules/accessibility.js +96 -57
  406. package/dist/seo/rules/ai-search.js +44 -31
  407. package/dist/seo/rules/analytics.d.ts +2 -0
  408. package/dist/seo/rules/analytics.js +306 -0
  409. package/dist/seo/rules/best-practices.js +21 -14
  410. package/dist/seo/rules/canonical.js +53 -32
  411. package/dist/seo/rules/content.js +317 -31
  412. package/dist/seo/rules/crawl.js +55 -40
  413. package/dist/seo/rules/cwv.js +21 -15
  414. package/dist/seo/rules/ecommerce.js +82 -22
  415. package/dist/seo/rules/i18n.js +75 -36
  416. package/dist/seo/rules/images.js +109 -30
  417. package/dist/seo/rules/index.js +2 -0
  418. package/dist/seo/rules/internal-linking.js +58 -39
  419. package/dist/seo/rules/links.js +79 -52
  420. package/dist/seo/rules/local.js +49 -25
  421. package/dist/seo/rules/meta.js +339 -81
  422. package/dist/seo/rules/mobile.js +112 -2
  423. package/dist/seo/rules/performance.js +434 -66
  424. package/dist/seo/rules/pwa.js +36 -39
  425. package/dist/seo/rules/readability.js +31 -22
  426. package/dist/seo/rules/redirects.js +21 -15
  427. package/dist/seo/rules/resources.js +59 -42
  428. package/dist/seo/rules/schema.js +333 -8
  429. package/dist/seo/rules/security.js +142 -80
  430. package/dist/seo/rules/social.js +277 -47
  431. package/dist/seo/rules/structural.js +87 -19
  432. package/dist/seo/rules/technical-advanced.js +30 -24
  433. package/dist/seo/rules/technical.js +243 -42
  434. package/dist/seo/rules/types.d.ts +53 -1
  435. package/dist/seo/seo-spider.d.ts +22 -0
  436. package/dist/seo/seo-spider.js +77 -13
  437. package/dist/seo/types.d.ts +8 -1
  438. package/dist/seo/validators/llms-txt.js +19 -0
  439. package/dist/seo/validators/rss.d.ts +11 -0
  440. package/dist/seo/validators/rss.js +93 -0
  441. package/dist/seo/validators/sitemap.js +36 -26
  442. package/dist/transport/curl.d.ts +4 -0
  443. package/dist/transport/curl.js +101 -0
  444. package/dist/transport/udp.js +0 -1
  445. package/dist/transport/undici.js +1 -2
  446. package/dist/transport/worker.d.ts +18 -0
  447. package/dist/transport/worker.js +278 -0
  448. package/dist/types/index.d.ts +4 -1
  449. package/dist/utils/binary-manager.d.ts +4 -0
  450. package/dist/utils/binary-manager.js +72 -0
  451. package/dist/utils/optional-require.d.ts +7 -8
  452. package/dist/utils/optional-require.js +2 -21
  453. package/dist/utils/upload.d.ts +6 -0
  454. package/dist/utils/upload.js +11 -0
  455. package/dist/utils/user-agent.js +2 -13
  456. package/dist/version.js +1 -1
  457. package/package.json +12 -6
  458. package/dist/browser/utils/optional-require.d.ts +0 -19
  459. package/dist/browser/utils/optional-require.js +0 -105
@@ -0,0 +1,288 @@
1
+ import { createResult } from './types.js';
2
+ export const i18nRules = [
3
+ {
4
+ id: 'i18n-hreflang-exists',
5
+ name: 'Hreflang Tags',
6
+ category: 'technical',
7
+ severity: 'warning',
8
+ description: 'Multi-language sites should have hreflang tags for proper language targeting',
9
+ check: (ctx) => {
10
+ if (!ctx.hreflangTags || ctx.hreflangTags.length === 0) {
11
+ if (ctx.hasLang && ctx.langValue && ctx.langValue !== 'en') {
12
+ return createResult({ id: 'i18n-hreflang-exists', name: 'Hreflang Tags', category: 'technical', severity: 'warning' }, 'info', 'No hreflang tags found (recommended for multi-language sites)', {
13
+ recommendation: 'Add hreflang tags to indicate language/region alternatives',
14
+ evidence: {
15
+ expected: '<link rel="alternate" hreflang="en" href="https://example.com/en/">',
16
+ example: '<link rel="alternate" hreflang="en" href="https://example.com/en/">\n<link rel="alternate" hreflang="es" href="https://example.com/es/">\n<link rel="alternate" hreflang="x-default" href="https://example.com/">',
17
+ impact: 'Without hreflang, search engines may show wrong language version to users in different countries',
18
+ learnMore: 'https://developers.google.com/search/docs/specialty/international/localized-versions',
19
+ },
20
+ });
21
+ }
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
+ });
25
+ }
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 });
27
+ },
28
+ },
29
+ {
30
+ id: 'i18n-hreflang-self',
31
+ name: 'Hreflang Self-Reference',
32
+ category: 'technical',
33
+ severity: 'warning',
34
+ description: 'Hreflang tags should include a self-referencing tag for the current page',
35
+ check: (ctx) => {
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
+ }
44
+ const currentUrl = ctx.url.toLowerCase().replace(/\/$/, '');
45
+ const hasSelfRef = ctx.hreflangTags.some((tag) => {
46
+ const href = tag.href?.toLowerCase().replace(/\/$/, '');
47
+ return href === currentUrl;
48
+ });
49
+ if (!hasSelfRef) {
50
+ return createResult({ id: 'i18n-hreflang-self', name: 'Hreflang Self-Reference', category: 'technical', severity: 'warning' }, 'warn', 'Missing self-referencing hreflang tag', {
51
+ recommendation: 'Add a hreflang tag that points to the current page',
52
+ evidence: {
53
+ found: `Current URL: ${ctx.url}`,
54
+ expected: `<link rel="alternate" hreflang="${ctx.langValue || 'en'}" href="${ctx.url}">`,
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/')}">`,
57
+ },
58
+ });
59
+ }
60
+ return createResult({ id: 'i18n-hreflang-self', name: 'Hreflang Self-Reference', category: 'technical', severity: 'warning' }, 'pass', 'Self-referencing hreflang tag present');
61
+ },
62
+ },
63
+ {
64
+ id: 'i18n-hreflang-x-default',
65
+ name: 'Hreflang X-Default',
66
+ category: 'technical',
67
+ severity: 'info',
68
+ description: 'Include x-default hreflang for users outside defined regions',
69
+ check: (ctx) => {
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
+ }
75
+ const hasXDefault = ctx.hreflangTags.some((tag) => tag.lang === 'x-default');
76
+ if (!hasXDefault) {
77
+ return createResult({ id: 'i18n-hreflang-x-default', name: 'Hreflang X-Default', category: 'technical', severity: 'info' }, 'info', 'No x-default hreflang tag found', {
78
+ recommendation: 'Add hreflang="x-default" to specify the fallback page for unmatched languages',
79
+ evidence: {
80
+ expected: '<link rel="alternate" hreflang="x-default" href="https://example.com/">',
81
+ impact: 'Without x-default, users in unsupported regions may not see the best version',
82
+ },
83
+ });
84
+ }
85
+ return createResult({ id: 'i18n-hreflang-x-default', name: 'Hreflang X-Default', category: 'technical', severity: 'info' }, 'pass', 'x-default hreflang tag present');
86
+ },
87
+ },
88
+ {
89
+ id: 'i18n-hreflang-valid-codes',
90
+ name: 'Hreflang Valid Codes',
91
+ category: 'technical',
92
+ severity: 'warning',
93
+ description: 'Hreflang language codes must be valid ISO 639-1 codes',
94
+ check: (ctx) => {
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
+ }
98
+ const validLanguageCodes = new Set([
99
+ 'aa', 'ab', 'af', 'ak', 'sq', 'am', 'ar', 'an', 'hy', 'as', 'av', 'ae', 'ay', 'az',
100
+ 'ba', 'bm', 'eu', 'be', 'bn', 'bh', 'bi', 'bo', 'bs', 'br', 'bg', 'my', 'ca', 'cs',
101
+ 'ch', 'ce', 'zh', 'cu', 'cv', 'kw', 'co', 'cr', 'cy', 'da', 'de', 'dv', 'nl', 'dz',
102
+ 'en', 'eo', 'et', 'ee', 'fo', 'fa', 'fj', 'fi', 'fr', 'fy', 'ff', 'ka', 'el', 'gn',
103
+ 'gu', 'ht', 'ha', 'he', 'hz', 'hi', 'ho', 'hr', 'hu', 'ig', 'is', 'io', 'ii', 'iu',
104
+ 'ie', 'ia', 'id', 'ik', 'it', 'jv', 'ja', 'kl', 'kn', 'ks', 'kr', 'kk', 'km', 'ki',
105
+ 'rw', 'ky', 'kv', 'kg', 'ko', 'kj', 'ku', 'lo', 'la', 'lv', 'li', 'ln', 'lt', 'lb',
106
+ 'lu', 'lg', 'mk', 'mh', 'ml', 'mi', 'mr', 'ms', 'mg', 'mt', 'mn', 'na', 'nv', 'nr',
107
+ 'nd', 'ng', 'ne', 'nn', 'nb', 'no', 'ny', 'oc', 'oj', 'or', 'om', 'os', 'pa', 'pi',
108
+ 'pl', 'pt', 'ps', 'qu', 'rm', 'ro', 'rn', 'ru', 'sg', 'sa', 'si', 'sk', 'sl', 'se',
109
+ 'sm', 'sn', 'sd', 'so', 'st', 'es', 'sc', 'sr', 'ss', 'su', 'sw', 'sv', 'ty', 'ta',
110
+ 'tt', 'te', 'tg', 'tl', 'th', 'ti', 'to', 'tn', 'ts', 'tk', 'tr', 'tw', 'ug', 'uk',
111
+ 'ur', 'uz', 've', 'vi', 'vo', 'wa', 'wo', 'xh', 'yi', 'yo', 'za', 'zu',
112
+ 'x-default',
113
+ ]);
114
+ const invalidTags = [];
115
+ for (const tag of ctx.hreflangTags) {
116
+ const lang = tag.lang?.toLowerCase().split('-')[0];
117
+ if (lang && !validLanguageCodes.has(lang)) {
118
+ invalidTags.push(tag.lang);
119
+ }
120
+ }
121
+ if (invalidTags.length > 0) {
122
+ return createResult({ id: 'i18n-hreflang-valid-codes', name: 'Hreflang Valid Codes', category: 'technical', severity: 'warning' }, 'warn', `Invalid hreflang codes: ${invalidTags.join(', ')}`, {
123
+ recommendation: 'Use valid ISO 639-1 language codes',
124
+ evidence: {
125
+ found: invalidTags,
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/">',
129
+ learnMore: 'https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes',
130
+ },
131
+ });
132
+ }
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');
134
+ },
135
+ },
136
+ {
137
+ id: 'i18n-hreflang-return-links',
138
+ name: 'Hreflang Return Links',
139
+ category: 'technical',
140
+ severity: 'warning',
141
+ description: 'All hreflang URLs should return links back to this page (bidirectional)',
142
+ check: (ctx) => {
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
+ }
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', {
149
+ recommendation: 'Ensure all alternate pages link back to this page with matching hreflang tags',
150
+ evidence: {
151
+ impact: 'Missing return links can cause Google to ignore hreflang annotations',
152
+ learnMore: 'https://developers.google.com/search/docs/specialty/international/localized-versions#bidirectional',
153
+ },
154
+ });
155
+ },
156
+ },
157
+ {
158
+ id: 'i18n-content-language',
159
+ name: 'Content-Language Header',
160
+ category: 'technical',
161
+ severity: 'info',
162
+ description: 'Content-Language header can indicate the language of the document',
163
+ check: (ctx) => {
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
+ }
167
+ const contentLang = ctx.responseHeaders['content-language'] || ctx.responseHeaders['Content-Language'];
168
+ if (!contentLang) {
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
+ });
175
+ }
176
+ if (ctx.langValue) {
177
+ const headerLang = Array.isArray(contentLang) ? contentLang[0] : contentLang;
178
+ const headerLangPrimary = headerLang.toLowerCase().split('-')[0].split(',')[0].trim();
179
+ const htmlLangPrimary = ctx.langValue.toLowerCase().split('-')[0];
180
+ if (headerLangPrimary !== htmlLangPrimary) {
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})`, {
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
+ },
188
+ });
189
+ }
190
+ }
191
+ return createResult({ id: 'i18n-content-language', name: 'Content-Language Header', category: 'technical', severity: 'info' }, 'pass', `Content-Language: ${contentLang}`);
192
+ },
193
+ },
194
+ {
195
+ id: 'i18n-lang-consistency',
196
+ name: 'Language Consistency',
197
+ category: 'technical',
198
+ severity: 'warning',
199
+ description: 'HTML lang attribute should match the og:locale if present',
200
+ check: (ctx) => {
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
+ }
213
+ const htmlLang = ctx.langValue?.toLowerCase().split('-')[0];
214
+ const ogLocaleLang = ctx.ogLocale.toLowerCase().split('_')[0];
215
+ if (htmlLang !== ogLocaleLang) {
216
+ return createResult({ id: 'i18n-lang-consistency', name: 'Language Consistency', category: 'technical', severity: 'warning' }, 'warn', `Language mismatch: html lang="${ctx.langValue}" vs og:locale="${ctx.ogLocale}"`, {
217
+ recommendation: 'Ensure html lang and og:locale represent the same language',
218
+ evidence: {
219
+ found: [`html lang="${ctx.langValue}"`, `og:locale="${ctx.ogLocale}"`],
220
+ expected: 'Both attributes should use the same language code',
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">',
223
+ },
224
+ });
225
+ }
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}"`);
227
+ },
228
+ },
229
+ {
230
+ id: 'i18n-lang-region',
231
+ name: 'Language Region Specificity',
232
+ category: 'technical',
233
+ severity: 'info',
234
+ description: 'Consider using region-specific language codes for better targeting',
235
+ check: (ctx) => {
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
+ }
241
+ const multiRegionalLangs = ['en', 'es', 'pt', 'zh', 'fr', 'de', 'ar'];
242
+ const langPrimary = ctx.langValue.toLowerCase().split('-')[0];
243
+ if (multiRegionalLangs.includes(langPrimary) && !ctx.langValue.includes('-')) {
244
+ return createResult({ id: 'i18n-lang-region', name: 'Language Region Specificity', category: 'technical', severity: 'info' }, 'info', `Consider using region-specific lang code (e.g., ${langPrimary}-US, ${langPrimary}-GB)`, {
245
+ recommendation: 'For multi-regional languages, specify the region for better targeting',
246
+ evidence: {
247
+ found: ctx.langValue,
248
+ expected: `${langPrimary}-XX (e.g., en-US, es-ES, pt-BR, zh-CN)`,
249
+ impact: 'Helps search engines serve the right regional variant',
250
+ },
251
+ });
252
+ }
253
+ return createResult({ id: 'i18n-lang-region', name: 'Language Region Specificity', category: 'technical', severity: 'info' }, 'pass', `Lang attribute: ${ctx.langValue}`);
254
+ },
255
+ },
256
+ {
257
+ id: 'hreflang-language-mismatch',
258
+ name: 'Hreflang Language Mismatch',
259
+ category: 'technical',
260
+ severity: 'warning',
261
+ description: 'Hreflang language should match page content language',
262
+ check: (ctx) => {
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
+ }
269
+ const selfHreflang = ctx.hreflangTags.find(tag => tag.href === ctx.url || tag.href === ctx.canonicalUrl);
270
+ if (selfHreflang && ctx.detectedLanguage) {
271
+ const hreflangLang = selfHreflang.lang.split('-')[0].toLowerCase();
272
+ const detectedLang = ctx.detectedLanguage.toLowerCase();
273
+ if (hreflangLang !== detectedLang && hreflangLang !== 'x-default') {
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}"`, {
275
+ recommendation: 'Verify the hreflang attribute matches the actual page language',
276
+ evidence: {
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}">`,
281
+ }
282
+ });
283
+ }
284
+ }
285
+ return createResult({ id: 'hreflang-language-mismatch', name: 'Hreflang Language Mismatch', category: 'technical', severity: 'warning' }, 'pass', 'Hreflang language matches detected content language');
286
+ },
287
+ },
288
+ ];
@@ -0,0 +1,2 @@
1
+ import { SeoRule } from './types.js';
2
+ export declare const imageRules: SeoRule[];
@@ -0,0 +1,255 @@
1
+ import { createResult } from './types.js';
2
+ import { SEO_THRESHOLDS } from './thresholds.js';
3
+ export const imageRules = [
4
+ {
5
+ id: 'images-alt-text',
6
+ name: 'Image Alt Text',
7
+ category: 'images',
8
+ severity: 'error',
9
+ description: 'All images must have alt text',
10
+ check: (ctx) => {
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
+ }
14
+ const withoutAlt = ctx.imagesWithoutAlt ?? 0;
15
+ if (withoutAlt > 0) {
16
+ const percentage = Math.round((withoutAlt / ctx.totalImages) * 100);
17
+ const severity = withoutAlt > ctx.totalImages / 2 ? 'fail' : 'warn';
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
+ });
29
+ }
30
+ return createResult({ id: 'images-alt-text', name: 'Image Alt Text', category: 'images', severity: 'error' }, 'pass', 'All images have alt text');
31
+ },
32
+ },
33
+ {
34
+ id: 'images-dimensions',
35
+ name: 'Image Dimensions',
36
+ category: 'images',
37
+ severity: 'warning',
38
+ description: 'Images should have width and height attributes to prevent CLS',
39
+ check: (ctx) => {
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
+ }
43
+ const missing = ctx.imagesMissingDimensions ?? 0;
44
+ if (missing > 0) {
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
+ });
57
+ }
58
+ return createResult({ id: 'images-dimensions', name: 'Image Dimensions', category: 'images', severity: 'warning' }, 'pass', 'All images have dimensions defined');
59
+ },
60
+ },
61
+ {
62
+ id: 'images-lazy-loading',
63
+ name: 'Lazy Loading',
64
+ category: 'images',
65
+ severity: 'info',
66
+ description: 'Below-the-fold images should use lazy loading',
67
+ check: (ctx) => {
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
+ }
71
+ const lazy = ctx.imagesWithLazyLoad ?? 0;
72
+ if (lazy === 0) {
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
+ });
83
+ }
84
+ return createResult({ id: 'images-lazy-loading', name: 'Lazy Loading', category: 'images', severity: 'info' }, 'pass', `${lazy} images use lazy loading`);
85
+ },
86
+ },
87
+ {
88
+ id: 'images-format-modern',
89
+ name: 'Modern Image Formats',
90
+ category: 'images',
91
+ severity: 'info',
92
+ description: 'Images should use modern formats like WebP or AVIF',
93
+ check: (ctx) => {
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
+ }
97
+ const modern = ctx.imagesUsingModernFormats ?? 0;
98
+ if (ctx.totalImages > 0 && modern === 0) {
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
+ });
110
+ }
111
+ return createResult({ id: 'images-format-modern', name: 'Modern Image Formats', category: 'images', severity: 'info' }, 'pass', `${modern} images using modern formats`, { value: modern });
112
+ },
113
+ },
114
+ {
115
+ id: 'images-empty-alt',
116
+ name: 'Empty Alt Text',
117
+ category: 'images',
118
+ severity: 'info',
119
+ description: 'Images with empty alt="" are treated as decorative',
120
+ check: (ctx) => {
121
+ const emptyAlt = ctx.imagesWithEmptyAlt ?? 0;
122
+ if (emptyAlt > 0) {
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' });
124
+ }
125
+ return createResult({ id: 'images-empty-alt', name: 'Empty Alt Text', category: 'images', severity: 'info' }, 'pass', 'No images with empty alt attributes');
126
+ },
127
+ },
128
+ {
129
+ id: 'images-alt-length',
130
+ name: 'Alt Text Length',
131
+ category: 'images',
132
+ severity: 'warning',
133
+ description: 'Alt text should be descriptive (ideal 80-120, max 150 chars)',
134
+ check: (ctx) => {
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
+ }
138
+ const { minLength, idealLength, maxLength } = SEO_THRESHOLDS.images.alt;
139
+ let shortAlts = 0;
140
+ let longAlts = 0;
141
+ let nonIdealAlts = 0;
142
+ ctx.altTextLengths.forEach(len => {
143
+ if (len < minLength)
144
+ shortAlts++;
145
+ else if (len > maxLength)
146
+ longAlts++;
147
+ else if (len < idealLength.min || len > idealLength.max)
148
+ nonIdealAlts++;
149
+ });
150
+ if (shortAlts > 0) {
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
+ });
162
+ }
163
+ if (longAlts > 0) {
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
+ });
175
+ }
176
+ if (nonIdealAlts > 0) {
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.` });
178
+ }
179
+ return createResult({ id: 'images-alt-length', name: 'Alt Text Length', category: 'images', severity: 'warning' }, 'pass', 'All alt text lengths are within ideal range');
180
+ },
181
+ },
182
+ {
183
+ id: 'images-clean-filenames',
184
+ name: 'Image Filenames',
185
+ category: 'images',
186
+ severity: 'info',
187
+ description: 'Image filenames should be descriptive and use keywords, not generic names.',
188
+ check: (ctx) => {
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
+ }
192
+ const genericFilenames = ctx.imageFilenames.filter(name => /^(img|image|photo|pic)\d*\.(jpg|jpeg|png|webp|avif|gif)$/i.test(name) ||
193
+ /^screenshot_\d*\.(jpg|jpeg|png)$/i.test(name) ||
194
+ /^untitled-\d*\.(jpg|jpeg|png)$/i.test(name));
195
+ if (genericFilenames.length > 0) {
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
+ });
207
+ }
208
+ return createResult({ id: 'images-clean-filenames', name: 'Image Filenames', category: 'images', severity: 'info' }, 'pass', 'All image filenames are descriptive');
209
+ },
210
+ },
211
+ {
212
+ id: 'images-decoding-async',
213
+ name: 'Image Decoding Async',
214
+ category: 'images',
215
+ severity: 'info',
216
+ description: 'Use decoding="async" for non-critical images to improve rendering performance.',
217
+ check: (ctx) => {
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
+ }
224
+ const nonAsync = ctx.totalImages - ctx.imagesWithAsyncDecoding;
225
+ if (nonAsync > 0 && ctx.totalImages > 3) {
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.' });
227
+ }
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');
229
+ },
230
+ },
231
+ {
232
+ id: 'broken-external-images',
233
+ name: 'Broken External Images',
234
+ category: 'images',
235
+ severity: 'warning',
236
+ description: 'External images should be accessible',
237
+ check: (ctx) => {
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
+ }
241
+ if (ctx.brokenExternalImages > 0) {
242
+ return createResult({ id: 'broken-external-images', name: 'Broken External Images', category: 'images', severity: 'warning' }, 'warn', `${ctx.brokenExternalImages} broken external images`, {
243
+ value: ctx.brokenExternalImages,
244
+ recommendation: 'Fix or remove broken external image references',
245
+ evidence: {
246
+ found: ctx.brokenExternalImageUrls?.slice(0, 5) || [],
247
+ expected: 'All images should load successfully',
248
+ impact: 'Broken images negatively affect user experience and may signal poor maintenance'
249
+ }
250
+ });
251
+ }
252
+ return createResult({ id: 'broken-external-images', name: 'Broken External Images', category: 'images', severity: 'warning' }, 'pass', 'No broken external images detected');
253
+ },
254
+ },
255
+ ];
@@ -0,0 +1,52 @@
1
+ import { SeoRule, RuleCategory, RuleSeverity, RuleContext, RuleResult } from './types.js';
2
+ export * from './types.js';
3
+ export * from './thresholds.js';
4
+ export declare const ALL_SEO_RULES: SeoRule[];
5
+ export declare const SCORING_WEIGHTS: {
6
+ readonly severity: {
7
+ readonly error: {
8
+ readonly pass: 10;
9
+ readonly fail: -15;
10
+ readonly warn: -10;
11
+ readonly info: 0;
12
+ };
13
+ readonly warning: {
14
+ readonly pass: 5;
15
+ readonly fail: -8;
16
+ readonly warn: -5;
17
+ readonly info: 0;
18
+ };
19
+ readonly info: {
20
+ readonly pass: 2;
21
+ readonly fail: -3;
22
+ readonly warn: -2;
23
+ readonly info: 0;
24
+ };
25
+ };
26
+ readonly category: Record<RuleCategory, number>;
27
+ };
28
+ export declare function calculateWeightedScore(results: RuleResult[]): {
29
+ score: number;
30
+ maxPossible: number;
31
+ details: {
32
+ category: RuleCategory;
33
+ score: number;
34
+ weight: number;
35
+ }[];
36
+ };
37
+ export interface RulesEngineOptions {
38
+ categories?: RuleCategory[];
39
+ excludeCategories?: RuleCategory[];
40
+ rules?: string[];
41
+ excludeRules?: string[];
42
+ minSeverity?: RuleSeverity;
43
+ }
44
+ export declare class SeoRulesEngine {
45
+ private rules;
46
+ constructor(options?: RulesEngineOptions);
47
+ evaluate(context: RuleContext): RuleResult[];
48
+ getRules(): SeoRule[];
49
+ getRulesByCategory(category: RuleCategory): SeoRule[];
50
+ getCategories(): RuleCategory[];
51
+ }
52
+ export declare function createRulesEngine(options?: RulesEngineOptions): SeoRulesEngine;