recker 1.0.43 → 1.0.44

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (459) hide show
  1. package/README.md +47 -0
  2. package/dist/bin/recker-linux-x64 +0 -0
  3. package/dist/bin/recker-macos-x64 +0 -0
  4. package/dist/bin/recker-win-x64.exe +0 -0
  5. package/dist/bin/rek.cjs +85152 -100207
  6. package/dist/browser/ai/adaptive-timeout.d.ts +50 -0
  7. package/dist/browser/ai/adaptive-timeout.js +208 -0
  8. package/dist/browser/ai/client.d.ts +22 -0
  9. package/dist/browser/ai/client.js +294 -0
  10. package/dist/browser/ai/index.d.ts +14 -0
  11. package/dist/browser/ai/index.js +11 -0
  12. package/dist/browser/ai/providers/anthropic.d.ts +63 -0
  13. package/dist/browser/ai/providers/anthropic.js +370 -0
  14. package/dist/browser/ai/providers/base.d.ts +48 -0
  15. package/dist/browser/ai/providers/base.js +150 -0
  16. package/dist/browser/ai/providers/google.d.ts +59 -0
  17. package/dist/browser/ai/providers/google.js +305 -0
  18. package/dist/browser/ai/providers/ollama.d.ts +44 -0
  19. package/dist/browser/ai/providers/ollama.js +240 -0
  20. package/dist/browser/ai/providers/openai.d.ts +64 -0
  21. package/dist/browser/ai/providers/openai.js +298 -0
  22. package/dist/browser/ai/rate-limiter.d.ts +43 -0
  23. package/dist/browser/ai/rate-limiter.js +215 -0
  24. package/dist/browser/ai/vector/index.d.ts +2 -0
  25. package/dist/browser/ai/vector/index.js +2 -0
  26. package/dist/browser/ai/vector/similarity.d.ts +2 -0
  27. package/dist/browser/ai/vector/similarity.js +27 -0
  28. package/dist/browser/ai/vector/store.d.ts +27 -0
  29. package/dist/browser/ai/vector/store.js +82 -0
  30. package/dist/browser/browser/cache.d.ts +2 -40
  31. package/dist/browser/browser/cache.js +2 -199
  32. package/dist/browser/browser/index.d.ts +8 -0
  33. package/dist/browser/browser/index.js +8 -0
  34. package/dist/browser/browser/recker.d.ts +8 -1
  35. package/dist/browser/browser/recker.js +8 -2
  36. package/dist/browser/cache/indexed-db.d.ts +10 -0
  37. package/dist/browser/cache/indexed-db.js +88 -0
  38. package/dist/browser/cache/service-worker-cache.d.ts +18 -0
  39. package/dist/browser/cache/service-worker-cache.js +103 -0
  40. package/dist/browser/cache.d.ts +2 -40
  41. package/dist/browser/cache.js +2 -199
  42. package/dist/browser/constants/user-agents.d.ts +7 -0
  43. package/dist/browser/constants/user-agents.js +7 -0
  44. package/dist/browser/core/client.d.ts +2 -0
  45. package/dist/browser/core/client.js +19 -1
  46. package/dist/browser/index.d.ts +8 -0
  47. package/dist/browser/index.js +8 -0
  48. package/dist/browser/plugins/har-recorder.d.ts +40 -0
  49. package/dist/browser/plugins/har-recorder.js +120 -0
  50. package/dist/browser/plugins/network-simulation.d.ts +7 -0
  51. package/dist/browser/plugins/network-simulation.js +13 -0
  52. package/dist/browser/presets/android.d.ts +2 -0
  53. package/dist/browser/presets/android.js +16 -0
  54. package/dist/browser/presets/anthropic.d.ts +8 -0
  55. package/dist/browser/presets/anthropic.js +27 -0
  56. package/dist/browser/presets/aws.d.ts +19 -0
  57. package/dist/browser/presets/aws.js +68 -0
  58. package/dist/browser/presets/azure-openai.d.ts +10 -0
  59. package/dist/browser/presets/azure-openai.js +35 -0
  60. package/dist/browser/presets/azure.d.ts +41 -0
  61. package/dist/browser/presets/azure.js +104 -0
  62. package/dist/browser/presets/chaturbate.d.ts +2 -0
  63. package/dist/browser/presets/chaturbate.js +17 -0
  64. package/dist/browser/presets/cloudflare.d.ts +12 -0
  65. package/dist/browser/presets/cloudflare.js +39 -0
  66. package/dist/browser/presets/cohere.d.ts +7 -0
  67. package/dist/browser/presets/cohere.js +22 -0
  68. package/dist/browser/presets/deepseek.d.ts +7 -0
  69. package/dist/browser/presets/deepseek.js +22 -0
  70. package/dist/browser/presets/digitalocean.d.ts +5 -0
  71. package/dist/browser/presets/digitalocean.js +16 -0
  72. package/dist/browser/presets/discord.d.ts +6 -0
  73. package/dist/browser/presets/discord.js +17 -0
  74. package/dist/browser/presets/elevenlabs.d.ts +6 -0
  75. package/dist/browser/presets/elevenlabs.js +20 -0
  76. package/dist/browser/presets/enhancers.d.ts +20 -0
  77. package/dist/browser/presets/enhancers.js +85 -0
  78. package/dist/browser/presets/fireworks.d.ts +7 -0
  79. package/dist/browser/presets/fireworks.js +22 -0
  80. package/dist/browser/presets/gcp.d.ts +34 -0
  81. package/dist/browser/presets/gcp.js +91 -0
  82. package/dist/browser/presets/gemini.d.ts +7 -0
  83. package/dist/browser/presets/gemini.js +23 -0
  84. package/dist/browser/presets/github.d.ts +6 -0
  85. package/dist/browser/presets/github.js +17 -0
  86. package/dist/browser/presets/gitlab.d.ts +6 -0
  87. package/dist/browser/presets/gitlab.js +16 -0
  88. package/dist/browser/presets/groq.d.ts +7 -0
  89. package/dist/browser/presets/groq.js +22 -0
  90. package/dist/browser/presets/hubspot.d.ts +9 -0
  91. package/dist/browser/presets/hubspot.js +28 -0
  92. package/dist/browser/presets/huggingface.d.ts +7 -0
  93. package/dist/browser/presets/huggingface.js +23 -0
  94. package/dist/browser/presets/index.d.ts +47 -0
  95. package/dist/browser/presets/index.js +47 -0
  96. package/dist/browser/presets/ios.d.ts +2 -0
  97. package/dist/browser/presets/ios.js +13 -0
  98. package/dist/browser/presets/linear.d.ts +5 -0
  99. package/dist/browser/presets/linear.js +16 -0
  100. package/dist/browser/presets/mailgun.d.ts +7 -0
  101. package/dist/browser/presets/mailgun.js +20 -0
  102. package/dist/browser/presets/meta.d.ts +10 -0
  103. package/dist/browser/presets/meta.js +33 -0
  104. package/dist/browser/presets/mistral.d.ts +7 -0
  105. package/dist/browser/presets/mistral.js +22 -0
  106. package/dist/browser/presets/notion.d.ts +6 -0
  107. package/dist/browser/presets/notion.js +17 -0
  108. package/dist/browser/presets/openai.d.ts +9 -0
  109. package/dist/browser/presets/openai.js +30 -0
  110. package/dist/browser/presets/oracle.d.ts +19 -0
  111. package/dist/browser/presets/oracle.js +117 -0
  112. package/dist/browser/presets/perplexity.d.ts +7 -0
  113. package/dist/browser/presets/perplexity.js +22 -0
  114. package/dist/browser/presets/pinecone.d.ts +8 -0
  115. package/dist/browser/presets/pinecone.js +42 -0
  116. package/dist/browser/presets/registry.d.ts +23 -0
  117. package/dist/browser/presets/registry.js +519 -0
  118. package/dist/browser/presets/replicate.d.ts +7 -0
  119. package/dist/browser/presets/replicate.js +23 -0
  120. package/dist/browser/presets/sendgrid.d.ts +6 -0
  121. package/dist/browser/presets/sendgrid.js +20 -0
  122. package/dist/browser/presets/sentry.d.ts +11 -0
  123. package/dist/browser/presets/sentry.js +48 -0
  124. package/dist/browser/presets/sinch.d.ts +9 -0
  125. package/dist/browser/presets/sinch.js +39 -0
  126. package/dist/browser/presets/slack.d.ts +5 -0
  127. package/dist/browser/presets/slack.js +16 -0
  128. package/dist/browser/presets/square.d.ts +10 -0
  129. package/dist/browser/presets/square.js +33 -0
  130. package/dist/browser/presets/stripe.d.ts +7 -0
  131. package/dist/browser/presets/stripe.js +23 -0
  132. package/dist/browser/presets/supabase.d.ts +6 -0
  133. package/dist/browser/presets/supabase.js +18 -0
  134. package/dist/browser/presets/tiktok.d.ts +10 -0
  135. package/dist/browser/presets/tiktok.js +38 -0
  136. package/dist/browser/presets/together.d.ts +7 -0
  137. package/dist/browser/presets/together.js +22 -0
  138. package/dist/browser/presets/twilio.d.ts +6 -0
  139. package/dist/browser/presets/twilio.js +17 -0
  140. package/dist/browser/presets/vercel.d.ts +6 -0
  141. package/dist/browser/presets/vercel.js +23 -0
  142. package/dist/browser/presets/vultr.d.ts +5 -0
  143. package/dist/browser/presets/vultr.js +16 -0
  144. package/dist/browser/presets/xai.d.ts +8 -0
  145. package/dist/browser/presets/xai.js +23 -0
  146. package/dist/browser/presets/youtube.d.ts +5 -0
  147. package/dist/browser/presets/youtube.js +20 -0
  148. package/dist/browser/recker.d.ts +8 -1
  149. package/dist/browser/recker.js +8 -2
  150. package/dist/browser/scrape/document.d.ts +5 -4
  151. package/dist/browser/scrape/document.js +89 -76
  152. package/dist/browser/scrape/element.d.ts +10 -8
  153. package/dist/browser/scrape/element.js +295 -81
  154. package/dist/browser/scrape/extractors.d.ts +11 -11
  155. package/dist/browser/scrape/extractors.js +145 -113
  156. package/dist/browser/scrape/parser/back.d.ts +1 -0
  157. package/dist/browser/scrape/parser/back.js +3 -0
  158. package/dist/browser/scrape/parser/index.d.ts +20 -0
  159. package/dist/browser/scrape/parser/index.js +19 -0
  160. package/dist/browser/scrape/parser/matcher.d.ts +30 -0
  161. package/dist/browser/scrape/parser/matcher.js +99 -0
  162. package/dist/browser/scrape/parser/nodes/comment.d.ts +12 -0
  163. package/dist/browser/scrape/parser/nodes/comment.js +21 -0
  164. package/dist/browser/scrape/parser/nodes/html.d.ts +110 -0
  165. package/dist/browser/scrape/parser/nodes/html.js +978 -0
  166. package/dist/browser/scrape/parser/nodes/node.d.ts +18 -0
  167. package/dist/browser/scrape/parser/nodes/node.js +31 -0
  168. package/dist/browser/scrape/parser/nodes/text.d.ts +14 -0
  169. package/dist/browser/scrape/parser/nodes/text.js +30 -0
  170. package/dist/browser/scrape/parser/nodes/type.d.ts +6 -0
  171. package/dist/browser/scrape/parser/nodes/type.js +7 -0
  172. package/dist/browser/scrape/parser/parse.d.ts +1 -0
  173. package/dist/browser/scrape/parser/parse.js +1 -0
  174. package/dist/browser/scrape/parser/valid.d.ts +2 -0
  175. package/dist/browser/scrape/parser/valid.js +5 -0
  176. package/dist/browser/scrape/parser/void-tag.d.ts +7 -0
  177. package/dist/browser/scrape/parser/void-tag.js +43 -0
  178. package/dist/browser/scrape/types.d.ts +7 -0
  179. package/dist/browser/seo/analyzer.d.ts +59 -0
  180. package/dist/browser/seo/analyzer.js +1399 -0
  181. package/dist/browser/seo/keywords.d.ts +16 -0
  182. package/dist/browser/seo/keywords.js +55 -0
  183. package/dist/browser/seo/rules/accessibility.d.ts +2 -0
  184. package/dist/browser/seo/rules/accessibility.js +733 -0
  185. package/dist/browser/seo/rules/ai-search.d.ts +2 -0
  186. package/dist/browser/seo/rules/ai-search.js +436 -0
  187. package/dist/browser/seo/rules/analytics.d.ts +2 -0
  188. package/dist/browser/seo/rules/analytics.js +306 -0
  189. package/dist/browser/seo/rules/best-practices.d.ts +2 -0
  190. package/dist/browser/seo/rules/best-practices.js +195 -0
  191. package/dist/browser/seo/rules/canonical.d.ts +12 -0
  192. package/dist/browser/seo/rules/canonical.js +270 -0
  193. package/dist/browser/seo/rules/content.d.ts +2 -0
  194. package/dist/browser/seo/rules/content.js +522 -0
  195. package/dist/browser/seo/rules/crawl.d.ts +2 -0
  196. package/dist/browser/seo/rules/crawl.js +435 -0
  197. package/dist/browser/seo/rules/cwv.d.ts +2 -0
  198. package/dist/browser/seo/rules/cwv.js +248 -0
  199. package/dist/browser/seo/rules/ecommerce.d.ts +2 -0
  200. package/dist/browser/seo/rules/ecommerce.js +312 -0
  201. package/dist/browser/seo/rules/i18n.d.ts +2 -0
  202. package/dist/browser/seo/rules/i18n.js +288 -0
  203. package/dist/browser/seo/rules/images.d.ts +2 -0
  204. package/dist/browser/seo/rules/images.js +255 -0
  205. package/dist/browser/seo/rules/index.d.ts +52 -0
  206. package/dist/browser/seo/rules/index.js +159 -0
  207. package/dist/browser/seo/rules/internal-linking.d.ts +2 -0
  208. package/dist/browser/seo/rules/internal-linking.js +394 -0
  209. package/dist/browser/seo/rules/links.d.ts +2 -0
  210. package/dist/browser/seo/rules/links.js +498 -0
  211. package/dist/browser/seo/rules/local.d.ts +2 -0
  212. package/dist/browser/seo/rules/local.js +289 -0
  213. package/dist/browser/seo/rules/meta.d.ts +2 -0
  214. package/dist/browser/seo/rules/meta.js +805 -0
  215. package/dist/browser/seo/rules/mobile.d.ts +2 -0
  216. package/dist/browser/seo/rules/mobile.js +161 -0
  217. package/dist/browser/seo/rules/performance.d.ts +2 -0
  218. package/dist/browser/seo/rules/performance.js +738 -0
  219. package/dist/browser/seo/rules/pwa.d.ts +2 -0
  220. package/dist/browser/seo/rules/pwa.js +299 -0
  221. package/dist/browser/seo/rules/readability.d.ts +2 -0
  222. package/dist/browser/seo/rules/readability.js +264 -0
  223. package/dist/browser/seo/rules/redirects.d.ts +16 -0
  224. package/dist/browser/seo/rules/redirects.js +199 -0
  225. package/dist/browser/seo/rules/resources.d.ts +2 -0
  226. package/dist/browser/seo/rules/resources.js +390 -0
  227. package/dist/browser/seo/rules/schema.d.ts +2 -0
  228. package/dist/browser/seo/rules/schema.js +379 -0
  229. package/dist/browser/seo/rules/security.d.ts +2 -0
  230. package/dist/browser/seo/rules/security.js +877 -0
  231. package/dist/browser/seo/rules/social.d.ts +2 -0
  232. package/dist/browser/seo/rules/social.js +603 -0
  233. package/dist/browser/seo/rules/structural.d.ts +2 -0
  234. package/dist/browser/seo/rules/structural.js +223 -0
  235. package/dist/browser/seo/rules/technical-advanced.d.ts +10 -0
  236. package/dist/browser/seo/rules/technical-advanced.js +289 -0
  237. package/dist/browser/seo/rules/technical.d.ts +2 -0
  238. package/dist/browser/seo/rules/technical.js +480 -0
  239. package/dist/browser/seo/rules/thresholds.d.ts +196 -0
  240. package/dist/browser/seo/rules/thresholds.js +118 -0
  241. package/dist/browser/seo/rules/types.d.ts +498 -0
  242. package/dist/browser/seo/rules/types.js +11 -0
  243. package/dist/browser/seo/types.d.ts +211 -0
  244. package/dist/browser/seo/types.js +1 -0
  245. package/dist/browser/transport/curl.d.ts +4 -0
  246. package/dist/browser/transport/curl.js +101 -0
  247. package/dist/browser/transport/undici.js +1 -2
  248. package/dist/browser/transport/worker.d.ts +18 -0
  249. package/dist/browser/transport/worker.js +278 -0
  250. package/dist/browser/types/index.d.ts +4 -1
  251. package/dist/browser/utils/binary-manager.d.ts +4 -0
  252. package/dist/browser/utils/binary-manager.js +72 -0
  253. package/dist/browser/utils/user-agent.js +2 -13
  254. package/dist/cache/indexed-db.d.ts +10 -0
  255. package/dist/cache/indexed-db.js +88 -0
  256. package/dist/cache/service-worker-cache.d.ts +18 -0
  257. package/dist/cache/service-worker-cache.js +103 -0
  258. package/dist/cli/commands/ai.d.ts +2 -0
  259. package/dist/cli/commands/ai.js +162 -0
  260. package/dist/cli/commands/bench.d.ts +2 -0
  261. package/dist/cli/commands/bench.js +51 -0
  262. package/dist/cli/commands/dns.d.ts +2 -0
  263. package/dist/cli/commands/dns.js +295 -0
  264. package/dist/cli/commands/har.d.ts +2 -0
  265. package/dist/cli/commands/har.js +171 -0
  266. package/dist/cli/commands/hls.d.ts +2 -0
  267. package/dist/cli/commands/hls.js +192 -0
  268. package/dist/cli/commands/network.d.ts +2 -0
  269. package/dist/cli/commands/network.js +288 -0
  270. package/dist/cli/commands/protocols.d.ts +2 -0
  271. package/dist/cli/commands/protocols.js +344 -0
  272. package/dist/cli/commands/scrape.d.ts +2 -0
  273. package/dist/cli/commands/scrape.js +176 -0
  274. package/dist/cli/commands/security.d.ts +2 -0
  275. package/dist/cli/commands/security.js +57 -0
  276. package/dist/cli/commands/seo.d.ts +2 -0
  277. package/dist/cli/commands/seo.js +125 -0
  278. package/dist/cli/commands/serve.d.ts +2 -0
  279. package/dist/cli/commands/serve.js +531 -0
  280. package/dist/cli/commands/spider.d.ts +3 -0
  281. package/dist/cli/commands/spider.js +456 -0
  282. package/dist/cli/commands/utils.d.ts +2 -0
  283. package/dist/cli/commands/utils.js +176 -0
  284. package/dist/cli/commands/vector.d.ts +2 -0
  285. package/dist/cli/commands/vector.js +158 -0
  286. package/dist/cli/handler.d.ts +2 -2
  287. package/dist/cli/handler.js +6 -6
  288. package/dist/cli/helpers.d.ts +7 -0
  289. package/dist/cli/helpers.js +128 -0
  290. package/dist/cli/index.js +96 -5228
  291. package/dist/cli/parser/help.d.ts +2 -0
  292. package/dist/cli/parser/help.js +52 -0
  293. package/dist/cli/parser/index.d.ts +3 -0
  294. package/dist/cli/parser/index.js +3 -0
  295. package/dist/cli/parser/parser.d.ts +4 -0
  296. package/dist/cli/parser/parser.js +146 -0
  297. package/dist/cli/parser/types.d.ts +41 -0
  298. package/dist/cli/parser/types.js +1 -0
  299. package/dist/cli/presets.d.ts +1 -1
  300. package/dist/cli/presets.js +1 -1
  301. package/dist/cli/router.d.ts +36 -0
  302. package/dist/cli/router.js +195 -0
  303. package/dist/cli/tui/ai-chat.js +1 -1
  304. package/dist/cli/tui/commands/context.d.ts +9 -0
  305. package/dist/cli/tui/commands/context.js +1 -0
  306. package/dist/cli/tui/commands/dns.d.ts +10 -0
  307. package/dist/cli/tui/commands/dns.js +461 -0
  308. package/dist/cli/tui/commands/hls.d.ts +2 -0
  309. package/dist/cli/tui/commands/hls.js +162 -0
  310. package/dist/cli/tui/commands/ip.d.ts +2 -0
  311. package/dist/cli/tui/commands/ip.js +45 -0
  312. package/dist/cli/tui/commands/network.d.ts +3 -0
  313. package/dist/cli/tui/commands/network.js +81 -0
  314. package/dist/cli/tui/commands/protocols.d.ts +6 -0
  315. package/dist/cli/tui/commands/protocols.js +531 -0
  316. package/dist/cli/tui/commands/security.d.ts +2 -0
  317. package/dist/cli/tui/commands/security.js +48 -0
  318. package/dist/cli/tui/commands/seo.d.ts +2 -0
  319. package/dist/cli/tui/commands/seo.js +74 -0
  320. package/dist/cli/tui/context.d.ts +12 -0
  321. package/dist/cli/tui/context.js +1 -0
  322. package/dist/cli/tui/shell.d.ts +11 -20
  323. package/dist/cli/tui/shell.js +216 -1873
  324. package/dist/constants/user-agents.d.ts +7 -0
  325. package/dist/constants/user-agents.js +7 -0
  326. package/dist/core/client.d.ts +2 -0
  327. package/dist/core/client.js +19 -1
  328. package/dist/index.d.ts +1 -0
  329. package/dist/index.js +1 -0
  330. package/dist/mcp/cli.js +2 -3
  331. package/dist/mcp/data/embeddings.json +1 -1
  332. package/dist/mcp/tools/network.js +298 -158
  333. package/dist/plugins/har-player.d.ts +23 -0
  334. package/dist/plugins/har-player.js +49 -0
  335. package/dist/plugins/har-recorder.d.ts +37 -3
  336. package/dist/plugins/har-recorder.js +116 -63
  337. package/dist/plugins/network-simulation.d.ts +7 -0
  338. package/dist/plugins/network-simulation.js +13 -0
  339. package/dist/presets/android.d.ts +2 -0
  340. package/dist/presets/android.js +16 -0
  341. package/dist/presets/chaturbate.d.ts +2 -0
  342. package/dist/presets/chaturbate.js +17 -0
  343. package/dist/presets/elevenlabs.d.ts +6 -0
  344. package/dist/presets/elevenlabs.js +20 -0
  345. package/dist/presets/enhancers.d.ts +20 -0
  346. package/dist/presets/enhancers.js +85 -0
  347. package/dist/presets/hubspot.d.ts +9 -0
  348. package/dist/presets/hubspot.js +28 -0
  349. package/dist/presets/index.d.ts +10 -0
  350. package/dist/presets/index.js +10 -0
  351. package/dist/presets/ios.d.ts +2 -0
  352. package/dist/presets/ios.js +13 -0
  353. package/dist/presets/pinecone.d.ts +8 -0
  354. package/dist/presets/pinecone.js +42 -0
  355. package/dist/presets/registry.js +60 -0
  356. package/dist/presets/sendgrid.d.ts +6 -0
  357. package/dist/presets/sendgrid.js +20 -0
  358. package/dist/presets/sentry.d.ts +11 -0
  359. package/dist/presets/sentry.js +48 -0
  360. package/dist/presets/square.d.ts +10 -0
  361. package/dist/presets/square.js +33 -0
  362. package/dist/recker.d.ts +3 -0
  363. package/dist/recker.js +4 -0
  364. package/dist/scrape/document.d.ts +5 -4
  365. package/dist/scrape/document.js +89 -76
  366. package/dist/scrape/element.d.ts +10 -8
  367. package/dist/scrape/element.js +295 -81
  368. package/dist/scrape/extractors.d.ts +11 -11
  369. package/dist/scrape/extractors.js +145 -113
  370. package/dist/scrape/index.d.ts +2 -0
  371. package/dist/scrape/index.js +1 -0
  372. package/dist/scrape/parser/back.d.ts +1 -0
  373. package/dist/scrape/parser/back.js +3 -0
  374. package/dist/scrape/parser/index.d.ts +20 -0
  375. package/dist/scrape/parser/index.js +19 -0
  376. package/dist/scrape/parser/matcher.d.ts +30 -0
  377. package/dist/scrape/parser/matcher.js +99 -0
  378. package/dist/scrape/parser/nodes/comment.d.ts +12 -0
  379. package/dist/scrape/parser/nodes/comment.js +21 -0
  380. package/dist/scrape/parser/nodes/html.d.ts +110 -0
  381. package/dist/scrape/parser/nodes/html.js +978 -0
  382. package/dist/scrape/parser/nodes/node.d.ts +18 -0
  383. package/dist/scrape/parser/nodes/node.js +31 -0
  384. package/dist/scrape/parser/nodes/text.d.ts +14 -0
  385. package/dist/scrape/parser/nodes/text.js +30 -0
  386. package/dist/scrape/parser/nodes/type.d.ts +6 -0
  387. package/dist/scrape/parser/nodes/type.js +7 -0
  388. package/dist/scrape/parser/parse.d.ts +1 -0
  389. package/dist/scrape/parser/parse.js +1 -0
  390. package/dist/scrape/parser/valid.d.ts +2 -0
  391. package/dist/scrape/parser/valid.js +5 -0
  392. package/dist/scrape/parser/void-tag.d.ts +7 -0
  393. package/dist/scrape/parser/void-tag.js +43 -0
  394. package/dist/scrape/spider.d.ts +19 -0
  395. package/dist/scrape/spider.js +28 -3
  396. package/dist/scrape/types.d.ts +7 -0
  397. package/dist/seo/analyzer.d.ts +15 -5
  398. package/dist/seo/analyzer.js +636 -175
  399. package/dist/seo/formatter.d.ts +16 -0
  400. package/dist/seo/formatter.js +228 -0
  401. package/dist/seo/index.d.ts +2 -0
  402. package/dist/seo/index.js +1 -0
  403. package/dist/seo/keywords.d.ts +16 -0
  404. package/dist/seo/keywords.js +55 -0
  405. package/dist/seo/rules/accessibility.js +96 -57
  406. package/dist/seo/rules/ai-search.js +44 -31
  407. package/dist/seo/rules/analytics.d.ts +2 -0
  408. package/dist/seo/rules/analytics.js +306 -0
  409. package/dist/seo/rules/best-practices.js +21 -14
  410. package/dist/seo/rules/canonical.js +53 -32
  411. package/dist/seo/rules/content.js +317 -31
  412. package/dist/seo/rules/crawl.js +55 -40
  413. package/dist/seo/rules/cwv.js +21 -15
  414. package/dist/seo/rules/ecommerce.js +82 -22
  415. package/dist/seo/rules/i18n.js +75 -36
  416. package/dist/seo/rules/images.js +109 -30
  417. package/dist/seo/rules/index.js +2 -0
  418. package/dist/seo/rules/internal-linking.js +58 -39
  419. package/dist/seo/rules/links.js +79 -52
  420. package/dist/seo/rules/local.js +49 -25
  421. package/dist/seo/rules/meta.js +339 -81
  422. package/dist/seo/rules/mobile.js +112 -2
  423. package/dist/seo/rules/performance.js +434 -66
  424. package/dist/seo/rules/pwa.js +36 -39
  425. package/dist/seo/rules/readability.js +31 -22
  426. package/dist/seo/rules/redirects.js +21 -15
  427. package/dist/seo/rules/resources.js +59 -42
  428. package/dist/seo/rules/schema.js +333 -8
  429. package/dist/seo/rules/security.js +142 -80
  430. package/dist/seo/rules/social.js +277 -47
  431. package/dist/seo/rules/structural.js +87 -19
  432. package/dist/seo/rules/technical-advanced.js +30 -24
  433. package/dist/seo/rules/technical.js +243 -42
  434. package/dist/seo/rules/types.d.ts +53 -1
  435. package/dist/seo/seo-spider.d.ts +22 -0
  436. package/dist/seo/seo-spider.js +77 -13
  437. package/dist/seo/types.d.ts +8 -1
  438. package/dist/seo/validators/llms-txt.js +19 -0
  439. package/dist/seo/validators/rss.d.ts +11 -0
  440. package/dist/seo/validators/rss.js +93 -0
  441. package/dist/seo/validators/sitemap.js +36 -26
  442. package/dist/transport/curl.d.ts +4 -0
  443. package/dist/transport/curl.js +101 -0
  444. package/dist/transport/udp.js +0 -1
  445. package/dist/transport/undici.js +1 -2
  446. package/dist/transport/worker.d.ts +18 -0
  447. package/dist/transport/worker.js +278 -0
  448. package/dist/types/index.d.ts +4 -1
  449. package/dist/utils/binary-manager.d.ts +4 -0
  450. package/dist/utils/binary-manager.js +72 -0
  451. package/dist/utils/optional-require.d.ts +7 -8
  452. package/dist/utils/optional-require.js +2 -21
  453. package/dist/utils/upload.d.ts +6 -0
  454. package/dist/utils/upload.js +11 -0
  455. package/dist/utils/user-agent.js +2 -13
  456. package/dist/version.js +1 -1
  457. package/package.json +12 -6
  458. package/dist/browser/utils/optional-require.d.ts +0 -19
  459. package/dist/browser/utils/optional-require.js +0 -105
@@ -0,0 +1,223 @@
1
+ import { createResult } from './types.js';
2
+ import { SEO_THRESHOLDS } from './thresholds.js';
3
+ export const structuralRules = [
4
+ {
5
+ id: 'h1-exists',
6
+ name: 'H1 Exists',
7
+ category: 'headings',
8
+ severity: 'error',
9
+ description: 'Page must have exactly one H1 heading',
10
+ check: (ctx) => {
11
+ if (ctx.h1Count === undefined) {
12
+ return createResult({ id: 'h1-exists', name: 'H1 Tag', category: 'headings', severity: 'error' }, 'info', 'Unable to check H1 (data unavailable)', { recommendation: 'Ensure HTML content is properly parsed' });
13
+ }
14
+ if (ctx.h1Count === 0) {
15
+ return createResult({ id: 'h1-exists', name: 'H1 Tag', category: 'headings', severity: 'error' }, 'fail', 'Missing H1 heading', {
16
+ recommendation: 'Add a single H1 heading. It should be the main headline and include your primary keyword.',
17
+ evidence: {
18
+ found: 'No H1 tags detected on the page',
19
+ expected: 'Exactly one <h1> tag',
20
+ impact: 'The H1 tag is the most important heading for SEO. It tells search engines and users what the main topic of the page is. Pages without an H1 have poor semantic structure and may rank lower.',
21
+ example: '<h1>Your Main Page Title Here</h1>'
22
+ }
23
+ });
24
+ }
25
+ if (ctx.h1Count > 1) {
26
+ return createResult({ id: 'h1-exists', name: 'H1 Tag', category: 'headings', severity: 'error' }, 'warn', `Multiple H1 tags (${ctx.h1Count} found)`, {
27
+ value: ctx.h1Count,
28
+ recommendation: 'Use only one H1 per page to clearly signal the main topic.',
29
+ evidence: {
30
+ found: `${ctx.h1Count} H1 tags on the page`,
31
+ expected: 'Exactly one <h1> tag',
32
+ impact: 'Multiple H1 tags dilute the main topic signal and confuse search engines about which heading is the primary page title. This can hurt topical relevance and rankings.',
33
+ example: '<h1>Main Title</h1>\n<!-- Use H2 for subtopics instead -->\n<h2>Subtopic One</h2>\n<h2>Subtopic Two</h2>'
34
+ }
35
+ });
36
+ }
37
+ return createResult({ id: 'h1-exists', name: 'H1 Tag', category: 'headings', severity: 'error' }, 'pass', 'Single H1 tag present');
38
+ },
39
+ },
40
+ {
41
+ id: 'h1-length',
42
+ name: 'H1 Length',
43
+ category: 'headings',
44
+ severity: 'warning',
45
+ description: 'H1 should be 20-70 characters',
46
+ check: (ctx) => {
47
+ if (!ctx.h1Text) {
48
+ return createResult({ id: 'h1-length', name: 'H1 Length', category: 'headings', severity: 'warning' }, 'info', 'No H1 text to analyze', { recommendation: 'First add an H1 heading to the page' });
49
+ }
50
+ const len = ctx.h1Length ?? ctx.h1Text.length;
51
+ const { minLength, maxLength } = SEO_THRESHOLDS.headings.h1;
52
+ if (len < minLength) {
53
+ return createResult({ id: 'h1-length', name: 'H1 Length', category: 'headings', severity: 'warning' }, 'warn', `H1 too short (${len} chars, min: ${minLength})`, {
54
+ value: len,
55
+ recommendation: `Expand H1 to at least ${minLength} characters`,
56
+ evidence: {
57
+ found: `H1 with ${len} characters: "${ctx.h1Text}"`,
58
+ expected: `${minLength}-${maxLength} characters`,
59
+ impact: 'Very short H1 tags may not provide enough context for search engines and users to understand the page topic. Descriptive H1s improve relevance signals and click-through rates.',
60
+ example: '<h1>Complete Guide to SEO Best Practices</h1>'
61
+ }
62
+ });
63
+ }
64
+ if (len > maxLength) {
65
+ return createResult({ id: 'h1-length', name: 'H1 Length', category: 'headings', severity: 'warning' }, 'warn', `H1 too long (${len} chars, max: ${maxLength})`, {
66
+ value: len,
67
+ recommendation: `Shorten H1 to under ${maxLength} characters`,
68
+ evidence: {
69
+ found: `H1 with ${len} characters: "${ctx.h1Text}"`,
70
+ expected: `${minLength}-${maxLength} characters`,
71
+ impact: 'Overly long H1 tags can be truncated in search results and may appear unfocused. Concise H1s are more scannable and effective for both users and search engines.',
72
+ example: '<h1>SEO Guide: On-Page Optimization Tips</h1>'
73
+ }
74
+ });
75
+ }
76
+ return createResult({ id: 'h1-length', name: 'H1 Length', category: 'headings', severity: 'warning' }, 'pass', `H1 length OK (${len} chars)`, { value: len });
77
+ },
78
+ },
79
+ {
80
+ id: 'heading-hierarchy',
81
+ name: 'Heading Hierarchy',
82
+ category: 'headings',
83
+ severity: 'warning',
84
+ description: 'Headings should follow sequential order (H1 → H2 → H3)',
85
+ check: (ctx) => {
86
+ if (ctx.headingHierarchyValid === undefined) {
87
+ return createResult({ id: 'heading-hierarchy', name: 'Heading Hierarchy', category: 'headings', severity: 'warning' }, 'info', 'Unable to check heading hierarchy (data unavailable)', { recommendation: 'Ensure heading analysis completed' });
88
+ }
89
+ if (!ctx.headingHierarchyValid) {
90
+ const skipped = ctx.headingSkippedLevels?.join(', ') || 'unknown';
91
+ return createResult({ id: 'heading-hierarchy', name: 'Heading Hierarchy', category: 'headings', severity: 'warning' }, 'warn', `Heading levels are skipped (${skipped})`, {
92
+ recommendation: 'Use sequential heading levels (H1 → H2 → H3)',
93
+ evidence: {
94
+ found: `Skipped heading levels: ${skipped}`,
95
+ expected: 'Sequential heading structure (H1 → H2 → H3 → H4, etc.)',
96
+ impact: 'Skipping heading levels disrupts the document outline and can confuse screen readers and search engine crawlers trying to understand content hierarchy. This weakens accessibility and semantic structure.',
97
+ example: '<h1>Main Title</h1>\n<h2>Section Title</h2>\n<h3>Subsection Title</h3>\n<!-- Do NOT skip from H1 to H3 -->'
98
+ }
99
+ });
100
+ }
101
+ return createResult({ id: 'heading-hierarchy', name: 'Heading Hierarchy', category: 'headings', severity: 'warning' }, 'pass', 'Heading structure is correct');
102
+ },
103
+ },
104
+ {
105
+ id: 'h2-count',
106
+ name: 'H2 Count',
107
+ category: 'headings',
108
+ severity: 'info',
109
+ description: 'Page should have 2-8 H2 headings for good structure',
110
+ check: (ctx) => {
111
+ if (ctx.h2Count === undefined) {
112
+ return createResult({ id: 'h2-count', name: 'H2 Count', category: 'headings', severity: 'info' }, 'info', 'Unable to check H2 count (data unavailable)', { recommendation: 'Ensure heading analysis completed' });
113
+ }
114
+ const { min, max } = SEO_THRESHOLDS.headings.h2;
115
+ if (ctx.h2Count < min) {
116
+ return createResult({ id: 'h2-count', name: 'H2 Count', category: 'headings', severity: 'info' }, 'info', `Few H2 headings (${ctx.h2Count})`, { value: ctx.h2Count, recommendation: `Consider adding more H2 headings for better structure (${min}-${max} ideal)` });
117
+ }
118
+ if (ctx.h2Count > max) {
119
+ return createResult({ id: 'h2-count', name: 'H2 Count', category: 'headings', severity: 'info' }, 'info', `Many H2 headings (${ctx.h2Count})`, { value: ctx.h2Count, recommendation: 'Consider consolidating sections' });
120
+ }
121
+ return createResult({ id: 'h2-count', name: 'H2 Count', category: 'headings', severity: 'info' }, 'pass', `Good H2 count (${ctx.h2Count})`, { value: ctx.h2Count });
122
+ },
123
+ },
124
+ {
125
+ id: 'html5-header-exists',
126
+ name: 'HTML5 Header',
127
+ category: 'technical',
128
+ severity: 'info',
129
+ description: 'Page should use a <header> element',
130
+ check: (ctx) => {
131
+ if (!ctx.hasHeader) {
132
+ return createResult({ id: 'html5-header-exists', name: 'HTML5 Header', category: 'technical', severity: 'info' }, 'info', 'Missing <header> element', { recommendation: 'Use <header> for site-wide or page-specific introductory content' });
133
+ }
134
+ return createResult({ id: 'html5-header-exists', name: 'HTML5 Header', category: 'technical', severity: 'info' }, 'pass', 'HTML5 <header> element present');
135
+ },
136
+ },
137
+ {
138
+ id: 'html5-nav-exists',
139
+ name: 'HTML5 Navigation',
140
+ category: 'technical',
141
+ severity: 'info',
142
+ description: 'Page should use a <nav> element for navigation links',
143
+ check: (ctx) => {
144
+ if (!ctx.hasNav) {
145
+ return createResult({ id: 'html5-nav-exists', name: 'HTML5 Navigation', category: 'technical', severity: 'info' }, 'info', 'Missing <nav> element', { recommendation: 'Use <nav> to define a block of navigation links' });
146
+ }
147
+ return createResult({ id: 'html5-nav-exists', name: 'HTML5 Navigation', category: 'technical', severity: 'info' }, 'pass', 'HTML5 <nav> element present');
148
+ },
149
+ },
150
+ {
151
+ id: 'html5-main-exists',
152
+ name: 'HTML5 Main Content',
153
+ category: 'technical',
154
+ severity: 'info',
155
+ description: 'Page should use a <main> element for the dominant content',
156
+ check: (ctx) => {
157
+ if (!ctx.hasMain) {
158
+ return createResult({ id: 'html5-main-exists', name: 'HTML5 Main Content', category: 'technical', severity: 'info' }, 'info', 'Missing <main> element', { recommendation: 'Use <main> to enclose the dominant content of the <body>' });
159
+ }
160
+ return createResult({ id: 'html5-main-exists', name: 'HTML5 Main Content', category: 'technical', severity: 'info' }, 'pass', 'HTML5 <main> element present');
161
+ },
162
+ },
163
+ {
164
+ id: 'html5-article-exists',
165
+ name: 'HTML5 Article',
166
+ category: 'technical',
167
+ severity: 'info',
168
+ description: 'Consider using <article> for independent, self-contained content',
169
+ check: (ctx) => {
170
+ if (!ctx.hasArticle && ctx.wordCount && ctx.wordCount > 500) {
171
+ return createResult({ id: 'html5-article-exists', name: 'HTML5 Article', category: 'technical', severity: 'info' }, 'info', 'Missing <article> element for substantial content', { recommendation: 'Consider using <article> for blog posts, news articles, etc.' });
172
+ }
173
+ return createResult({ id: 'html5-article-exists', name: 'HTML5 Article', category: 'technical', severity: 'info' }, 'pass', 'HTML5 <article> element present or not required');
174
+ },
175
+ },
176
+ {
177
+ id: 'html5-section-exists',
178
+ name: 'HTML5 Section',
179
+ category: 'technical',
180
+ severity: 'info',
181
+ description: 'Consider using <section> for thematic grouping of content',
182
+ check: (ctx) => {
183
+ if (!ctx.hasSection && (ctx.h2Count && ctx.h2Count > 1)) {
184
+ return createResult({ id: 'html5-section-exists', name: 'HTML5 Section', category: 'technical', severity: 'info' }, 'info', 'Missing <section> element for content grouping', { recommendation: 'Consider using <section> to group related content, usually with a heading' });
185
+ }
186
+ return createResult({ id: 'html5-section-exists', name: 'HTML5 Section', category: 'technical', severity: 'info' }, 'pass', 'HTML5 <section> element present or not required');
187
+ },
188
+ },
189
+ {
190
+ id: 'html5-footer-exists',
191
+ name: 'HTML5 Footer',
192
+ category: 'technical',
193
+ severity: 'info',
194
+ description: 'Page should use a <footer> element',
195
+ check: (ctx) => {
196
+ if (!ctx.hasFooter) {
197
+ return createResult({ id: 'html5-footer-exists', name: 'HTML5 Footer', category: 'technical', severity: 'info' }, 'info', 'Missing <footer> element', { recommendation: 'Use <footer> for site-wide or page-specific footer content' });
198
+ }
199
+ return createResult({ id: 'html5-footer-exists', name: 'HTML5 Footer', category: 'technical', severity: 'info' }, 'pass', 'HTML5 <footer> element present');
200
+ },
201
+ },
202
+ {
203
+ id: 'keywords-in-h1',
204
+ name: 'Keywords in H1',
205
+ category: 'headings',
206
+ severity: 'warning',
207
+ description: 'H1 should contain main keywords',
208
+ check: (ctx) => {
209
+ if (ctx.keywordsInH1 === false && ctx.topKeywords && ctx.topKeywords.length > 0) {
210
+ return createResult({ id: 'keywords-in-h1', name: 'Keywords in H1', category: 'headings', severity: 'warning' }, 'warn', 'H1 does not appear to contain top keywords', {
211
+ recommendation: 'Ensure your H1 tag includes your main target keyword naturally.',
212
+ evidence: {
213
+ found: `H1: "${ctx.h1Text}"`,
214
+ expected: `Should contain one of: ${ctx.topKeywords.join(', ')}`,
215
+ impact: 'Keywords in H1 confirm relevance to the user and search engines. The H1 should naturally include your primary target keyword to reinforce the page topic and improve rankings for that term.',
216
+ example: `<h1>Complete Guide to ${ctx.topKeywords?.[0] || 'Your Primary Keyword'}</h1>`
217
+ }
218
+ });
219
+ }
220
+ return createResult({ id: 'keywords-in-h1', name: 'Keywords in H1', category: 'headings', severity: 'warning' }, 'pass', 'Keywords present in H1');
221
+ },
222
+ },
223
+ ];
@@ -0,0 +1,10 @@
1
+ import { SeoRule } from './types.js';
2
+ export declare const technicalAdvancedRules: SeoRule[];
3
+ declare module './types.js' {
4
+ interface RuleContext {
5
+ metaRefresh?: {
6
+ delay: number;
7
+ url?: string;
8
+ };
9
+ }
10
+ }
@@ -0,0 +1,289 @@
1
+ import { createResult } from './types.js';
2
+ export const technicalAdvancedRules = [
3
+ {
4
+ id: 'meta-refresh-redirect',
5
+ name: 'Meta Refresh Redirect',
6
+ category: 'technical',
7
+ severity: 'warning',
8
+ description: 'Pages should not use meta refresh redirects',
9
+ check: (ctx) => {
10
+ if (!ctx.metaRefresh) {
11
+ return createResult({ id: 'meta-refresh-redirect', name: 'Meta Refresh Redirect', category: 'technical', severity: 'warning' }, 'info', 'Not applicable (no meta refresh detected)', { recommendation: 'This rule checks for meta refresh redirects which can impact SEO' });
12
+ }
13
+ const { delay, url } = ctx.metaRefresh;
14
+ if (url) {
15
+ return createResult({ id: 'meta-refresh-redirect', name: 'Meta Refresh Redirect', category: 'technical', severity: 'warning' }, delay === 0 ? 'warn' : 'fail', `Page uses meta refresh redirect (${delay}s delay)`, {
16
+ value: delay,
17
+ recommendation: 'Use HTTP 301/302 redirects instead of meta refresh for SEO',
18
+ evidence: {
19
+ found: `<meta http-equiv="refresh" content="${delay};url=${url}">`,
20
+ expected: 'HTTP 301/302 redirect',
21
+ impact: delay > 0
22
+ ? 'Meta refresh with delay confuses users and search engines'
23
+ : 'Meta refresh redirects are not as SEO-friendly as HTTP redirects',
24
+ learnMore: 'https://developers.google.com/search/docs/crawling-indexing/http-network-errors#meta-refresh'
25
+ }
26
+ });
27
+ }
28
+ else if (delay > 0) {
29
+ return createResult({ id: 'meta-refresh-redirect', name: 'Meta Refresh Redirect', category: 'technical', severity: 'warning' }, 'warn', `Page auto-refreshes every ${delay} seconds`, {
30
+ value: delay,
31
+ recommendation: 'Avoid auto-refresh; let users control when to refresh content',
32
+ evidence: {
33
+ found: `<meta http-equiv="refresh" content="${delay}">`,
34
+ expected: 'No auto-refresh',
35
+ impact: 'Auto-refresh can be disorienting and wastes bandwidth'
36
+ }
37
+ });
38
+ }
39
+ return createResult({ id: 'meta-refresh-redirect', name: 'Meta Refresh Redirect', category: 'technical', severity: 'warning' }, 'info', 'Not applicable (no meta refresh detected)', { recommendation: 'This rule checks for meta refresh redirects which can impact SEO' });
40
+ },
41
+ },
42
+ {
43
+ id: 'html-page-size',
44
+ name: 'HTML Page Size',
45
+ category: 'performance',
46
+ severity: 'warning',
47
+ description: 'HTML page should not exceed reasonable size limits',
48
+ check: (ctx) => {
49
+ if (ctx.htmlSize === undefined) {
50
+ return createResult({ id: 'html-page-size', name: 'HTML Page Size', category: 'performance', severity: 'warning' }, 'info', 'Not applicable (HTML size data unavailable)', { recommendation: 'This rule checks if HTML size is within recommended limits for optimal performance' });
51
+ }
52
+ const sizeKb = ctx.htmlSize / 1024;
53
+ const sizeMb = sizeKb / 1024;
54
+ if (sizeMb > 2) {
55
+ return createResult({ id: 'html-page-size', name: 'HTML Page Size', category: 'performance', severity: 'warning' }, 'fail', `HTML size ${sizeMb.toFixed(2)}MB exceeds 2MB limit`, {
56
+ value: ctx.htmlSize,
57
+ recommendation: 'Reduce HTML size by removing inline scripts/styles and optimizing content',
58
+ evidence: {
59
+ found: `${sizeMb.toFixed(2)}MB`,
60
+ expected: '<2MB',
61
+ impact: 'Very large HTML files slow down crawling and may not be fully indexed'
62
+ }
63
+ });
64
+ }
65
+ if (sizeKb > 500) {
66
+ return createResult({ id: 'html-page-size', name: 'HTML Page Size', category: 'performance', severity: 'warning' }, 'warn', `HTML size ${sizeKb.toFixed(0)}KB is large`, {
67
+ value: ctx.htmlSize,
68
+ recommendation: 'Consider reducing HTML size for faster page loads',
69
+ evidence: {
70
+ found: `${sizeKb.toFixed(0)}KB`,
71
+ expected: '<500KB',
72
+ impact: 'Large HTML files increase time to first meaningful paint'
73
+ }
74
+ });
75
+ }
76
+ return createResult({ id: 'html-page-size', name: 'HTML Page Size', category: 'performance', severity: 'warning' }, 'pass', `HTML size ${sizeKb.toFixed(0)}KB is acceptable`);
77
+ },
78
+ },
79
+ {
80
+ id: 'total-page-size',
81
+ name: 'Total Page Size',
82
+ category: 'performance',
83
+ severity: 'warning',
84
+ description: 'Total page weight should be optimized for performance',
85
+ check: (ctx) => {
86
+ if (ctx.totalPageSize === undefined) {
87
+ return createResult({ id: 'total-page-size', name: 'Total Page Size', category: 'performance', severity: 'warning' }, 'info', 'Not applicable (total page size data unavailable)', { recommendation: 'This rule checks total page weight including all resources for optimal load time' });
88
+ }
89
+ const sizeMb = ctx.totalPageSize / (1024 * 1024);
90
+ if (sizeMb > 5) {
91
+ return createResult({ id: 'total-page-size', name: 'Total Page Size', category: 'performance', severity: 'warning' }, 'fail', `Total page size ${sizeMb.toFixed(2)}MB exceeds 5MB`, {
92
+ value: ctx.totalPageSize,
93
+ recommendation: 'Optimize images, defer scripts, and reduce overall page weight',
94
+ evidence: {
95
+ found: `${sizeMb.toFixed(2)}MB`,
96
+ expected: '<5MB',
97
+ impact: 'Very large pages significantly impact mobile users and Core Web Vitals'
98
+ }
99
+ });
100
+ }
101
+ if (sizeMb > 3) {
102
+ return createResult({ id: 'total-page-size', name: 'Total Page Size', category: 'performance', severity: 'warning' }, 'warn', `Total page size ${sizeMb.toFixed(2)}MB is large`, {
103
+ value: ctx.totalPageSize,
104
+ recommendation: 'Consider optimizing resources to improve load times',
105
+ evidence: {
106
+ found: `${sizeMb.toFixed(2)}MB`,
107
+ expected: '<3MB',
108
+ impact: 'Large pages increase bounce rate, especially on mobile'
109
+ }
110
+ });
111
+ }
112
+ return createResult({ id: 'total-page-size', name: 'Total Page Size', category: 'performance', severity: 'warning' }, 'pass', `Total page size ${sizeMb.toFixed(2)}MB is acceptable`);
113
+ },
114
+ },
115
+ {
116
+ id: 'server-response-time',
117
+ name: 'Server Response Time',
118
+ category: 'performance',
119
+ severity: 'warning',
120
+ description: 'Server should respond within acceptable time limits',
121
+ check: (ctx) => {
122
+ const ttfb = ctx.timings?.ttfb;
123
+ if (ttfb === undefined) {
124
+ return createResult({ id: 'server-response-time', name: 'Server Response Time', category: 'performance', severity: 'warning' }, 'info', 'Not applicable (TTFB timing data unavailable)', { recommendation: 'This rule checks server response time (TTFB) for performance optimization' });
125
+ }
126
+ if (ttfb > 5000) {
127
+ return createResult({ id: 'server-response-time', name: 'Server Response Time', category: 'performance', severity: 'warning' }, 'fail', `TTFB ${(ttfb / 1000).toFixed(2)}s exceeds 5s`, {
128
+ value: ttfb,
129
+ recommendation: 'Investigate server performance, caching, and database queries',
130
+ evidence: {
131
+ found: `${(ttfb / 1000).toFixed(2)}s`,
132
+ expected: '<5s',
133
+ impact: 'Very slow server response leads to timeout errors and poor UX'
134
+ }
135
+ });
136
+ }
137
+ if (ttfb > 2000) {
138
+ return createResult({ id: 'server-response-time', name: 'Server Response Time', category: 'performance', severity: 'warning' }, 'warn', `TTFB ${(ttfb / 1000).toFixed(2)}s is slow`, {
139
+ value: ttfb,
140
+ recommendation: 'Optimize server response time for better Core Web Vitals',
141
+ evidence: {
142
+ found: `${(ttfb / 1000).toFixed(2)}s`,
143
+ expected: '<2s (ideally <600ms)',
144
+ impact: 'Slow server response negatively affects LCP and user experience'
145
+ }
146
+ });
147
+ }
148
+ return createResult({ id: 'server-response-time', name: 'Server Response Time', category: 'performance', severity: 'warning' }, 'pass', `TTFB ${ttfb}ms is good`);
149
+ },
150
+ },
151
+ {
152
+ id: 'url-length',
153
+ name: 'URL Length',
154
+ category: 'technical',
155
+ severity: 'warning',
156
+ description: 'URLs should not be excessively long',
157
+ check: (ctx) => {
158
+ if (!ctx.url) {
159
+ return createResult({ id: 'url-length', name: 'URL Length', category: 'technical', severity: 'warning' }, 'info', 'Not applicable (URL data unavailable)', { recommendation: 'This rule checks URL length to ensure it is within recommended limits' });
160
+ }
161
+ const length = ctx.url.length;
162
+ if (length > 2000) {
163
+ return createResult({ id: 'url-length', name: 'URL Length', category: 'technical', severity: 'warning' }, 'fail', `URL length ${length} chars exceeds browser limits`, {
164
+ value: length,
165
+ recommendation: 'Shorten URL to under 2000 characters',
166
+ evidence: {
167
+ found: `${length} characters`,
168
+ expected: '<2000 characters',
169
+ impact: 'URLs over 2000 characters may be truncated or rejected by browsers'
170
+ }
171
+ });
172
+ }
173
+ if (length > 200) {
174
+ return createResult({ id: 'url-length', name: 'URL Length', category: 'technical', severity: 'warning' }, 'warn', `URL length ${length} chars is long`, {
175
+ value: length,
176
+ recommendation: 'Consider using shorter, cleaner URLs for SEO',
177
+ evidence: {
178
+ found: `${length} characters`,
179
+ expected: '<200 characters for best SEO',
180
+ impact: 'Long URLs are harder to share and may be truncated in SERPs'
181
+ }
182
+ });
183
+ }
184
+ return createResult({ id: 'url-length', name: 'URL Length', category: 'technical', severity: 'warning' }, 'pass', `URL length ${length} chars is acceptable`);
185
+ },
186
+ },
187
+ {
188
+ id: 'url-special-chars',
189
+ name: 'URL Special Characters',
190
+ category: 'technical',
191
+ severity: 'warning',
192
+ description: 'URLs should not contain problematic special characters',
193
+ check: (ctx) => {
194
+ if (!ctx.url) {
195
+ return createResult({ id: 'url-special-chars', name: 'URL Special Characters', category: 'technical', severity: 'warning' }, 'info', 'Not applicable (URL data unavailable)', { recommendation: 'This rule checks for problematic special characters in URLs' });
196
+ }
197
+ try {
198
+ const urlObj = new URL(ctx.url);
199
+ const path = urlObj.pathname + urlObj.search;
200
+ const issues = [];
201
+ if (/[A-Z]/.test(path)) {
202
+ issues.push('uppercase letters');
203
+ }
204
+ if (path.includes(' ') || path.includes('%20')) {
205
+ issues.push('spaces');
206
+ }
207
+ if (path.includes('_')) {
208
+ issues.push('underscores (use hyphens)');
209
+ }
210
+ if (/\/\//.test(path)) {
211
+ issues.push('double slashes');
212
+ }
213
+ if (/[àáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿ]/i.test(decodeURIComponent(path))) {
214
+ issues.push('accented characters');
215
+ }
216
+ if (issues.length > 0) {
217
+ return createResult({ id: 'url-special-chars', name: 'URL Special Characters', category: 'technical', severity: 'warning' }, 'warn', `URL contains: ${issues.join(', ')}`, {
218
+ recommendation: 'Use lowercase letters, hyphens, and avoid special characters',
219
+ evidence: {
220
+ found: issues,
221
+ expected: 'Lowercase letters, numbers, hyphens only',
222
+ impact: 'Non-standard URL characters can cause crawling and indexing issues'
223
+ }
224
+ });
225
+ }
226
+ return createResult({ id: 'url-special-chars', name: 'URL Special Characters', category: 'technical', severity: 'warning' }, 'pass', 'URL uses clean, SEO-friendly characters');
227
+ }
228
+ catch {
229
+ return createResult({ id: 'url-special-chars', name: 'URL Special Characters', category: 'technical', severity: 'warning' }, 'fail', 'Invalid URL syntax', {
230
+ recommendation: 'Fix URL syntax',
231
+ evidence: {
232
+ found: ctx.url,
233
+ expected: 'Valid URL format (e.g., https://example.com/path)',
234
+ impact: 'Invalid URLs cannot be crawled or indexed',
235
+ example: 'https://example.com/valid-path-with-hyphens',
236
+ }
237
+ });
238
+ }
239
+ },
240
+ },
241
+ {
242
+ id: 'password-on-http',
243
+ name: 'Password Fields on HTTP',
244
+ category: 'security',
245
+ severity: 'error',
246
+ description: 'Pages with password fields must use HTTPS',
247
+ check: (ctx) => {
248
+ if (!ctx.hasPasswordField || ctx.isHttps === undefined) {
249
+ return createResult({ id: 'password-on-http', name: 'Password Fields on HTTP', category: 'security', severity: 'error' }, 'info', 'Not applicable (no password fields detected)', { recommendation: 'This rule ensures password fields are served over HTTPS for security' });
250
+ }
251
+ if (ctx.hasPasswordField && !ctx.isHttps) {
252
+ return createResult({ id: 'password-on-http', name: 'Password Fields on HTTP', category: 'security', severity: 'error' }, 'fail', 'Password field detected on non-HTTPS page', {
253
+ recommendation: 'Serve login/registration pages over HTTPS only',
254
+ evidence: {
255
+ found: '<input type="password"> on HTTP',
256
+ expected: 'HTTPS for all pages with password fields',
257
+ impact: 'User credentials can be intercepted in transit',
258
+ learnMore: 'https://web.dev/is-on-https/'
259
+ }
260
+ });
261
+ }
262
+ return createResult({ id: 'password-on-http', name: 'Password Fields on HTTP', category: 'security', severity: 'error' }, 'pass', 'Password field properly served over HTTPS');
263
+ },
264
+ },
265
+ {
266
+ id: 'forms-on-http',
267
+ name: 'Forms on HTTP',
268
+ category: 'security',
269
+ severity: 'warning',
270
+ description: 'Forms should submit data over HTTPS',
271
+ check: (ctx) => {
272
+ if (ctx.formsOnHttp === undefined) {
273
+ return createResult({ id: 'forms-on-http', name: 'Forms on HTTP', category: 'security', severity: 'warning' }, 'info', 'Not applicable (form data unavailable)', { recommendation: 'This rule checks if forms submit data over secure HTTPS connections' });
274
+ }
275
+ if (ctx.formsOnHttp > 0) {
276
+ return createResult({ id: 'forms-on-http', name: 'Forms on HTTP', category: 'security', severity: 'warning' }, 'warn', `${ctx.formsOnHttp} form(s) submit to HTTP URLs`, {
277
+ value: ctx.formsOnHttp,
278
+ recommendation: 'Update form actions to use HTTPS URLs',
279
+ evidence: {
280
+ found: ctx.formsOnHttp,
281
+ expected: 0,
282
+ impact: 'Form data submitted over HTTP can be intercepted'
283
+ }
284
+ });
285
+ }
286
+ return createResult({ id: 'forms-on-http', name: 'Forms on HTTP', category: 'security', severity: 'warning' }, 'pass', 'All forms submit over HTTPS or no forms detected');
287
+ },
288
+ },
289
+ ];
@@ -0,0 +1,2 @@
1
+ import { SeoRule } from './types.js';
2
+ export declare const technicalRules: SeoRule[];