recker 1.0.42 → 1.0.43-next.7a08602

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (458) hide show
  1. package/dist/bin/recker-linux-x64 +0 -0
  2. package/dist/bin/recker-macos-x64 +0 -0
  3. package/dist/bin/recker-win-x64.exe +0 -0
  4. package/dist/bin/rek.cjs +93958 -0
  5. package/dist/browser/ai/adaptive-timeout.d.ts +50 -0
  6. package/dist/browser/ai/adaptive-timeout.js +208 -0
  7. package/dist/browser/ai/client.d.ts +22 -0
  8. package/dist/browser/ai/client.js +294 -0
  9. package/dist/browser/ai/index.d.ts +14 -0
  10. package/dist/browser/ai/index.js +11 -0
  11. package/dist/browser/ai/providers/anthropic.d.ts +63 -0
  12. package/dist/browser/ai/providers/anthropic.js +370 -0
  13. package/dist/browser/ai/providers/base.d.ts +48 -0
  14. package/dist/browser/ai/providers/base.js +150 -0
  15. package/dist/browser/ai/providers/google.d.ts +59 -0
  16. package/dist/browser/ai/providers/google.js +305 -0
  17. package/dist/browser/ai/providers/ollama.d.ts +44 -0
  18. package/dist/browser/ai/providers/ollama.js +240 -0
  19. package/dist/browser/ai/providers/openai.d.ts +64 -0
  20. package/dist/browser/ai/providers/openai.js +298 -0
  21. package/dist/browser/ai/rate-limiter.d.ts +43 -0
  22. package/dist/browser/ai/rate-limiter.js +215 -0
  23. package/dist/browser/ai/vector/index.d.ts +2 -0
  24. package/dist/browser/ai/vector/index.js +2 -0
  25. package/dist/browser/ai/vector/similarity.d.ts +2 -0
  26. package/dist/browser/ai/vector/similarity.js +27 -0
  27. package/dist/browser/ai/vector/store.d.ts +27 -0
  28. package/dist/browser/ai/vector/store.js +82 -0
  29. package/dist/browser/browser/cache.d.ts +2 -40
  30. package/dist/browser/browser/cache.js +2 -199
  31. package/dist/browser/browser/index.d.ts +8 -0
  32. package/dist/browser/browser/index.js +8 -0
  33. package/dist/browser/browser/recker.d.ts +8 -1
  34. package/dist/browser/browser/recker.js +8 -2
  35. package/dist/browser/cache/indexed-db.d.ts +10 -0
  36. package/dist/browser/cache/indexed-db.js +88 -0
  37. package/dist/browser/cache/service-worker-cache.d.ts +18 -0
  38. package/dist/browser/cache/service-worker-cache.js +103 -0
  39. package/dist/browser/cache.d.ts +2 -40
  40. package/dist/browser/cache.js +2 -199
  41. package/dist/browser/constants/user-agents.d.ts +7 -0
  42. package/dist/browser/constants/user-agents.js +7 -0
  43. package/dist/browser/core/client.d.ts +2 -0
  44. package/dist/browser/core/client.js +19 -1
  45. package/dist/browser/index.d.ts +8 -0
  46. package/dist/browser/index.js +8 -0
  47. package/dist/browser/plugins/har-recorder.d.ts +40 -0
  48. package/dist/browser/plugins/har-recorder.js +120 -0
  49. package/dist/browser/plugins/network-simulation.d.ts +7 -0
  50. package/dist/browser/plugins/network-simulation.js +13 -0
  51. package/dist/browser/presets/android.d.ts +2 -0
  52. package/dist/browser/presets/android.js +16 -0
  53. package/dist/browser/presets/anthropic.d.ts +8 -0
  54. package/dist/browser/presets/anthropic.js +27 -0
  55. package/dist/browser/presets/aws.d.ts +19 -0
  56. package/dist/browser/presets/aws.js +68 -0
  57. package/dist/browser/presets/azure-openai.d.ts +10 -0
  58. package/dist/browser/presets/azure-openai.js +35 -0
  59. package/dist/browser/presets/azure.d.ts +41 -0
  60. package/dist/browser/presets/azure.js +104 -0
  61. package/dist/browser/presets/chaturbate.d.ts +2 -0
  62. package/dist/browser/presets/chaturbate.js +17 -0
  63. package/dist/browser/presets/cloudflare.d.ts +12 -0
  64. package/dist/browser/presets/cloudflare.js +39 -0
  65. package/dist/browser/presets/cohere.d.ts +7 -0
  66. package/dist/browser/presets/cohere.js +22 -0
  67. package/dist/browser/presets/deepseek.d.ts +7 -0
  68. package/dist/browser/presets/deepseek.js +22 -0
  69. package/dist/browser/presets/digitalocean.d.ts +5 -0
  70. package/dist/browser/presets/digitalocean.js +16 -0
  71. package/dist/browser/presets/discord.d.ts +6 -0
  72. package/dist/browser/presets/discord.js +17 -0
  73. package/dist/browser/presets/elevenlabs.d.ts +6 -0
  74. package/dist/browser/presets/elevenlabs.js +20 -0
  75. package/dist/browser/presets/enhancers.d.ts +20 -0
  76. package/dist/browser/presets/enhancers.js +85 -0
  77. package/dist/browser/presets/fireworks.d.ts +7 -0
  78. package/dist/browser/presets/fireworks.js +22 -0
  79. package/dist/browser/presets/gcp.d.ts +34 -0
  80. package/dist/browser/presets/gcp.js +91 -0
  81. package/dist/browser/presets/gemini.d.ts +7 -0
  82. package/dist/browser/presets/gemini.js +23 -0
  83. package/dist/browser/presets/github.d.ts +6 -0
  84. package/dist/browser/presets/github.js +17 -0
  85. package/dist/browser/presets/gitlab.d.ts +6 -0
  86. package/dist/browser/presets/gitlab.js +16 -0
  87. package/dist/browser/presets/groq.d.ts +7 -0
  88. package/dist/browser/presets/groq.js +22 -0
  89. package/dist/browser/presets/hubspot.d.ts +9 -0
  90. package/dist/browser/presets/hubspot.js +28 -0
  91. package/dist/browser/presets/huggingface.d.ts +7 -0
  92. package/dist/browser/presets/huggingface.js +23 -0
  93. package/dist/browser/presets/index.d.ts +47 -0
  94. package/dist/browser/presets/index.js +47 -0
  95. package/dist/browser/presets/ios.d.ts +2 -0
  96. package/dist/browser/presets/ios.js +13 -0
  97. package/dist/browser/presets/linear.d.ts +5 -0
  98. package/dist/browser/presets/linear.js +16 -0
  99. package/dist/browser/presets/mailgun.d.ts +7 -0
  100. package/dist/browser/presets/mailgun.js +20 -0
  101. package/dist/browser/presets/meta.d.ts +10 -0
  102. package/dist/browser/presets/meta.js +33 -0
  103. package/dist/browser/presets/mistral.d.ts +7 -0
  104. package/dist/browser/presets/mistral.js +22 -0
  105. package/dist/browser/presets/notion.d.ts +6 -0
  106. package/dist/browser/presets/notion.js +17 -0
  107. package/dist/browser/presets/openai.d.ts +9 -0
  108. package/dist/browser/presets/openai.js +30 -0
  109. package/dist/browser/presets/oracle.d.ts +19 -0
  110. package/dist/browser/presets/oracle.js +117 -0
  111. package/dist/browser/presets/perplexity.d.ts +7 -0
  112. package/dist/browser/presets/perplexity.js +22 -0
  113. package/dist/browser/presets/pinecone.d.ts +8 -0
  114. package/dist/browser/presets/pinecone.js +42 -0
  115. package/dist/browser/presets/registry.d.ts +23 -0
  116. package/dist/browser/presets/registry.js +519 -0
  117. package/dist/browser/presets/replicate.d.ts +7 -0
  118. package/dist/browser/presets/replicate.js +23 -0
  119. package/dist/browser/presets/sendgrid.d.ts +6 -0
  120. package/dist/browser/presets/sendgrid.js +20 -0
  121. package/dist/browser/presets/sentry.d.ts +11 -0
  122. package/dist/browser/presets/sentry.js +48 -0
  123. package/dist/browser/presets/sinch.d.ts +9 -0
  124. package/dist/browser/presets/sinch.js +39 -0
  125. package/dist/browser/presets/slack.d.ts +5 -0
  126. package/dist/browser/presets/slack.js +16 -0
  127. package/dist/browser/presets/square.d.ts +10 -0
  128. package/dist/browser/presets/square.js +33 -0
  129. package/dist/browser/presets/stripe.d.ts +7 -0
  130. package/dist/browser/presets/stripe.js +23 -0
  131. package/dist/browser/presets/supabase.d.ts +6 -0
  132. package/dist/browser/presets/supabase.js +18 -0
  133. package/dist/browser/presets/tiktok.d.ts +10 -0
  134. package/dist/browser/presets/tiktok.js +38 -0
  135. package/dist/browser/presets/together.d.ts +7 -0
  136. package/dist/browser/presets/together.js +22 -0
  137. package/dist/browser/presets/twilio.d.ts +6 -0
  138. package/dist/browser/presets/twilio.js +17 -0
  139. package/dist/browser/presets/vercel.d.ts +6 -0
  140. package/dist/browser/presets/vercel.js +23 -0
  141. package/dist/browser/presets/vultr.d.ts +5 -0
  142. package/dist/browser/presets/vultr.js +16 -0
  143. package/dist/browser/presets/xai.d.ts +8 -0
  144. package/dist/browser/presets/xai.js +23 -0
  145. package/dist/browser/presets/youtube.d.ts +5 -0
  146. package/dist/browser/presets/youtube.js +20 -0
  147. package/dist/browser/recker.d.ts +8 -1
  148. package/dist/browser/recker.js +8 -2
  149. package/dist/browser/scrape/document.d.ts +5 -4
  150. package/dist/browser/scrape/document.js +89 -76
  151. package/dist/browser/scrape/element.d.ts +10 -8
  152. package/dist/browser/scrape/element.js +295 -81
  153. package/dist/browser/scrape/extractors.d.ts +11 -11
  154. package/dist/browser/scrape/extractors.js +145 -113
  155. package/dist/browser/scrape/parser/back.d.ts +1 -0
  156. package/dist/browser/scrape/parser/back.js +3 -0
  157. package/dist/browser/scrape/parser/index.d.ts +20 -0
  158. package/dist/browser/scrape/parser/index.js +19 -0
  159. package/dist/browser/scrape/parser/matcher.d.ts +30 -0
  160. package/dist/browser/scrape/parser/matcher.js +99 -0
  161. package/dist/browser/scrape/parser/nodes/comment.d.ts +12 -0
  162. package/dist/browser/scrape/parser/nodes/comment.js +21 -0
  163. package/dist/browser/scrape/parser/nodes/html.d.ts +110 -0
  164. package/dist/browser/scrape/parser/nodes/html.js +978 -0
  165. package/dist/browser/scrape/parser/nodes/node.d.ts +18 -0
  166. package/dist/browser/scrape/parser/nodes/node.js +31 -0
  167. package/dist/browser/scrape/parser/nodes/text.d.ts +14 -0
  168. package/dist/browser/scrape/parser/nodes/text.js +30 -0
  169. package/dist/browser/scrape/parser/nodes/type.d.ts +6 -0
  170. package/dist/browser/scrape/parser/nodes/type.js +7 -0
  171. package/dist/browser/scrape/parser/parse.d.ts +1 -0
  172. package/dist/browser/scrape/parser/parse.js +1 -0
  173. package/dist/browser/scrape/parser/valid.d.ts +2 -0
  174. package/dist/browser/scrape/parser/valid.js +5 -0
  175. package/dist/browser/scrape/parser/void-tag.d.ts +7 -0
  176. package/dist/browser/scrape/parser/void-tag.js +43 -0
  177. package/dist/browser/scrape/types.d.ts +7 -0
  178. package/dist/browser/seo/analyzer.d.ts +59 -0
  179. package/dist/browser/seo/analyzer.js +1399 -0
  180. package/dist/browser/seo/keywords.d.ts +16 -0
  181. package/dist/browser/seo/keywords.js +55 -0
  182. package/dist/browser/seo/rules/accessibility.d.ts +2 -0
  183. package/dist/browser/seo/rules/accessibility.js +722 -0
  184. package/dist/browser/seo/rules/ai-search.d.ts +2 -0
  185. package/dist/browser/seo/rules/ai-search.js +436 -0
  186. package/dist/browser/seo/rules/analytics.d.ts +2 -0
  187. package/dist/browser/seo/rules/analytics.js +306 -0
  188. package/dist/browser/seo/rules/best-practices.d.ts +2 -0
  189. package/dist/browser/seo/rules/best-practices.js +195 -0
  190. package/dist/browser/seo/rules/canonical.d.ts +12 -0
  191. package/dist/browser/seo/rules/canonical.js +258 -0
  192. package/dist/browser/seo/rules/content.d.ts +2 -0
  193. package/dist/browser/seo/rules/content.js +424 -0
  194. package/dist/browser/seo/rules/crawl.d.ts +2 -0
  195. package/dist/browser/seo/rules/crawl.js +435 -0
  196. package/dist/browser/seo/rules/cwv.d.ts +2 -0
  197. package/dist/browser/seo/rules/cwv.js +248 -0
  198. package/dist/browser/seo/rules/ecommerce.d.ts +2 -0
  199. package/dist/browser/seo/rules/ecommerce.js +283 -0
  200. package/dist/browser/seo/rules/i18n.d.ts +2 -0
  201. package/dist/browser/seo/rules/i18n.js +277 -0
  202. package/dist/browser/seo/rules/images.d.ts +2 -0
  203. package/dist/browser/seo/rules/images.js +235 -0
  204. package/dist/browser/seo/rules/index.d.ts +52 -0
  205. package/dist/browser/seo/rules/index.js +159 -0
  206. package/dist/browser/seo/rules/internal-linking.d.ts +2 -0
  207. package/dist/browser/seo/rules/internal-linking.js +394 -0
  208. package/dist/browser/seo/rules/links.d.ts +2 -0
  209. package/dist/browser/seo/rules/links.js +490 -0
  210. package/dist/browser/seo/rules/local.d.ts +2 -0
  211. package/dist/browser/seo/rules/local.js +289 -0
  212. package/dist/browser/seo/rules/meta.d.ts +2 -0
  213. package/dist/browser/seo/rules/meta.js +646 -0
  214. package/dist/browser/seo/rules/mobile.d.ts +2 -0
  215. package/dist/browser/seo/rules/mobile.js +161 -0
  216. package/dist/browser/seo/rules/performance.d.ts +2 -0
  217. package/dist/browser/seo/rules/performance.js +625 -0
  218. package/dist/browser/seo/rules/pwa.d.ts +2 -0
  219. package/dist/browser/seo/rules/pwa.js +299 -0
  220. package/dist/browser/seo/rules/readability.d.ts +2 -0
  221. package/dist/browser/seo/rules/readability.js +264 -0
  222. package/dist/browser/seo/rules/redirects.d.ts +16 -0
  223. package/dist/browser/seo/rules/redirects.js +199 -0
  224. package/dist/browser/seo/rules/resources.d.ts +2 -0
  225. package/dist/browser/seo/rules/resources.js +390 -0
  226. package/dist/browser/seo/rules/schema.d.ts +2 -0
  227. package/dist/browser/seo/rules/schema.js +379 -0
  228. package/dist/browser/seo/rules/security.d.ts +2 -0
  229. package/dist/browser/seo/rules/security.js +877 -0
  230. package/dist/browser/seo/rules/social.d.ts +2 -0
  231. package/dist/browser/seo/rules/social.js +603 -0
  232. package/dist/browser/seo/rules/structural.d.ts +2 -0
  233. package/dist/browser/seo/rules/structural.js +179 -0
  234. package/dist/browser/seo/rules/technical-advanced.d.ts +10 -0
  235. package/dist/browser/seo/rules/technical-advanced.js +288 -0
  236. package/dist/browser/seo/rules/technical.d.ts +2 -0
  237. package/dist/browser/seo/rules/technical.js +480 -0
  238. package/dist/browser/seo/rules/thresholds.d.ts +196 -0
  239. package/dist/browser/seo/rules/thresholds.js +118 -0
  240. package/dist/browser/seo/rules/types.d.ts +498 -0
  241. package/dist/browser/seo/rules/types.js +11 -0
  242. package/dist/browser/seo/types.d.ts +211 -0
  243. package/dist/browser/seo/types.js +1 -0
  244. package/dist/browser/transport/curl.d.ts +4 -0
  245. package/dist/browser/transport/curl.js +101 -0
  246. package/dist/browser/transport/undici.js +1 -2
  247. package/dist/browser/transport/worker.d.ts +18 -0
  248. package/dist/browser/transport/worker.js +278 -0
  249. package/dist/browser/types/index.d.ts +4 -1
  250. package/dist/browser/utils/binary-manager.d.ts +4 -0
  251. package/dist/browser/utils/binary-manager.js +72 -0
  252. package/dist/browser/utils/user-agent.js +2 -13
  253. package/dist/cache/indexed-db.d.ts +10 -0
  254. package/dist/cache/indexed-db.js +88 -0
  255. package/dist/cache/service-worker-cache.d.ts +18 -0
  256. package/dist/cache/service-worker-cache.js +103 -0
  257. package/dist/cli/commands/ai.d.ts +2 -0
  258. package/dist/cli/commands/ai.js +162 -0
  259. package/dist/cli/commands/bench.d.ts +2 -0
  260. package/dist/cli/commands/bench.js +51 -0
  261. package/dist/cli/commands/dns.d.ts +2 -0
  262. package/dist/cli/commands/dns.js +295 -0
  263. package/dist/cli/commands/har.d.ts +2 -0
  264. package/dist/cli/commands/har.js +171 -0
  265. package/dist/cli/commands/hls.d.ts +2 -0
  266. package/dist/cli/commands/hls.js +192 -0
  267. package/dist/cli/commands/network.d.ts +2 -0
  268. package/dist/cli/commands/network.js +288 -0
  269. package/dist/cli/commands/protocols.d.ts +2 -0
  270. package/dist/cli/commands/protocols.js +344 -0
  271. package/dist/cli/commands/scrape.d.ts +2 -0
  272. package/dist/cli/commands/scrape.js +176 -0
  273. package/dist/cli/commands/security.d.ts +2 -0
  274. package/dist/cli/commands/security.js +57 -0
  275. package/dist/cli/commands/seo.d.ts +2 -0
  276. package/dist/cli/commands/seo.js +125 -0
  277. package/dist/cli/commands/serve.d.ts +2 -0
  278. package/dist/cli/commands/serve.js +531 -0
  279. package/dist/cli/commands/spider.d.ts +3 -0
  280. package/dist/cli/commands/spider.js +456 -0
  281. package/dist/cli/commands/utils.d.ts +2 -0
  282. package/dist/cli/commands/utils.js +176 -0
  283. package/dist/cli/commands/vector.d.ts +2 -0
  284. package/dist/cli/commands/vector.js +158 -0
  285. package/dist/cli/handler.d.ts +2 -2
  286. package/dist/cli/handler.js +6 -6
  287. package/dist/cli/helpers.d.ts +7 -0
  288. package/dist/cli/helpers.js +128 -0
  289. package/dist/cli/index.js +96 -5228
  290. package/dist/cli/parser/help.d.ts +2 -0
  291. package/dist/cli/parser/help.js +52 -0
  292. package/dist/cli/parser/index.d.ts +3 -0
  293. package/dist/cli/parser/index.js +3 -0
  294. package/dist/cli/parser/parser.d.ts +4 -0
  295. package/dist/cli/parser/parser.js +146 -0
  296. package/dist/cli/parser/types.d.ts +41 -0
  297. package/dist/cli/parser/types.js +1 -0
  298. package/dist/cli/presets.d.ts +1 -1
  299. package/dist/cli/presets.js +1 -1
  300. package/dist/cli/router.d.ts +36 -0
  301. package/dist/cli/router.js +195 -0
  302. package/dist/cli/tui/ai-chat.js +1 -1
  303. package/dist/cli/tui/commands/context.d.ts +9 -0
  304. package/dist/cli/tui/commands/context.js +1 -0
  305. package/dist/cli/tui/commands/dns.d.ts +10 -0
  306. package/dist/cli/tui/commands/dns.js +461 -0
  307. package/dist/cli/tui/commands/hls.d.ts +2 -0
  308. package/dist/cli/tui/commands/hls.js +162 -0
  309. package/dist/cli/tui/commands/ip.d.ts +2 -0
  310. package/dist/cli/tui/commands/ip.js +45 -0
  311. package/dist/cli/tui/commands/network.d.ts +3 -0
  312. package/dist/cli/tui/commands/network.js +81 -0
  313. package/dist/cli/tui/commands/protocols.d.ts +6 -0
  314. package/dist/cli/tui/commands/protocols.js +531 -0
  315. package/dist/cli/tui/commands/security.d.ts +2 -0
  316. package/dist/cli/tui/commands/security.js +48 -0
  317. package/dist/cli/tui/commands/seo.d.ts +2 -0
  318. package/dist/cli/tui/commands/seo.js +74 -0
  319. package/dist/cli/tui/context.d.ts +12 -0
  320. package/dist/cli/tui/context.js +1 -0
  321. package/dist/cli/tui/shell.d.ts +11 -20
  322. package/dist/cli/tui/shell.js +216 -1873
  323. package/dist/constants/user-agents.d.ts +7 -0
  324. package/dist/constants/user-agents.js +7 -0
  325. package/dist/core/client.d.ts +2 -0
  326. package/dist/core/client.js +19 -1
  327. package/dist/index.d.ts +1 -0
  328. package/dist/index.js +1 -0
  329. package/dist/mcp/cli.js +2 -3
  330. package/dist/mcp/data/embeddings.json +1 -0
  331. package/dist/mcp/tools/network.js +298 -158
  332. package/dist/plugins/har-player.d.ts +23 -0
  333. package/dist/plugins/har-player.js +49 -0
  334. package/dist/plugins/har-recorder.d.ts +37 -3
  335. package/dist/plugins/har-recorder.js +116 -63
  336. package/dist/plugins/network-simulation.d.ts +7 -0
  337. package/dist/plugins/network-simulation.js +13 -0
  338. package/dist/presets/android.d.ts +2 -0
  339. package/dist/presets/android.js +16 -0
  340. package/dist/presets/chaturbate.d.ts +2 -0
  341. package/dist/presets/chaturbate.js +17 -0
  342. package/dist/presets/elevenlabs.d.ts +6 -0
  343. package/dist/presets/elevenlabs.js +20 -0
  344. package/dist/presets/enhancers.d.ts +20 -0
  345. package/dist/presets/enhancers.js +85 -0
  346. package/dist/presets/hubspot.d.ts +9 -0
  347. package/dist/presets/hubspot.js +28 -0
  348. package/dist/presets/index.d.ts +10 -0
  349. package/dist/presets/index.js +10 -0
  350. package/dist/presets/ios.d.ts +2 -0
  351. package/dist/presets/ios.js +13 -0
  352. package/dist/presets/pinecone.d.ts +8 -0
  353. package/dist/presets/pinecone.js +42 -0
  354. package/dist/presets/registry.js +60 -0
  355. package/dist/presets/sendgrid.d.ts +6 -0
  356. package/dist/presets/sendgrid.js +20 -0
  357. package/dist/presets/sentry.d.ts +11 -0
  358. package/dist/presets/sentry.js +48 -0
  359. package/dist/presets/square.d.ts +10 -0
  360. package/dist/presets/square.js +33 -0
  361. package/dist/recker.d.ts +3 -0
  362. package/dist/recker.js +4 -0
  363. package/dist/scrape/document.d.ts +5 -4
  364. package/dist/scrape/document.js +89 -76
  365. package/dist/scrape/element.d.ts +10 -8
  366. package/dist/scrape/element.js +295 -81
  367. package/dist/scrape/extractors.d.ts +11 -11
  368. package/dist/scrape/extractors.js +145 -113
  369. package/dist/scrape/index.d.ts +2 -0
  370. package/dist/scrape/index.js +1 -0
  371. package/dist/scrape/parser/back.d.ts +1 -0
  372. package/dist/scrape/parser/back.js +3 -0
  373. package/dist/scrape/parser/index.d.ts +20 -0
  374. package/dist/scrape/parser/index.js +19 -0
  375. package/dist/scrape/parser/matcher.d.ts +30 -0
  376. package/dist/scrape/parser/matcher.js +99 -0
  377. package/dist/scrape/parser/nodes/comment.d.ts +12 -0
  378. package/dist/scrape/parser/nodes/comment.js +21 -0
  379. package/dist/scrape/parser/nodes/html.d.ts +110 -0
  380. package/dist/scrape/parser/nodes/html.js +978 -0
  381. package/dist/scrape/parser/nodes/node.d.ts +18 -0
  382. package/dist/scrape/parser/nodes/node.js +31 -0
  383. package/dist/scrape/parser/nodes/text.d.ts +14 -0
  384. package/dist/scrape/parser/nodes/text.js +30 -0
  385. package/dist/scrape/parser/nodes/type.d.ts +6 -0
  386. package/dist/scrape/parser/nodes/type.js +7 -0
  387. package/dist/scrape/parser/parse.d.ts +1 -0
  388. package/dist/scrape/parser/parse.js +1 -0
  389. package/dist/scrape/parser/valid.d.ts +2 -0
  390. package/dist/scrape/parser/valid.js +5 -0
  391. package/dist/scrape/parser/void-tag.d.ts +7 -0
  392. package/dist/scrape/parser/void-tag.js +43 -0
  393. package/dist/scrape/spider.d.ts +19 -0
  394. package/dist/scrape/spider.js +28 -3
  395. package/dist/scrape/types.d.ts +7 -0
  396. package/dist/seo/analyzer.d.ts +15 -5
  397. package/dist/seo/analyzer.js +636 -175
  398. package/dist/seo/formatter.d.ts +16 -0
  399. package/dist/seo/formatter.js +228 -0
  400. package/dist/seo/index.d.ts +2 -0
  401. package/dist/seo/index.js +1 -0
  402. package/dist/seo/keywords.d.ts +16 -0
  403. package/dist/seo/keywords.js +55 -0
  404. package/dist/seo/rules/accessibility.js +85 -57
  405. package/dist/seo/rules/ai-search.js +44 -31
  406. package/dist/seo/rules/analytics.d.ts +2 -0
  407. package/dist/seo/rules/analytics.js +306 -0
  408. package/dist/seo/rules/best-practices.js +21 -14
  409. package/dist/seo/rules/canonical.js +31 -22
  410. package/dist/seo/rules/content.js +207 -19
  411. package/dist/seo/rules/crawl.js +55 -40
  412. package/dist/seo/rules/cwv.js +21 -15
  413. package/dist/seo/rules/ecommerce.js +51 -20
  414. package/dist/seo/rules/i18n.js +61 -33
  415. package/dist/seo/rules/images.js +87 -28
  416. package/dist/seo/rules/index.js +2 -0
  417. package/dist/seo/rules/internal-linking.js +58 -39
  418. package/dist/seo/rules/links.js +70 -51
  419. package/dist/seo/rules/local.js +49 -25
  420. package/dist/seo/rules/meta.js +161 -62
  421. package/dist/seo/rules/mobile.js +112 -2
  422. package/dist/seo/rules/performance.js +309 -54
  423. package/dist/seo/rules/pwa.js +36 -39
  424. package/dist/seo/rules/readability.js +31 -22
  425. package/dist/seo/rules/redirects.js +21 -15
  426. package/dist/seo/rules/resources.js +59 -42
  427. package/dist/seo/rules/schema.js +333 -8
  428. package/dist/seo/rules/security.js +142 -80
  429. package/dist/seo/rules/social.js +277 -47
  430. package/dist/seo/rules/structural.js +40 -16
  431. package/dist/seo/rules/technical-advanced.js +27 -22
  432. package/dist/seo/rules/technical.js +243 -42
  433. package/dist/seo/rules/types.d.ts +53 -1
  434. package/dist/seo/seo-spider.d.ts +22 -0
  435. package/dist/seo/seo-spider.js +77 -13
  436. package/dist/seo/types.d.ts +8 -1
  437. package/dist/seo/validators/llms-txt.js +19 -0
  438. package/dist/seo/validators/rss.d.ts +11 -0
  439. package/dist/seo/validators/rss.js +93 -0
  440. package/dist/seo/validators/sitemap.js +36 -26
  441. package/dist/transport/curl.d.ts +4 -0
  442. package/dist/transport/curl.js +101 -0
  443. package/dist/transport/udp.js +0 -1
  444. package/dist/transport/undici.js +1 -2
  445. package/dist/transport/worker.d.ts +18 -0
  446. package/dist/transport/worker.js +278 -0
  447. package/dist/types/index.d.ts +4 -1
  448. package/dist/utils/binary-manager.d.ts +4 -0
  449. package/dist/utils/binary-manager.js +72 -0
  450. package/dist/utils/optional-require.d.ts +7 -8
  451. package/dist/utils/optional-require.js +2 -21
  452. package/dist/utils/upload.d.ts +6 -0
  453. package/dist/utils/upload.js +11 -0
  454. package/dist/utils/user-agent.js +2 -13
  455. package/dist/version.js +1 -1
  456. package/package.json +14 -5
  457. package/dist/browser/utils/optional-require.d.ts +0 -19
  458. package/dist/browser/utils/optional-require.js +0 -105
@@ -7,8 +7,9 @@ export const cwvRules = [
7
7
  severity: 'warning',
8
8
  description: 'Hero images should be optimized for fast LCP',
9
9
  check: (ctx) => {
10
- if (!ctx.lcpCandidate)
11
- return null;
10
+ if (!ctx.lcpCandidate) {
11
+ return createResult({ id: 'cwv-lcp-hero-image', name: 'LCP Hero Image', category: 'performance', severity: 'warning' }, 'info', 'No LCP candidate detected', { recommendation: 'Ensure the page has a hero image or prominent content element' });
12
+ }
12
13
  const issues = [];
13
14
  if (ctx.lcpCandidate.loading === 'lazy') {
14
15
  issues.push('LCP image has loading="lazy" (should be eager or omitted)');
@@ -42,8 +43,9 @@ export const cwvRules = [
42
43
  severity: 'warning',
43
44
  description: 'Text LCP elements should render immediately without font blocking',
44
45
  check: (ctx) => {
45
- if (!ctx.webFonts || ctx.webFonts.length === 0)
46
- return null;
46
+ if (!ctx.webFonts || ctx.webFonts.length === 0) {
47
+ return createResult({ id: 'cwv-lcp-text-visible', name: 'LCP Text Visibility', category: 'performance', severity: 'warning' }, 'info', 'No web fonts detected', { recommendation: 'No custom web fonts to analyze' });
48
+ }
47
49
  const blockingFonts = ctx.webFonts.filter(f => !f.hasSwap && !f.hasOptional);
48
50
  if (blockingFonts.length > 0) {
49
51
  return createResult({ id: 'cwv-lcp-text-visible', name: 'LCP Text Visibility', category: 'performance', severity: 'warning' }, 'warn', `${blockingFonts.length} web font(s) may block text rendering`, {
@@ -67,8 +69,9 @@ export const cwvRules = [
67
69
  severity: 'warning',
68
70
  description: 'Minimize render-blocking CSS for faster LCP',
69
71
  check: (ctx) => {
70
- if (ctx.renderBlockingStylesheets === undefined)
71
- return null;
72
+ if (ctx.renderBlockingStylesheets === undefined) {
73
+ return createResult({ id: 'cwv-render-blocking-css', name: 'Render-Blocking CSS', category: 'performance', severity: 'warning' }, 'info', 'Unable to check render-blocking CSS (data unavailable)', { recommendation: 'Ensure CSS resource analysis completed' });
74
+ }
72
75
  if (ctx.renderBlockingStylesheets > 3) {
73
76
  return createResult({ id: 'cwv-render-blocking-css', name: 'Render-Blocking CSS', category: 'performance', severity: 'warning' }, 'warn', `${ctx.renderBlockingStylesheets} render-blocking stylesheets`, {
74
77
  recommendation: 'Reduce render-blocking CSS or use critical CSS inline',
@@ -90,8 +93,9 @@ export const cwvRules = [
90
93
  severity: 'warning',
91
94
  description: 'Scripts in head should use async or defer',
92
95
  check: (ctx) => {
93
- if (ctx.renderBlockingScripts === undefined)
94
- return null;
96
+ if (ctx.renderBlockingScripts === undefined) {
97
+ return createResult({ id: 'cwv-render-blocking-js', name: 'Render-Blocking JavaScript', category: 'performance', severity: 'warning' }, 'info', 'Unable to check render-blocking scripts (data unavailable)', { recommendation: 'Ensure script analysis completed' });
98
+ }
95
99
  if (ctx.renderBlockingScripts > 0) {
96
100
  return createResult({ id: 'cwv-render-blocking-js', name: 'Render-Blocking JavaScript', category: 'performance', severity: 'warning' }, 'warn', `${ctx.renderBlockingScripts} render-blocking script(s) in <head>`, {
97
101
  recommendation: 'Add async or defer to scripts, or move to end of body',
@@ -113,8 +117,9 @@ export const cwvRules = [
113
117
  severity: 'warning',
114
118
  description: 'Images should have explicit width and height to prevent layout shift',
115
119
  check: (ctx) => {
116
- if (ctx.imagesMissingDimensions === undefined)
117
- return null;
120
+ if (ctx.imagesMissingDimensions === undefined) {
121
+ return createResult({ id: 'cwv-cls-image-dimensions', name: 'Image Dimensions', category: 'performance', severity: 'warning' }, 'info', 'Unable to check image dimensions (data unavailable)', { recommendation: 'Ensure image analysis completed' });
122
+ }
118
123
  if (ctx.imagesMissingDimensions > 0) {
119
124
  const total = ctx.totalImages || 0;
120
125
  const percent = total > 0 ? Math.round((ctx.imagesMissingDimensions / total) * 100) : 0;
@@ -149,7 +154,7 @@ export const cwvRules = [
149
154
  },
150
155
  });
151
156
  }
152
- return null;
157
+ return createResult({ id: 'cwv-cls-aspect-ratio', name: 'Aspect Ratio CSS', category: 'performance', severity: 'info' }, 'pass', 'Aspect ratio handling is appropriate');
153
158
  },
154
159
  },
155
160
  {
@@ -159,8 +164,9 @@ export const cwvRules = [
159
164
  severity: 'info',
160
165
  description: 'Web fonts should have size-matched fallbacks',
161
166
  check: (ctx) => {
162
- if (!ctx.webFonts || ctx.webFonts.length === 0)
163
- return null;
167
+ if (!ctx.webFonts || ctx.webFonts.length === 0) {
168
+ return createResult({ id: 'cwv-cls-font-fallback', name: 'Font Fallback Metrics', category: 'performance', severity: 'info' }, 'info', 'No web fonts to check', { recommendation: 'No custom web fonts detected' });
169
+ }
164
170
  const fontsWithoutMetrics = ctx.webFonts.filter(f => !f.hasSizeAdjust && !f.hasAscentOverride);
165
171
  if (fontsWithoutMetrics.length > 0 && ctx.webFonts.length > 0) {
166
172
  return createResult({ id: 'cwv-cls-font-fallback', name: 'Font Fallback Metrics', category: 'performance', severity: 'info' }, 'info', 'Web fonts may cause layout shift during swap', {
@@ -177,7 +183,7 @@ export const cwvRules = [
177
183
  },
178
184
  });
179
185
  }
180
- return null;
186
+ return createResult({ id: 'cwv-cls-font-fallback', name: 'Font Fallback Metrics', category: 'performance', severity: 'info' }, 'pass', 'Font fallback metrics configured');
181
187
  },
182
188
  },
183
189
  {
@@ -236,7 +242,7 @@ export const cwvRules = [
236
242
  },
237
243
  });
238
244
  }
239
- return null;
245
+ return createResult({ id: 'cwv-critical-css', name: 'Critical CSS', category: 'performance', severity: 'info' }, 'info', 'No render-blocking stylesheets to optimize', { recommendation: 'Page has no render-blocking CSS' });
240
246
  },
241
247
  },
242
248
  ];
@@ -7,10 +7,16 @@ export const ecommerceRules = [
7
7
  severity: 'warning',
8
8
  description: 'Product pages should have Product schema for rich snippets',
9
9
  check: (ctx) => {
10
- if (!ctx.jsonLdTypes)
11
- return null;
12
- if (!ctx.isProductPage)
13
- return null;
10
+ if (!ctx.isProductPage) {
11
+ return createResult({ id: 'ecommerce-product-schema', name: 'Product Schema', category: 'structured-data', severity: 'warning' }, 'info', 'Not a product page', {
12
+ recommendation: 'Add Product schema if this is a product page',
13
+ });
14
+ }
15
+ if (!ctx.jsonLdTypes) {
16
+ return createResult({ id: 'ecommerce-product-schema', name: 'Product Schema', category: 'structured-data', severity: 'warning' }, 'warn', 'Product page has no JSON-LD structured data', {
17
+ recommendation: 'Add Product structured data for rich snippets',
18
+ });
19
+ }
14
20
  const hasProduct = ctx.jsonLdTypes.includes('Product');
15
21
  if (!hasProduct) {
16
22
  return createResult({ id: 'ecommerce-product-schema', name: 'Product Schema', category: 'structured-data', severity: 'warning' }, 'warn', 'Product page missing Product schema', {
@@ -42,8 +48,11 @@ export const ecommerceRules = [
42
48
  severity: 'warning',
43
49
  description: 'Product schema should include price information',
44
50
  check: (ctx) => {
45
- if (!ctx.productSchema)
46
- return null;
51
+ if (!ctx.productSchema) {
52
+ return createResult({ id: 'ecommerce-product-price', name: 'Product Price', category: 'structured-data', severity: 'warning' }, 'info', 'Not applicable (no Product schema)', {
53
+ recommendation: 'Add Product schema first, then include price information',
54
+ });
55
+ }
47
56
  const hasPrice = ctx.productSchema.offers?.price !== undefined ||
48
57
  ctx.productSchema.offers?.lowPrice !== undefined;
49
58
  const hasCurrency = ctx.productSchema.offers?.priceCurrency !== undefined;
@@ -75,8 +84,11 @@ export const ecommerceRules = [
75
84
  severity: 'info',
76
85
  description: 'Product schema should include availability status',
77
86
  check: (ctx) => {
78
- if (!ctx.productSchema?.offers)
79
- return null;
87
+ if (!ctx.productSchema?.offers) {
88
+ return createResult({ id: 'ecommerce-product-availability', name: 'Product Availability', category: 'structured-data', severity: 'info' }, 'info', 'Not applicable (no Product offers schema)', {
89
+ recommendation: 'Add Product schema with offers to include availability',
90
+ });
91
+ }
80
92
  const availability = ctx.productSchema.offers.availability;
81
93
  if (!availability) {
82
94
  return createResult({ id: 'ecommerce-product-availability', name: 'Product Availability', category: 'structured-data', severity: 'info' }, 'info', 'Product schema missing availability', {
@@ -99,8 +111,11 @@ export const ecommerceRules = [
99
111
  severity: 'warning',
100
112
  description: 'Product schema should include high-quality images',
101
113
  check: (ctx) => {
102
- if (!ctx.productSchema)
103
- return null;
114
+ if (!ctx.productSchema) {
115
+ return createResult({ id: 'ecommerce-product-image', name: 'Product Image', category: 'structured-data', severity: 'warning' }, 'info', 'Not applicable (no Product schema)', {
116
+ recommendation: 'Add Product schema first, then include images',
117
+ });
118
+ }
104
119
  const hasImage = ctx.productSchema.image !== undefined;
105
120
  if (!hasImage) {
106
121
  return createResult({ id: 'ecommerce-product-image', name: 'Product Image', category: 'structured-data', severity: 'warning' }, 'warn', 'Product schema missing image', {
@@ -124,8 +139,11 @@ export const ecommerceRules = [
124
139
  severity: 'info',
125
140
  description: 'Product schema can include aggregate ratings for star snippets',
126
141
  check: (ctx) => {
127
- if (!ctx.productSchema)
128
- return null;
142
+ if (!ctx.productSchema) {
143
+ return createResult({ id: 'ecommerce-product-reviews', name: 'Product Reviews', category: 'structured-data', severity: 'info' }, 'info', 'Not applicable (no Product schema)', {
144
+ recommendation: 'Add Product schema first, then include reviews/ratings',
145
+ });
146
+ }
129
147
  const hasRating = ctx.productSchema.aggregateRating !== undefined;
130
148
  const hasReviews = ctx.productSchema.review !== undefined;
131
149
  if (!hasRating && !hasReviews) {
@@ -155,8 +173,11 @@ export const ecommerceRules = [
155
173
  severity: 'info',
156
174
  description: 'Product schema should include brand information',
157
175
  check: (ctx) => {
158
- if (!ctx.productSchema)
159
- return null;
176
+ if (!ctx.productSchema) {
177
+ return createResult({ id: 'ecommerce-product-brand', name: 'Product Brand', category: 'structured-data', severity: 'info' }, 'info', 'Not applicable (no Product schema)', {
178
+ recommendation: 'Add Product schema first, then include brand',
179
+ });
180
+ }
160
181
  const hasBrand = ctx.productSchema.brand !== undefined;
161
182
  if (!hasBrand) {
162
183
  return createResult({ id: 'ecommerce-product-brand', name: 'Product Brand', category: 'structured-data', severity: 'info' }, 'info', 'Product schema missing brand', {
@@ -180,8 +201,11 @@ export const ecommerceRules = [
180
201
  severity: 'info',
181
202
  description: 'Product schema should include unique identifiers (SKU, GTIN, MPN)',
182
203
  check: (ctx) => {
183
- if (!ctx.productSchema)
184
- return null;
204
+ if (!ctx.productSchema) {
205
+ return createResult({ id: 'ecommerce-product-sku', name: 'Product Identifiers', category: 'structured-data', severity: 'info' }, 'info', 'Not applicable (no Product schema)', {
206
+ recommendation: 'Add Product schema first, then include identifiers',
207
+ });
208
+ }
185
209
  const hasSku = ctx.productSchema.sku !== undefined;
186
210
  const hasGtin = ctx.productSchema.gtin !== undefined ||
187
211
  ctx.productSchema.gtin13 !== undefined ||
@@ -215,11 +239,13 @@ export const ecommerceRules = [
215
239
  severity: 'info',
216
240
  description: 'Time-sensitive offers should have valid date ranges',
217
241
  check: (ctx) => {
218
- if (!ctx.productSchema?.offers)
219
- return null;
242
+ if (!ctx.productSchema?.offers) {
243
+ return createResult({ id: 'ecommerce-offer-valid-dates', name: 'Offer Valid Dates', category: 'structured-data', severity: 'info' }, 'info', 'Not applicable (no Product offers schema)', {
244
+ recommendation: 'Add Product schema with offers to enable date validation',
245
+ });
246
+ }
220
247
  const offers = ctx.productSchema.offers;
221
248
  const priceValidUntil = offers.priceValidUntil;
222
- const validFrom = offers.validFrom;
223
249
  const validThrough = offers.validThrough;
224
250
  if (priceValidUntil) {
225
251
  const endDate = new Date(priceValidUntil);
@@ -246,7 +272,12 @@ export const ecommerceRules = [
246
272
  });
247
273
  }
248
274
  }
249
- return null;
275
+ if (priceValidUntil || validThrough) {
276
+ return createResult({ id: 'ecommerce-offer-valid-dates', name: 'Offer Valid Dates', category: 'structured-data', severity: 'info' }, 'pass', 'Offer dates are valid');
277
+ }
278
+ return createResult({ id: 'ecommerce-offer-valid-dates', name: 'Offer Valid Dates', category: 'structured-data', severity: 'info' }, 'info', 'No offer date constraints specified', {
279
+ recommendation: 'Consider adding priceValidUntil for time-limited offers',
280
+ });
250
281
  },
251
282
  },
252
283
  ];
@@ -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(/\/$/, '');
@@ -50,7 +56,7 @@ export const i18nRules = [
50
56
  },
51
57
  });
52
58
  }
53
- return null;
59
+ return createResult({ id: 'i18n-hreflang-self', name: 'Hreflang Self-Reference', category: 'technical', severity: 'warning' }, 'pass', 'Self-referencing hreflang tag present');
54
60
  },
55
61
  },
56
62
  {
@@ -60,8 +66,11 @@ export const i18nRules = [
60
66
  severity: 'info',
61
67
  description: 'Include x-default hreflang for users outside defined regions',
62
68
  check: (ctx) => {
63
- if (!ctx.hreflangTags || ctx.hreflangTags.length < 2)
64
- return null;
69
+ if (!ctx.hreflangTags || ctx.hreflangTags.length < 2) {
70
+ 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)', {
71
+ recommendation: 'Add multiple hreflang tags to support international visitors',
72
+ });
73
+ }
65
74
  const hasXDefault = ctx.hreflangTags.some((tag) => tag.lang === 'x-default');
66
75
  if (!hasXDefault) {
67
76
  return createResult({ id: 'i18n-hreflang-x-default', name: 'Hreflang X-Default', category: 'technical', severity: 'info' }, 'info', 'No x-default hreflang tag found', {
@@ -82,8 +91,9 @@ export const i18nRules = [
82
91
  severity: 'warning',
83
92
  description: 'Hreflang language codes must be valid ISO 639-1 codes',
84
93
  check: (ctx) => {
85
- if (!ctx.hreflangTags || ctx.hreflangTags.length === 0)
86
- return null;
94
+ if (!ctx.hreflangTags || ctx.hreflangTags.length === 0) {
95
+ return createResult({ id: 'i18n-hreflang-valid-codes', name: 'Hreflang Valid Codes', category: 'technical', severity: 'warning' }, 'info', 'Not applicable (no hreflang tags)');
96
+ }
87
97
  const validLanguageCodes = new Set([
88
98
  'aa', 'ab', 'af', 'ak', 'sq', 'am', 'ar', 'an', 'hy', 'as', 'av', 'ae', 'ay', 'az',
89
99
  'ba', 'bm', 'eu', 'be', 'bn', 'bh', 'bi', 'bo', 'bs', 'br', 'bg', 'my', 'ca', 'cs',
@@ -117,7 +127,7 @@ export const i18nRules = [
117
127
  },
118
128
  });
119
129
  }
120
- return null;
130
+ 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
131
  },
122
132
  },
123
133
  {
@@ -127,8 +137,11 @@ export const i18nRules = [
127
137
  severity: 'warning',
128
138
  description: 'All hreflang URLs should return links back to this page (bidirectional)',
129
139
  check: (ctx) => {
130
- if (!ctx.hreflangTags || ctx.hreflangTags.length < 2)
131
- return null;
140
+ if (!ctx.hreflangTags || ctx.hreflangTags.length < 2) {
141
+ return createResult({ id: 'i18n-hreflang-return-links', name: 'Hreflang Return Links', category: 'technical', severity: 'warning' }, 'info', 'Not applicable (need 2+ hreflang tags)', {
142
+ recommendation: 'Add hreflang tags to enable return link verification',
143
+ });
144
+ }
132
145
  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
146
  recommendation: 'Ensure all alternate pages link back to this page with matching hreflang tags',
134
147
  evidence: {
@@ -145,19 +158,17 @@ export const i18nRules = [
145
158
  severity: 'info',
146
159
  description: 'Content-Language header can indicate the language of the document',
147
160
  check: (ctx) => {
148
- if (!ctx.responseHeaders)
149
- return null;
161
+ if (!ctx.responseHeaders) {
162
+ return createResult({ id: 'i18n-content-language', name: 'Content-Language Header', category: 'technical', severity: 'info' }, 'info', 'Cannot verify (response headers not available)');
163
+ }
150
164
  const contentLang = ctx.responseHeaders['content-language'] || ctx.responseHeaders['Content-Language'];
151
165
  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;
166
+ return createResult({ id: 'i18n-content-language', name: 'Content-Language Header', category: 'technical', severity: 'info' }, 'info', 'Content-Language header not set', {
167
+ recommendation: `Consider adding Content-Language: ${ctx.langValue || 'en'} header`,
168
+ evidence: {
169
+ impact: 'While not critical for SEO, it helps with content negotiation',
170
+ },
171
+ });
161
172
  }
162
173
  if (ctx.langValue) {
163
174
  const headerLang = Array.isArray(contentLang) ? contentLang[0] : contentLang;
@@ -179,8 +190,18 @@ export const i18nRules = [
179
190
  severity: 'warning',
180
191
  description: 'HTML lang attribute should match the og:locale if present',
181
192
  check: (ctx) => {
182
- if (!ctx.hasLang || !ctx.ogLocale)
183
- return null;
193
+ if (!ctx.hasLang && !ctx.ogLocale) {
194
+ return createResult({ id: 'i18n-lang-consistency', name: 'Language Consistency', category: 'technical', severity: 'warning' }, 'info', 'No lang attribute or og:locale to compare', {
195
+ recommendation: 'Add html lang attribute and og:locale for language consistency',
196
+ });
197
+ }
198
+ if (!ctx.hasLang || !ctx.ogLocale) {
199
+ 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', {
200
+ recommendation: ctx.hasLang
201
+ ? 'Add og:locale meta tag for social platforms'
202
+ : 'Add html lang attribute for language declaration',
203
+ });
204
+ }
184
205
  const htmlLang = ctx.langValue?.toLowerCase().split('-')[0];
185
206
  const ogLocaleLang = ctx.ogLocale.toLowerCase().split('_')[0];
186
207
  if (htmlLang !== ogLocaleLang) {
@@ -192,7 +213,7 @@ export const i18nRules = [
192
213
  },
193
214
  });
194
215
  }
195
- return null;
216
+ 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
217
  },
197
218
  },
198
219
  {
@@ -202,8 +223,11 @@ export const i18nRules = [
202
223
  severity: 'info',
203
224
  description: 'Consider using region-specific language codes for better targeting',
204
225
  check: (ctx) => {
205
- if (!ctx.hasLang || !ctx.langValue)
206
- return null;
226
+ if (!ctx.hasLang || !ctx.langValue) {
227
+ return createResult({ id: 'i18n-lang-region', name: 'Language Region Specificity', category: 'technical', severity: 'info' }, 'info', 'No lang attribute to analyze', {
228
+ recommendation: 'Add html lang attribute first',
229
+ });
230
+ }
207
231
  const multiRegionalLangs = ['en', 'es', 'pt', 'zh', 'fr', 'de', 'ar'];
208
232
  const langPrimary = ctx.langValue.toLowerCase().split('-')[0];
209
233
  if (multiRegionalLangs.includes(langPrimary) && !ctx.langValue.includes('-')) {
@@ -216,7 +240,7 @@ export const i18nRules = [
216
240
  },
217
241
  });
218
242
  }
219
- return null;
243
+ return createResult({ id: 'i18n-lang-region', name: 'Language Region Specificity', category: 'technical', severity: 'info' }, 'pass', `Lang attribute: ${ctx.langValue}`);
220
244
  },
221
245
  },
222
246
  {
@@ -226,8 +250,12 @@ export const i18nRules = [
226
250
  severity: 'warning',
227
251
  description: 'Hreflang language should match page content language',
228
252
  check: (ctx) => {
229
- if (!ctx.hreflangTags || !ctx.detectedLanguage)
230
- return null;
253
+ if (!ctx.hreflangTags || ctx.hreflangTags.length === 0) {
254
+ return createResult({ id: 'hreflang-language-mismatch', name: 'Hreflang Language Mismatch', category: 'technical', severity: 'warning' }, 'info', 'Not applicable (no hreflang tags)');
255
+ }
256
+ if (!ctx.detectedLanguage) {
257
+ return createResult({ id: 'hreflang-language-mismatch', name: 'Hreflang Language Mismatch', category: 'technical', severity: 'warning' }, 'info', 'Cannot verify (language detection not available)');
258
+ }
231
259
  const selfHreflang = ctx.hreflangTags.find(tag => tag.href === ctx.url || tag.href === ctx.canonicalUrl);
232
260
  if (selfHreflang && ctx.detectedLanguage) {
233
261
  const hreflangLang = selfHreflang.lang.split('-')[0].toLowerCase();
@@ -243,7 +271,7 @@ export const i18nRules = [
243
271
  });
244
272
  }
245
273
  }
246
- return null;
274
+ return createResult({ id: 'hreflang-language-mismatch', name: 'Hreflang Language Mismatch', category: 'technical', severity: 'warning' }, 'pass', 'Hreflang language matches detected content language');
247
275
  },
248
276
  },
249
277
  ];