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
@@ -31,8 +31,9 @@ export const crawlRules = [
31
31
  severity: 'error',
32
32
  description: 'Check if page is blocked from indexing',
33
33
  check: (ctx) => {
34
- if (!ctx.metaRobots)
35
- return null;
34
+ if (!ctx.metaRobots) {
35
+ return createResult({ id: 'crawl-robots-noindex', name: 'Robots Noindex', category: 'technical', severity: 'error' }, 'info', 'Not applicable (robots meta tag not present)', { recommendation: 'This rule checks for noindex directives when robots meta tags are present' });
36
+ }
36
37
  const hasNoindex = ctx.metaRobots.some(r => r.toLowerCase().includes('noindex'));
37
38
  if (hasNoindex) {
38
39
  return createResult({ id: 'crawl-robots-noindex', name: 'Robots Noindex', category: 'technical', severity: 'error' }, 'fail', 'Page is set to noindex', {
@@ -43,7 +44,7 @@ export const crawlRules = [
43
44
  },
44
45
  });
45
46
  }
46
- return null;
47
+ return createResult({ id: 'crawl-robots-noindex', name: 'Robots Noindex', category: 'technical', severity: 'error' }, 'info', 'Not applicable (noindex not detected)', { recommendation: 'This rule checks for noindex directives that would prevent page indexing' });
47
48
  },
48
49
  },
49
50
  {
@@ -53,8 +54,9 @@ export const crawlRules = [
53
54
  severity: 'warning',
54
55
  description: 'Check if page links are blocked from following',
55
56
  check: (ctx) => {
56
- if (!ctx.metaRobots)
57
- return null;
57
+ if (!ctx.metaRobots) {
58
+ return createResult({ id: 'crawl-robots-nofollow', name: 'Robots Nofollow', category: 'technical', severity: 'warning' }, 'info', 'Not applicable (robots meta tag not present)', { recommendation: 'This rule checks for nofollow directives when robots meta tags are present' });
59
+ }
58
60
  const hasNofollow = ctx.metaRobots.some(r => r.toLowerCase().includes('nofollow'));
59
61
  if (hasNofollow) {
60
62
  return createResult({ id: 'crawl-robots-nofollow', name: 'Robots Nofollow', category: 'technical', severity: 'warning' }, 'warn', 'Page has nofollow directive', {
@@ -65,7 +67,7 @@ export const crawlRules = [
65
67
  },
66
68
  });
67
69
  }
68
- return null;
70
+ return createResult({ id: 'crawl-robots-nofollow', name: 'Robots Nofollow', category: 'technical', severity: 'warning' }, 'info', 'Not applicable (nofollow not detected)', { recommendation: 'This rule checks for nofollow directives that would prevent link following' });
69
71
  },
70
72
  },
71
73
  {
@@ -105,12 +107,14 @@ export const crawlRules = [
105
107
  severity: 'warning',
106
108
  description: 'Check X-Robots-Tag HTTP header for indexing directives',
107
109
  check: (ctx) => {
108
- if (!ctx.responseHeaders)
109
- return null;
110
+ if (!ctx.responseHeaders) {
111
+ return createResult({ id: 'crawl-x-robots-tag', name: 'X-Robots-Tag Header', category: 'technical', severity: 'warning' }, 'info', 'Not applicable (response headers unavailable)', { recommendation: 'This rule checks for X-Robots-Tag headers when HTTP response headers are available' });
112
+ }
110
113
  const xRobotsTag = ctx.responseHeaders['x-robots-tag'] ||
111
114
  ctx.responseHeaders['X-Robots-Tag'];
112
- if (!xRobotsTag)
113
- return null;
115
+ if (!xRobotsTag) {
116
+ return createResult({ id: 'crawl-x-robots-tag', name: 'X-Robots-Tag Header', category: 'technical', severity: 'warning' }, 'info', 'Not applicable (X-Robots-Tag header not present)', { recommendation: 'This rule checks for X-Robots-Tag headers when the header is present' });
117
+ }
114
118
  const tagValue = Array.isArray(xRobotsTag) ? xRobotsTag.join(', ') : xRobotsTag;
115
119
  if (tagValue.toLowerCase().includes('noindex')) {
116
120
  return createResult({ id: 'crawl-x-robots-tag', name: 'X-Robots-Tag Header', category: 'technical', severity: 'warning' }, 'fail', 'X-Robots-Tag contains noindex', {
@@ -151,8 +155,9 @@ export const crawlRules = [
151
155
  severity: 'info',
152
156
  description: 'Canonical should point to the current page or explicit alternate',
153
157
  check: (ctx) => {
154
- if (!ctx.hasCanonical || !ctx.canonicalUrl || !ctx.url)
155
- return null;
158
+ if (!ctx.hasCanonical || !ctx.canonicalUrl || !ctx.url) {
159
+ return createResult({ id: 'crawl-canonical-self', name: 'Canonical Self-Reference', category: 'technical', severity: 'info' }, 'info', 'Not applicable (canonical URL or page URL unavailable)', { recommendation: 'This rule checks canonical self-reference when canonical and page URL information is available' });
160
+ }
156
161
  const normalizeUrl = (url) => {
157
162
  try {
158
163
  const u = new URL(url);
@@ -174,7 +179,7 @@ export const crawlRules = [
174
179
  },
175
180
  });
176
181
  }
177
- return null;
182
+ return createResult({ id: 'crawl-canonical-self', name: 'Canonical Self-Reference', category: 'technical', severity: 'info' }, 'info', 'Not applicable (canonical is self-referencing)', { recommendation: 'This rule checks for non-self-referencing canonicals which may indicate URL consolidation' });
178
183
  },
179
184
  },
180
185
  {
@@ -184,8 +189,9 @@ export const crawlRules = [
184
189
  severity: 'warning',
185
190
  description: 'Canonical URL should be absolute, not relative',
186
191
  check: (ctx) => {
187
- if (!ctx.canonicalUrl)
188
- return null;
192
+ if (!ctx.canonicalUrl) {
193
+ return createResult({ id: 'crawl-canonical-absolute', name: 'Canonical Absolute URL', category: 'technical', severity: 'warning' }, 'info', 'Not applicable (canonical URL not present)', { recommendation: 'This rule checks canonical URL format when a canonical URL exists' });
194
+ }
189
195
  const isAbsolute = ctx.canonicalUrl.startsWith('http://') ||
190
196
  ctx.canonicalUrl.startsWith('https://');
191
197
  if (!isAbsolute) {
@@ -197,7 +203,7 @@ export const crawlRules = [
197
203
  },
198
204
  });
199
205
  }
200
- return null;
206
+ return createResult({ id: 'crawl-canonical-absolute', name: 'Canonical Absolute URL', category: 'technical', severity: 'warning' }, 'info', 'Not applicable (canonical URL is absolute)', { recommendation: 'This rule checks for relative canonical URLs which should be avoided' });
201
207
  },
202
208
  },
203
209
  {
@@ -207,8 +213,9 @@ export const crawlRules = [
207
213
  severity: 'warning',
208
214
  description: 'Canonical URL should use HTTPS',
209
215
  check: (ctx) => {
210
- if (!ctx.canonicalUrl)
211
- return null;
216
+ if (!ctx.canonicalUrl) {
217
+ return createResult({ id: 'crawl-canonical-https', name: 'Canonical HTTPS', category: 'technical', severity: 'warning' }, 'info', 'Not applicable (canonical URL not present)', { recommendation: 'This rule checks canonical URL protocol when a canonical URL exists' });
218
+ }
212
219
  if (ctx.canonicalUrl.startsWith('http://')) {
213
220
  return createResult({ id: 'crawl-canonical-https', name: 'Canonical HTTPS', category: 'technical', severity: 'warning' }, 'warn', 'Canonical URL uses HTTP instead of HTTPS', {
214
221
  evidence: {
@@ -218,7 +225,7 @@ export const crawlRules = [
218
225
  },
219
226
  });
220
227
  }
221
- return null;
228
+ return createResult({ id: 'crawl-canonical-https', name: 'Canonical HTTPS', category: 'technical', severity: 'warning' }, 'info', 'Not applicable (canonical URL uses HTTPS)', { recommendation: 'This rule checks for HTTP canonical URLs which should use HTTPS instead' });
222
229
  },
223
230
  },
224
231
  {
@@ -228,8 +235,9 @@ export const crawlRules = [
228
235
  severity: 'info',
229
236
  description: 'URLs with tracking parameters should have proper canonical',
230
237
  check: (ctx) => {
231
- if (!ctx.url)
232
- return null;
238
+ if (!ctx.url) {
239
+ return createResult({ id: 'crawl-url-parameters', name: 'URL Parameters', category: 'technical', severity: 'info' }, 'info', 'Not applicable (page URL unavailable)', { recommendation: 'This rule checks for tracking parameters when page URL information is available' });
240
+ }
233
241
  try {
234
242
  const url = new URL(ctx.url);
235
243
  const trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'fbclid', 'gclid', 'ref'];
@@ -247,7 +255,7 @@ export const crawlRules = [
247
255
  }
248
256
  catch {
249
257
  }
250
- return null;
258
+ return createResult({ id: 'crawl-url-parameters', name: 'URL Parameters', category: 'technical', severity: 'info' }, 'info', 'Not applicable (no tracking parameters detected or canonical is present)', { recommendation: 'This rule checks for tracking parameters without canonical tags' });
251
259
  },
252
260
  },
253
261
  {
@@ -257,8 +265,9 @@ export const crawlRules = [
257
265
  severity: 'info',
258
266
  description: 'Paginated content should use proper rel attributes',
259
267
  check: (ctx) => {
260
- if (!ctx.isPaginatedPage)
261
- return null;
268
+ if (!ctx.isPaginatedPage) {
269
+ return createResult({ id: 'crawl-pagination-rel', name: 'Pagination Links', category: 'technical', severity: 'info' }, 'info', 'Not applicable (page is not paginated)', { recommendation: 'This rule checks pagination links on paginated pages only' });
270
+ }
262
271
  const hasPrevNext = ctx.hasRelPrev || ctx.hasRelNext;
263
272
  if (!hasPrevNext) {
264
273
  return createResult({ id: 'crawl-pagination-rel', name: 'Pagination Links', category: 'technical', severity: 'info' }, 'info', 'Paginated page missing rel="prev/next" (deprecated but still useful)', {
@@ -280,8 +289,9 @@ export const crawlRules = [
280
289
  severity: 'info',
281
290
  description: 'Check for noarchive and nocache directives',
282
291
  check: (ctx) => {
283
- if (!ctx.metaRobots)
284
- return null;
292
+ if (!ctx.metaRobots) {
293
+ return createResult({ id: 'crawl-noarchive', name: 'Cache Directives', category: 'technical', severity: 'info' }, 'info', 'Not applicable (robots meta tag not present)', { recommendation: 'This rule checks for cache directives when robots meta tags are present' });
294
+ }
285
295
  const directives = ctx.metaRobots.join(', ').toLowerCase();
286
296
  const hasNoarchive = directives.includes('noarchive');
287
297
  const hasNocache = directives.includes('nocache');
@@ -301,7 +311,7 @@ export const crawlRules = [
301
311
  },
302
312
  });
303
313
  }
304
- return null;
314
+ return createResult({ id: 'crawl-noarchive', name: 'Cache Directives', category: 'technical', severity: 'info' }, 'info', 'Not applicable (no cache restriction directives present)', { recommendation: 'This rule checks for cache restriction directives in robots meta tags' });
305
315
  },
306
316
  },
307
317
  {
@@ -311,8 +321,9 @@ export const crawlRules = [
311
321
  severity: 'info',
312
322
  description: 'Website should have a robots.txt file',
313
323
  check: (ctx) => {
314
- if (ctx.robotsTxtExists === undefined)
315
- return null;
324
+ if (ctx.robotsTxtExists === undefined) {
325
+ return createResult({ id: 'robots-txt-exists', name: 'robots.txt Exists', category: 'crawlability', severity: 'info' }, 'info', 'Not applicable (robots.txt status unavailable)', { recommendation: 'This rule checks for robots.txt file when robots.txt information is available' });
326
+ }
316
327
  if (!ctx.robotsTxtExists) {
317
328
  return createResult({ id: 'robots-txt-exists', name: 'robots.txt Exists', category: 'crawlability', severity: 'info' }, 'info', 'No robots.txt file found', {
318
329
  recommendation: 'Create a robots.txt file to control search engine crawling',
@@ -333,8 +344,9 @@ export const crawlRules = [
333
344
  severity: 'warning',
334
345
  description: 'robots.txt should reference sitemap.xml location',
335
346
  check: (ctx) => {
336
- if (ctx.robotsTxtHasSitemap === undefined)
337
- return null;
347
+ if (ctx.robotsTxtHasSitemap === undefined) {
348
+ return createResult({ id: 'sitemap-in-robots', name: 'Sitemap Reference in robots.txt', category: 'crawlability', severity: 'warning' }, 'info', 'Not applicable (robots.txt sitemap reference status unavailable)', { recommendation: 'This rule checks for sitemap references in robots.txt when robots.txt information is available' });
349
+ }
338
350
  if (!ctx.robotsTxtHasSitemap) {
339
351
  return createResult({ id: 'sitemap-in-robots', name: 'Sitemap Reference in robots.txt', category: 'crawlability', severity: 'warning' }, 'warn', 'robots.txt does not reference sitemap.xml', {
340
352
  recommendation: 'Add sitemap location to robots.txt',
@@ -355,8 +367,9 @@ export const crawlRules = [
355
367
  severity: 'warning',
356
368
  description: 'CSS/JS resources should not be blocked by robots.txt',
357
369
  check: (ctx) => {
358
- if (ctx.blockedResources === undefined)
359
- return null;
370
+ if (ctx.blockedResources === undefined) {
371
+ return createResult({ id: 'blocked-resources', name: 'Blocked Resources', category: 'crawlability', severity: 'warning' }, 'info', 'Not applicable (blocked resources information unavailable)', { recommendation: 'This rule checks for blocked resources when resource blocking information is available' });
372
+ }
360
373
  if (ctx.blockedResources > 0) {
361
374
  return createResult({ id: 'blocked-resources', name: 'Blocked Resources', category: 'crawlability', severity: 'warning' }, 'warn', `${ctx.blockedResources} resources blocked by robots.txt`, {
362
375
  value: ctx.blockedResources,
@@ -369,7 +382,7 @@ export const crawlRules = [
369
382
  }
370
383
  });
371
384
  }
372
- return null;
385
+ return createResult({ id: 'blocked-resources', name: 'Blocked Resources', category: 'crawlability', severity: 'warning' }, 'info', 'Not applicable (no resources blocked by robots.txt)', { recommendation: 'This rule checks for CSS/JS resources blocked by robots.txt' });
373
386
  },
374
387
  },
375
388
  {
@@ -379,8 +392,9 @@ export const crawlRules = [
379
392
  severity: 'warning',
380
393
  description: 'X-Robots-Tag should not block important pages',
381
394
  check: (ctx) => {
382
- if (!ctx.xRobotsTag)
383
- return null;
395
+ if (!ctx.xRobotsTag) {
396
+ return createResult({ id: 'x-robots-tag-noindex', name: 'X-Robots-Tag Noindex', category: 'crawlability', severity: 'warning' }, 'info', 'Not applicable (X-Robots-Tag header not present)', { recommendation: 'This rule checks X-Robots-Tag for noindex directives when the header is present' });
397
+ }
384
398
  const tag = ctx.xRobotsTag.toLowerCase();
385
399
  if (tag.includes('noindex')) {
386
400
  return createResult({ id: 'x-robots-tag-noindex', name: 'X-Robots-Tag Noindex', category: 'crawlability', severity: 'warning' }, 'warn', 'Page blocked by X-Robots-Tag: noindex', {
@@ -392,7 +406,7 @@ export const crawlRules = [
392
406
  }
393
407
  });
394
408
  }
395
- return null;
409
+ return createResult({ id: 'x-robots-tag-noindex', name: 'X-Robots-Tag Noindex', category: 'crawlability', severity: 'warning' }, 'info', 'Not applicable (X-Robots-Tag does not contain noindex)', { recommendation: 'This rule checks for noindex in X-Robots-Tag header' });
396
410
  },
397
411
  },
398
412
  {
@@ -402,8 +416,9 @@ export const crawlRules = [
402
416
  severity: 'info',
403
417
  description: 'External resources blocked by robots.txt may affect rendering',
404
418
  check: (ctx) => {
405
- if (ctx.blockedExternalResources === undefined)
406
- return null;
419
+ if (ctx.blockedExternalResources === undefined) {
420
+ return createResult({ id: 'blocked-external-resources', name: 'Blocked External Resources', category: 'crawlability', severity: 'info' }, 'info', 'Not applicable (blocked external resources information unavailable)', { recommendation: 'This rule checks for blocked external resources when resource blocking information is available' });
421
+ }
407
422
  if (ctx.blockedExternalResources > 0) {
408
423
  return createResult({ id: 'blocked-external-resources', name: 'Blocked External Resources', category: 'crawlability', severity: 'info' }, 'info', `${ctx.blockedExternalResources} external resources blocked by robots.txt`, {
409
424
  value: ctx.blockedExternalResources,
@@ -414,7 +429,7 @@ export const crawlRules = [
414
429
  }
415
430
  });
416
431
  }
417
- return null;
432
+ return createResult({ id: 'blocked-external-resources', name: 'Blocked External Resources', category: 'crawlability', severity: 'info' }, 'info', 'Not applicable (no external resources blocked by robots.txt)', { recommendation: 'This rule checks for external resources blocked by robots.txt' });
418
433
  },
419
434
  },
420
435
  ];
@@ -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,34 @@ 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
+ evidence: {
19
+ found: 'No JSON-LD structured data',
20
+ expected: 'Product schema with name, image, price, availability',
21
+ impact: 'Missing product schema prevents rich snippets in search results',
22
+ example: `<script type="application/ld+json">
23
+ {
24
+ "@context": "https://schema.org",
25
+ "@type": "Product",
26
+ "name": "Product Name",
27
+ "image": "https://example.com/image.jpg",
28
+ "offers": {
29
+ "@type": "Offer",
30
+ "price": "99.99",
31
+ "priceCurrency": "USD"
32
+ }
33
+ }
34
+ </script>`,
35
+ },
36
+ });
37
+ }
14
38
  const hasProduct = ctx.jsonLdTypes.includes('Product');
15
39
  if (!hasProduct) {
16
40
  return createResult({ id: 'ecommerce-product-schema', name: 'Product Schema', category: 'structured-data', severity: 'warning' }, 'warn', 'Product page missing Product schema', {
@@ -42,8 +66,11 @@ export const ecommerceRules = [
42
66
  severity: 'warning',
43
67
  description: 'Product schema should include price information',
44
68
  check: (ctx) => {
45
- if (!ctx.productSchema)
46
- return null;
69
+ if (!ctx.productSchema) {
70
+ return createResult({ id: 'ecommerce-product-price', name: 'Product Price', category: 'structured-data', severity: 'warning' }, 'info', 'Not applicable (no Product schema)', {
71
+ recommendation: 'Add Product schema first, then include price information',
72
+ });
73
+ }
47
74
  const hasPrice = ctx.productSchema.offers?.price !== undefined ||
48
75
  ctx.productSchema.offers?.lowPrice !== undefined;
49
76
  const hasCurrency = ctx.productSchema.offers?.priceCurrency !== undefined;
@@ -51,8 +78,10 @@ export const ecommerceRules = [
51
78
  return createResult({ id: 'ecommerce-product-price', name: 'Product Price', category: 'structured-data', severity: 'warning' }, 'warn', 'Product schema missing price', {
52
79
  recommendation: 'Add price to Product offers for price display in search results',
53
80
  evidence: {
81
+ found: 'Product offers without price',
54
82
  expected: 'offers.price or offers.lowPrice with priceCurrency',
55
83
  impact: 'Products without price may not show in Google Shopping results',
84
+ example: '"offers": {\n "@type": "Offer",\n "price": "99.99",\n "priceCurrency": "USD"\n}',
56
85
  },
57
86
  });
58
87
  }
@@ -62,6 +91,8 @@ export const ecommerceRules = [
62
91
  evidence: {
63
92
  found: `price: ${ctx.productSchema.offers?.price}`,
64
93
  expected: 'priceCurrency: "USD" or similar ISO 4217 code',
94
+ impact: 'Currency is required for Google Shopping and price display in search results',
95
+ example: '"priceCurrency": "USD"',
65
96
  },
66
97
  });
67
98
  }
@@ -75,8 +106,11 @@ export const ecommerceRules = [
75
106
  severity: 'info',
76
107
  description: 'Product schema should include availability status',
77
108
  check: (ctx) => {
78
- if (!ctx.productSchema?.offers)
79
- return null;
109
+ if (!ctx.productSchema?.offers) {
110
+ return createResult({ id: 'ecommerce-product-availability', name: 'Product Availability', category: 'structured-data', severity: 'info' }, 'info', 'Not applicable (no Product offers schema)', {
111
+ recommendation: 'Add Product schema with offers to include availability',
112
+ });
113
+ }
80
114
  const availability = ctx.productSchema.offers.availability;
81
115
  if (!availability) {
82
116
  return createResult({ id: 'ecommerce-product-availability', name: 'Product Availability', category: 'structured-data', severity: 'info' }, 'info', 'Product schema missing availability', {
@@ -99,15 +133,20 @@ export const ecommerceRules = [
99
133
  severity: 'warning',
100
134
  description: 'Product schema should include high-quality images',
101
135
  check: (ctx) => {
102
- if (!ctx.productSchema)
103
- return null;
136
+ if (!ctx.productSchema) {
137
+ return createResult({ id: 'ecommerce-product-image', name: 'Product Image', category: 'structured-data', severity: 'warning' }, 'info', 'Not applicable (no Product schema)', {
138
+ recommendation: 'Add Product schema first, then include images',
139
+ });
140
+ }
104
141
  const hasImage = ctx.productSchema.image !== undefined;
105
142
  if (!hasImage) {
106
143
  return createResult({ id: 'ecommerce-product-image', name: 'Product Image', category: 'structured-data', severity: 'warning' }, 'warn', 'Product schema missing image', {
107
144
  recommendation: 'Add product images for visual search results',
108
145
  evidence: {
146
+ found: 'Product schema without image',
109
147
  expected: 'At least one high-quality product image',
110
148
  impact: 'Products without images are less likely to appear in image search and shopping results',
149
+ example: '"image": "https://example.com/product-image.jpg"',
111
150
  },
112
151
  });
113
152
  }
@@ -124,8 +163,11 @@ export const ecommerceRules = [
124
163
  severity: 'info',
125
164
  description: 'Product schema can include aggregate ratings for star snippets',
126
165
  check: (ctx) => {
127
- if (!ctx.productSchema)
128
- return null;
166
+ if (!ctx.productSchema) {
167
+ return createResult({ id: 'ecommerce-product-reviews', name: 'Product Reviews', category: 'structured-data', severity: 'info' }, 'info', 'Not applicable (no Product schema)', {
168
+ recommendation: 'Add Product schema first, then include reviews/ratings',
169
+ });
170
+ }
129
171
  const hasRating = ctx.productSchema.aggregateRating !== undefined;
130
172
  const hasReviews = ctx.productSchema.review !== undefined;
131
173
  if (!hasRating && !hasReviews) {
@@ -155,8 +197,11 @@ export const ecommerceRules = [
155
197
  severity: 'info',
156
198
  description: 'Product schema should include brand information',
157
199
  check: (ctx) => {
158
- if (!ctx.productSchema)
159
- return null;
200
+ if (!ctx.productSchema) {
201
+ return createResult({ id: 'ecommerce-product-brand', name: 'Product Brand', category: 'structured-data', severity: 'info' }, 'info', 'Not applicable (no Product schema)', {
202
+ recommendation: 'Add Product schema first, then include brand',
203
+ });
204
+ }
160
205
  const hasBrand = ctx.productSchema.brand !== undefined;
161
206
  if (!hasBrand) {
162
207
  return createResult({ id: 'ecommerce-product-brand', name: 'Product Brand', category: 'structured-data', severity: 'info' }, 'info', 'Product schema missing brand', {
@@ -180,8 +225,11 @@ export const ecommerceRules = [
180
225
  severity: 'info',
181
226
  description: 'Product schema should include unique identifiers (SKU, GTIN, MPN)',
182
227
  check: (ctx) => {
183
- if (!ctx.productSchema)
184
- return null;
228
+ if (!ctx.productSchema) {
229
+ return createResult({ id: 'ecommerce-product-sku', name: 'Product Identifiers', category: 'structured-data', severity: 'info' }, 'info', 'Not applicable (no Product schema)', {
230
+ recommendation: 'Add Product schema first, then include identifiers',
231
+ });
232
+ }
185
233
  const hasSku = ctx.productSchema.sku !== undefined;
186
234
  const hasGtin = ctx.productSchema.gtin !== undefined ||
187
235
  ctx.productSchema.gtin13 !== undefined ||
@@ -215,21 +263,25 @@ export const ecommerceRules = [
215
263
  severity: 'info',
216
264
  description: 'Time-sensitive offers should have valid date ranges',
217
265
  check: (ctx) => {
218
- if (!ctx.productSchema?.offers)
219
- return null;
266
+ if (!ctx.productSchema?.offers) {
267
+ return createResult({ id: 'ecommerce-offer-valid-dates', name: 'Offer Valid Dates', category: 'structured-data', severity: 'info' }, 'info', 'Not applicable (no Product offers schema)', {
268
+ recommendation: 'Add Product schema with offers to enable date validation',
269
+ });
270
+ }
220
271
  const offers = ctx.productSchema.offers;
221
272
  const priceValidUntil = offers.priceValidUntil;
222
- const validFrom = offers.validFrom;
223
273
  const validThrough = offers.validThrough;
224
274
  if (priceValidUntil) {
225
275
  const endDate = new Date(priceValidUntil);
226
276
  const now = new Date();
227
277
  if (endDate < now) {
228
278
  return createResult({ id: 'ecommerce-offer-valid-dates', name: 'Offer Valid Dates', category: 'structured-data', severity: 'info' }, 'warn', 'Offer priceValidUntil date has passed', {
279
+ recommendation: 'Update or remove expired priceValidUntil date',
229
280
  evidence: {
230
281
  found: priceValidUntil,
231
- issue: 'Expired offer dates should be updated or removed',
282
+ expected: 'Future date or no priceValidUntil',
232
283
  impact: 'Expired dates may cause Google to distrust your pricing data',
284
+ example: `"priceValidUntil": "${new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString().split('T')[0]}"`,
233
285
  },
234
286
  });
235
287
  }
@@ -239,14 +291,22 @@ export const ecommerceRules = [
239
291
  const now = new Date();
240
292
  if (endDate < now) {
241
293
  return createResult({ id: 'ecommerce-offer-valid-dates', name: 'Offer Valid Dates', category: 'structured-data', severity: 'info' }, 'warn', 'Offer validThrough date has passed', {
294
+ recommendation: 'Update or remove expired validThrough date',
242
295
  evidence: {
243
296
  found: validThrough,
244
- issue: 'Expired validity dates should be updated',
297
+ expected: 'Future date or no validThrough',
298
+ impact: 'Expired validity dates may prevent offer from showing in search results',
299
+ example: `"validThrough": "${new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString().split('T')[0]}"`,
245
300
  },
246
301
  });
247
302
  }
248
303
  }
249
- return null;
304
+ if (priceValidUntil || validThrough) {
305
+ return createResult({ id: 'ecommerce-offer-valid-dates', name: 'Offer Valid Dates', category: 'structured-data', severity: 'info' }, 'pass', 'Offer dates are valid');
306
+ }
307
+ return createResult({ id: 'ecommerce-offer-valid-dates', name: 'Offer Valid Dates', category: 'structured-data', severity: 'info' }, 'info', 'No offer date constraints specified', {
308
+ recommendation: 'Consider adding priceValidUntil for time-limited offers',
309
+ });
250
310
  },
251
311
  },
252
312
  ];