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,498 @@
1
+ import { createResult } from './types.js';
2
+ import { SEO_THRESHOLDS } from './thresholds.js';
3
+ const GENERIC_ANCHOR_TEXTS = new Set([
4
+ 'click here', 'click', 'here', 'read more', 'learn more', 'more', 'link',
5
+ 'this', 'go', 'continue', 'next', 'previous', 'back', 'see more',
6
+ 'find out more', 'discover more', 'view more', 'show more', 'details',
7
+ 'clique aqui', 'clique', 'aqui', 'saiba mais', 'leia mais', 'veja mais',
8
+ 'mais', 'confira', 'ver mais', 'continuar', 'próximo', 'anterior', 'voltar',
9
+ 'haga clic aquí', 'clic aquí', 'aquí', 'leer más', 'ver más', 'más',
10
+ 'saber más', 'descubrir más', 'continuar',
11
+ 'cliquez ici', 'ici', 'en savoir plus', 'lire la suite', 'voir plus',
12
+ 'plus', 'continuer', 'suivant', 'précédent',
13
+ 'hier klicken', 'hier', 'mehr erfahren', 'weiterlesen', 'mehr',
14
+ 'weiter', 'zurück',
15
+ 'clicca qui', 'qui', 'scopri di più', 'leggi di più', 'continua',
16
+ ]);
17
+ function isGenericAnchorText(text) {
18
+ const normalized = text.trim().toLowerCase();
19
+ if (normalized.length === 0)
20
+ return true;
21
+ if (normalized.length < 3)
22
+ return true;
23
+ if (GENERIC_ANCHOR_TEXTS.has(normalized))
24
+ return true;
25
+ if (/^[\d\s\-_.,!?#@%&*()[\]{}|\\/<>:;"'`~+=]+$/.test(normalized))
26
+ return true;
27
+ if (['link', 'url', 'page', 'site', 'website', 'info'].includes(normalized))
28
+ return true;
29
+ return false;
30
+ }
31
+ export const linkRules = [
32
+ {
33
+ id: 'links-descriptive-text',
34
+ name: 'Link Text',
35
+ category: 'links',
36
+ severity: 'warning',
37
+ description: 'Links should have descriptive anchor text',
38
+ check: (ctx) => {
39
+ if (ctx.totalLinks === undefined || ctx.totalLinks === 0) {
40
+ return createResult({ id: 'links-descriptive-text', name: 'Link Text', category: 'links', severity: 'warning' }, 'info', 'Not applicable (no links detected on page)', { recommendation: 'This rule checks for descriptive anchor text on all links when links are present' });
41
+ }
42
+ const withoutText = ctx.problematicLinks?.withoutText ?? [];
43
+ if (withoutText.length > 0) {
44
+ const examples = withoutText.slice(0, 3).map((l) => l.href).join(', ');
45
+ return createResult({ id: 'links-descriptive-text', name: 'Link Text', category: 'links', severity: 'warning' }, 'warn', `${withoutText.length} link(s) without descriptive text`, {
46
+ value: withoutText.length,
47
+ recommendation: 'Add descriptive anchor text to all links for better accessibility and SEO',
48
+ evidence: {
49
+ found: withoutText.map((l) => l.href),
50
+ example: '<a href="/page">Learn more about our services</a>',
51
+ impact: 'Screen readers cannot describe the link destination to users',
52
+ },
53
+ });
54
+ }
55
+ return createResult({ id: 'links-descriptive-text', name: 'Link Text', category: 'links', severity: 'warning' }, 'pass', 'All links have descriptive text');
56
+ },
57
+ },
58
+ {
59
+ id: 'links-generic-text',
60
+ name: 'Generic Link Text',
61
+ category: 'links',
62
+ severity: 'warning',
63
+ description: 'Avoid generic link text like "click here" or "read more"',
64
+ check: (ctx) => {
65
+ const genericLinks = ctx.problematicLinks?.genericText ?? [];
66
+ if (genericLinks.length > 0) {
67
+ return createResult({ id: 'links-generic-text', name: 'Generic Link Text', category: 'links', severity: 'warning' }, 'warn', `${genericLinks.length} link(s) with generic text`, {
68
+ value: genericLinks.length,
69
+ recommendation: 'Replace generic anchor text with descriptive text that explains where the link goes',
70
+ evidence: {
71
+ found: genericLinks.map((l) => `"${l.text}" → ${l.href}`),
72
+ issue: 'Generic text like "click here", "read more", "here" provides no context',
73
+ example: 'Instead of <a href="/docs">Click here</a>, use <a href="/docs">View documentation</a>',
74
+ },
75
+ });
76
+ }
77
+ return createResult({ id: 'links-generic-text', name: 'Generic Link Text', category: 'links', severity: 'warning' }, 'pass', 'All links have descriptive anchor text');
78
+ },
79
+ },
80
+ {
81
+ id: 'links-internal-count',
82
+ name: 'Internal Links',
83
+ category: 'links',
84
+ severity: 'info',
85
+ description: 'Page should have at least 3 internal links',
86
+ check: (ctx) => {
87
+ if (ctx.internalLinks === undefined) {
88
+ return createResult({ id: 'links-internal-count', name: 'Internal Links', category: 'links', severity: 'info' }, 'info', 'Not applicable (internal links data unavailable)', { recommendation: 'This rule checks for internal linking structure when link data is available' });
89
+ }
90
+ const min = SEO_THRESHOLDS.links.minInternal;
91
+ if (ctx.internalLinks < min) {
92
+ return createResult({ id: 'links-internal-count', name: 'Internal Links', category: 'links', severity: 'info' }, 'info', `Few internal links (${ctx.internalLinks})`, { value: ctx.internalLinks, recommendation: `Add at least ${min} internal links for better navigation` });
93
+ }
94
+ return createResult({ id: 'links-internal-count', name: 'Internal Links', category: 'links', severity: 'info' }, 'pass', `Good internal linking (${ctx.internalLinks} links)`, { value: ctx.internalLinks });
95
+ },
96
+ },
97
+ {
98
+ id: 'links-external-count',
99
+ name: 'External Links',
100
+ category: 'links',
101
+ severity: 'info',
102
+ description: 'Page should not have too many external links',
103
+ check: (ctx) => {
104
+ if (ctx.externalLinks === undefined) {
105
+ return createResult({ id: 'links-external-count', name: 'External Links', category: 'links', severity: 'info' }, 'info', 'Not applicable (external links data unavailable)', { recommendation: 'This rule checks external link count when link data is available' });
106
+ }
107
+ const max = SEO_THRESHOLDS.links.maxExternal;
108
+ if (ctx.externalLinks > max) {
109
+ return createResult({ id: 'links-external-count', name: 'External Links', category: 'links', severity: 'info' }, 'warn', `Too many external links (${ctx.externalLinks})`, {
110
+ value: ctx.externalLinks,
111
+ recommendation: `Reduce external links to under ${max}`,
112
+ evidence: {
113
+ found: `${ctx.externalLinks} external links`,
114
+ expected: `Less than ${max} external links`,
115
+ impact: 'Too many external links can dilute page authority and may appear spammy',
116
+ },
117
+ });
118
+ }
119
+ return createResult({ id: 'links-external-count', name: 'External Links', category: 'links', severity: 'info' }, 'pass', `External links count is acceptable (${ctx.externalLinks} links)`, { value: ctx.externalLinks });
120
+ },
121
+ },
122
+ {
123
+ id: 'links-external-noopener',
124
+ name: 'External Links Noopener',
125
+ category: 'security',
126
+ severity: 'warning',
127
+ description: 'External links with target="_blank" should have rel="noopener"',
128
+ check: (ctx) => {
129
+ const missingNoopener = ctx.problematicLinks?.missingNoopener ?? [];
130
+ if (missingNoopener.length > 0) {
131
+ return createResult({ id: 'links-external-noopener', name: 'External Links Noopener', category: 'security', severity: 'warning' }, 'warn', `${missingNoopener.length} external link(s) missing rel="noopener"`, {
132
+ value: missingNoopener.length,
133
+ recommendation: 'Add rel="noopener" to all external links with target="_blank"',
134
+ evidence: {
135
+ found: missingNoopener.map((l) => l.href),
136
+ issue: 'Links with target="_blank" without rel="noopener" allow the new page to access window.opener',
137
+ impact: 'Security vulnerability: the linked page can redirect your page or access sensitive data',
138
+ example: '<a href="https://external.com" target="_blank" rel="noopener noreferrer">External Site</a>',
139
+ },
140
+ });
141
+ }
142
+ return createResult({ id: 'links-external-noopener', name: 'External Links Noopener', category: 'security', severity: 'warning' }, 'pass', 'All external links with target="_blank" have proper security attributes');
143
+ },
144
+ },
145
+ {
146
+ id: 'links-external-noreferrer',
147
+ name: 'External Links Noreferrer',
148
+ category: 'security',
149
+ severity: 'info',
150
+ description: 'External links may benefit from rel="noreferrer" for privacy',
151
+ check: (ctx) => {
152
+ const missingNoreferrer = ctx.problematicLinks?.missingNoreferrer ?? [];
153
+ if (missingNoreferrer.length > 3) {
154
+ return createResult({ id: 'links-external-noreferrer', name: 'External Links Noreferrer', category: 'security', severity: 'info' }, 'info', `${missingNoreferrer.length} external link(s) without rel="noreferrer"`, {
155
+ value: missingNoreferrer.length,
156
+ recommendation: 'Consider adding rel="noreferrer" to prevent referrer leakage to external sites',
157
+ evidence: {
158
+ found: missingNoreferrer.slice(0, 5).map((l) => l.href),
159
+ issue: 'External sites can see your page URL in their analytics via the Referer header',
160
+ example: '<a href="https://external.com" target="_blank" rel="noopener noreferrer">External</a>',
161
+ },
162
+ });
163
+ }
164
+ return createResult({ id: 'links-external-noreferrer', name: 'External Links Noreferrer', category: 'security', severity: 'info' }, 'pass', 'External links have appropriate privacy attributes');
165
+ },
166
+ },
167
+ {
168
+ id: 'links-sponsored-ugc-directives',
169
+ name: 'Sponsored/UGC Links',
170
+ category: 'links',
171
+ severity: 'info',
172
+ description: 'Rel attributes `sponsored` and `ugc` should be used for paid or user-generated content links.',
173
+ check: (ctx) => {
174
+ if (!ctx.totalLinks) {
175
+ return createResult({ id: 'links-sponsored-ugc-directives', name: 'Sponsored/UGC Links', category: 'links', severity: 'info' }, 'info', 'Not applicable (no links detected on page)', { recommendation: 'This rule checks for proper rel="sponsored" and rel="ugc" attributes when links are present' });
176
+ }
177
+ let messages = [];
178
+ if (ctx.sponsoredLinks && ctx.sponsoredLinks > 0) {
179
+ messages.push(`${ctx.sponsoredLinks} link(s) with rel="sponsored".`);
180
+ }
181
+ if (ctx.ugcLinks && ctx.ugcLinks > 0) {
182
+ messages.push(`${ctx.ugcLinks} link(s) with rel="ugc".`);
183
+ }
184
+ if (messages.length > 0) {
185
+ return createResult({ id: 'links-sponsored-ugc-directives', name: 'Sponsored/UGC Links', category: 'links', severity: 'info' }, 'info', messages.join(' '), { recommendation: 'Ensure rel="sponsored" is used for paid links and rel="ugc" for user-generated content.' });
186
+ }
187
+ return createResult({ id: 'links-sponsored-ugc-directives', name: 'Sponsored/UGC Links', category: 'links', severity: 'info' }, 'pass', 'No sponsored or UGC links detected');
188
+ },
189
+ },
190
+ {
191
+ id: 'links-anchor-text-non-descriptive',
192
+ name: 'Non-Descriptive Anchor Text',
193
+ category: 'links',
194
+ severity: 'warning',
195
+ description: 'Anchor text should describe the link destination',
196
+ check: (ctx) => {
197
+ if (!ctx.allLinks || ctx.allLinks.length === 0) {
198
+ return createResult({ id: 'links-anchor-text-non-descriptive', name: 'Non-Descriptive Anchor Text', category: 'links', severity: 'warning' }, 'info', 'Not applicable (no links data available)', { recommendation: 'This rule checks for descriptive anchor text on all links when link data is available' });
199
+ }
200
+ const nonDescriptive = ctx.allLinks.filter((link) => link.text && isGenericAnchorText(link.text));
201
+ if (nonDescriptive.length > 0) {
202
+ return createResult({ id: 'links-anchor-text-non-descriptive', name: 'Non-Descriptive Anchor Text', category: 'links', severity: 'warning' }, 'warn', `${nonDescriptive.length} link(s) with non-descriptive anchor text`, {
203
+ value: nonDescriptive.length,
204
+ recommendation: 'Replace generic text with descriptive anchors',
205
+ evidence: {
206
+ found: nonDescriptive.slice(0, 5).map((l) => `"${l.text}" → ${l.href}`),
207
+ issue: 'Generic anchor text like "click here" provides no context',
208
+ example: 'Instead of <a href="/pricing">Click here</a>, use <a href="/pricing">View our pricing plans</a>',
209
+ },
210
+ });
211
+ }
212
+ return createResult({ id: 'links-anchor-text-non-descriptive', name: 'Non-Descriptive Anchor Text', category: 'links', severity: 'warning' }, 'pass', 'All links have descriptive anchor text');
213
+ },
214
+ },
215
+ {
216
+ id: 'links-self-referencing',
217
+ name: 'Self-Referencing Links',
218
+ category: 'links',
219
+ severity: 'info',
220
+ description: 'Pages should not link to themselves excessively',
221
+ check: (ctx) => {
222
+ if (!ctx.selfReferencingLinks) {
223
+ return createResult({ id: 'links-self-referencing', name: 'Self-Referencing Links', category: 'links', severity: 'info' }, 'info', 'Not applicable (self-referencing links data unavailable)', { recommendation: 'This rule checks for excessive self-referencing links when link data is available' });
224
+ }
225
+ if (ctx.selfReferencingLinks > 3) {
226
+ return createResult({ id: 'links-self-referencing', name: 'Self-Referencing Links', category: 'links', severity: 'info' }, 'info', `${ctx.selfReferencingLinks} self-referencing link(s)`, {
227
+ value: ctx.selfReferencingLinks,
228
+ recommendation: 'Remove unnecessary self-referencing links',
229
+ evidence: {
230
+ impact: 'Self-links can confuse users and dilute link equity',
231
+ },
232
+ });
233
+ }
234
+ return createResult({ id: 'links-self-referencing', name: 'Self-Referencing Links', category: 'links', severity: 'info' }, 'pass', `Self-referencing links are acceptable (${ctx.selfReferencingLinks} found)`, { value: ctx.selfReferencingLinks });
235
+ },
236
+ },
237
+ {
238
+ id: 'links-broken-internal',
239
+ name: 'Broken Internal Links',
240
+ category: 'links',
241
+ severity: 'error',
242
+ description: 'Internal links should not return 4xx errors',
243
+ check: (ctx) => {
244
+ if (!ctx.brokenInternalLinks || ctx.brokenInternalLinks.length === 0) {
245
+ return createResult({ id: 'links-broken-internal', name: 'Broken Internal Links', category: 'links', severity: 'error' }, 'pass', 'No broken internal links detected');
246
+ }
247
+ return createResult({ id: 'links-broken-internal', name: 'Broken Internal Links', category: 'links', severity: 'error' }, 'fail', `${ctx.brokenInternalLinks.length} broken internal link(s)`, {
248
+ value: ctx.brokenInternalLinks.length,
249
+ recommendation: 'Fix or remove broken internal links',
250
+ evidence: {
251
+ found: ctx.brokenInternalLinks.slice(0, 10),
252
+ impact: 'Broken links harm user experience and waste crawl budget',
253
+ },
254
+ });
255
+ },
256
+ },
257
+ {
258
+ id: 'links-broken-external',
259
+ name: 'Broken External Links',
260
+ category: 'links',
261
+ severity: 'warning',
262
+ description: 'External links should not return 4xx errors',
263
+ check: (ctx) => {
264
+ if (!ctx.brokenExternalLinks || ctx.brokenExternalLinks.length === 0) {
265
+ return createResult({ id: 'links-broken-external', name: 'Broken External Links', category: 'links', severity: 'warning' }, 'pass', 'No broken external links detected');
266
+ }
267
+ return createResult({ id: 'links-broken-external', name: 'Broken External Links', category: 'links', severity: 'warning' }, 'warn', `${ctx.brokenExternalLinks.length} broken external link(s)`, {
268
+ value: ctx.brokenExternalLinks.length,
269
+ recommendation: 'Update or remove broken external links',
270
+ evidence: {
271
+ found: ctx.brokenExternalLinks.slice(0, 10),
272
+ impact: 'Broken external links reduce trust and user experience',
273
+ },
274
+ });
275
+ },
276
+ },
277
+ {
278
+ id: 'links-redirect-chains',
279
+ name: 'Redirect Chain Links',
280
+ category: 'links',
281
+ severity: 'warning',
282
+ description: 'Internal links should not point to redirect chains',
283
+ check: (ctx) => {
284
+ if (!ctx.redirectChainLinks || ctx.redirectChainLinks.length === 0) {
285
+ return createResult({ id: 'links-redirect-chains', name: 'Redirect Chain Links', category: 'links', severity: 'warning' }, 'pass', 'No redirect chain links detected');
286
+ }
287
+ return createResult({ id: 'links-redirect-chains', name: 'Redirect Chain Links', category: 'links', severity: 'warning' }, 'warn', `${ctx.redirectChainLinks.length} link(s) with redirect chains`, {
288
+ value: ctx.redirectChainLinks.length,
289
+ recommendation: 'Update links to point directly to final destination',
290
+ evidence: {
291
+ found: ctx.redirectChainLinks.slice(0, 5).map((l) => `${l.from} → ${l.to} (${l.hops} hops)`),
292
+ impact: 'Redirect chains slow page load and waste crawl budget',
293
+ },
294
+ });
295
+ },
296
+ },
297
+ {
298
+ id: 'links-few-internal',
299
+ name: 'Pages with Few Internal Links',
300
+ category: 'links',
301
+ severity: 'warning',
302
+ description: 'Pages should have at least 3 internal links',
303
+ check: (ctx) => {
304
+ if (ctx.internalLinks === undefined) {
305
+ return createResult({ id: 'links-few-internal', name: 'Pages with Few Internal Links', category: 'links', severity: 'warning' }, 'info', 'Not applicable (internal links data unavailable)', { recommendation: 'This rule checks for sufficient internal linking when link data is available' });
306
+ }
307
+ if (ctx.internalLinks < 3) {
308
+ return createResult({ id: 'links-few-internal', name: 'Pages with Few Internal Links', category: 'links', severity: 'warning' }, 'warn', `Only ${ctx.internalLinks} internal link(s)`, {
309
+ value: ctx.internalLinks,
310
+ recommendation: 'Add more internal links to improve site navigation',
311
+ evidence: {
312
+ found: ctx.internalLinks,
313
+ expected: '3 or more internal links',
314
+ impact: 'Few internal links isolate pages and hurt discoverability',
315
+ },
316
+ });
317
+ }
318
+ return createResult({ id: 'links-few-internal', name: 'Pages with Few Internal Links', category: 'links', severity: 'warning' }, 'pass', `Good internal linking (${ctx.internalLinks} internal links)`, { value: ctx.internalLinks });
319
+ },
320
+ },
321
+ {
322
+ id: 'links-orphan-page',
323
+ name: 'Orphan Page Detection',
324
+ category: 'links',
325
+ severity: 'warning',
326
+ description: 'Pages should have incoming internal links',
327
+ check: (ctx) => {
328
+ if (ctx.incomingInternalLinks === undefined) {
329
+ return createResult({ id: 'links-orphan-page', name: 'Orphan Page Detection', category: 'links', severity: 'warning' }, 'info', 'Not applicable (incoming links data unavailable)', { recommendation: 'This rule checks for orphan pages when incoming link data is available' });
330
+ }
331
+ if (ctx.isStartPage) {
332
+ return createResult({ id: 'links-orphan-page', name: 'Orphan Page Detection', category: 'links', severity: 'warning' }, 'info', 'Not applicable (start page does not require incoming links)', { recommendation: 'This rule checks non-homepage pages for incoming internal links' });
333
+ }
334
+ if (ctx.incomingInternalLinks === 0) {
335
+ return createResult({ id: 'links-orphan-page', name: 'Orphan Page Detection', category: 'links', severity: 'warning' }, 'warn', 'Page has no incoming internal links (orphan)', {
336
+ value: 0,
337
+ recommendation: 'Add internal links pointing to this page',
338
+ evidence: {
339
+ impact: 'Orphan pages are hard to discover and may not be indexed',
340
+ },
341
+ });
342
+ }
343
+ return createResult({ id: 'links-orphan-page', name: 'Orphan Page Detection', category: 'links', severity: 'warning' }, 'pass', `Page has incoming internal links (${ctx.incomingInternalLinks} links)`, { value: ctx.incomingInternalLinks });
344
+ },
345
+ },
346
+ {
347
+ id: 'links-click-depth',
348
+ name: 'Page Click Depth',
349
+ category: 'links',
350
+ severity: 'warning',
351
+ description: 'Important pages should be within 3 clicks from homepage',
352
+ check: (ctx) => {
353
+ if (ctx.clickDepth === undefined) {
354
+ return createResult({ id: 'links-click-depth', name: 'Page Click Depth', category: 'links', severity: 'warning' }, 'info', 'Not applicable (click depth data unavailable)', { recommendation: 'This rule checks page depth from homepage when crawl data is available' });
355
+ }
356
+ if (ctx.clickDepth > 3) {
357
+ return createResult({ id: 'links-click-depth', name: 'Page Click Depth', category: 'links', severity: 'warning' }, 'warn', `Page is ${ctx.clickDepth} clicks from homepage`, {
358
+ value: ctx.clickDepth,
359
+ recommendation: 'Improve site structure to keep pages within 3 clicks',
360
+ evidence: {
361
+ found: `${ctx.clickDepth} clicks`,
362
+ expected: '3 clicks or less',
363
+ impact: 'Deep pages are harder to discover and get less link equity',
364
+ },
365
+ });
366
+ }
367
+ return createResult({ id: 'links-click-depth', name: 'Page Click Depth', category: 'links', severity: 'warning' }, 'pass', `Page is ${ctx.clickDepth} click(s) from homepage`, { value: ctx.clickDepth });
368
+ },
369
+ },
370
+ {
371
+ id: 'links-external-ratio',
372
+ name: 'External to Internal Link Ratio',
373
+ category: 'links',
374
+ severity: 'info',
375
+ description: 'Pages should have more internal than external links',
376
+ check: (ctx) => {
377
+ if (ctx.internalLinks === undefined || ctx.externalLinks === undefined) {
378
+ return createResult({ id: 'links-external-ratio', name: 'External to Internal Link Ratio', category: 'links', severity: 'info' }, 'info', 'Not applicable (link ratio data unavailable)', { recommendation: 'This rule checks external to internal link ratio when link data is available' });
379
+ }
380
+ if (ctx.totalLinks === undefined || ctx.totalLinks === 0) {
381
+ return createResult({ id: 'links-external-ratio', name: 'External to Internal Link Ratio', category: 'links', severity: 'info' }, 'info', 'Not applicable (no links detected on page)', { recommendation: 'This rule checks external to internal link ratio when links are present' });
382
+ }
383
+ const externalRatio = (ctx.externalLinks / ctx.totalLinks) * 100;
384
+ if (externalRatio > 70) {
385
+ return createResult({ id: 'links-external-ratio', name: 'External to Internal Link Ratio', category: 'links', severity: 'info' }, 'info', `High external link ratio (${Math.round(externalRatio)}%)`, {
386
+ value: Math.round(externalRatio),
387
+ recommendation: 'Add more internal links to balance link distribution',
388
+ evidence: {
389
+ found: `${ctx.externalLinks} external / ${ctx.internalLinks} internal`,
390
+ expected: 'More internal than external links',
391
+ impact: 'High external ratio may leak PageRank',
392
+ },
393
+ });
394
+ }
395
+ return createResult({ id: 'links-external-ratio', name: 'External to Internal Link Ratio', category: 'links', severity: 'info' }, 'pass', `Good link distribution (${Math.round(externalRatio)}% external)`, { value: Math.round(externalRatio) });
396
+ },
397
+ },
398
+ {
399
+ id: 'links-nofollow-internal',
400
+ name: 'Internal Nofollow Links',
401
+ category: 'links',
402
+ severity: 'info',
403
+ description: 'Internal links with nofollow prevent PageRank flow',
404
+ check: (ctx) => {
405
+ if (!ctx.nofollowInternalLinks || ctx.nofollowInternalLinks === 0) {
406
+ return createResult({ id: 'links-nofollow-internal', name: 'Internal Nofollow Links', category: 'links', severity: 'info' }, 'pass', 'No internal nofollow links detected');
407
+ }
408
+ return createResult({ id: 'links-nofollow-internal', name: 'Internal Nofollow Links', category: 'links', severity: 'info' }, 'info', `${ctx.nofollowInternalLinks} internal link(s) with nofollow`, {
409
+ value: ctx.nofollowInternalLinks,
410
+ recommendation: 'Remove nofollow from internal links unless intentional',
411
+ evidence: {
412
+ impact: 'Nofollow internal links waste PageRank',
413
+ },
414
+ });
415
+ },
416
+ },
417
+ {
418
+ id: 'excessive-links',
419
+ name: 'Excessive Links on Page',
420
+ category: 'links',
421
+ severity: 'warning',
422
+ description: 'Pages should not have more than 3,000 links',
423
+ check: (ctx) => {
424
+ if (ctx.totalLinks === undefined) {
425
+ return createResult({ id: 'excessive-links', name: 'Excessive Links on Page', category: 'links', severity: 'warning' }, 'info', 'Not applicable (total links data unavailable)', { recommendation: 'This rule checks for excessive links (>3,000) when link data is available' });
426
+ }
427
+ if (ctx.totalLinks > 3000) {
428
+ return createResult({ id: 'excessive-links', name: 'Excessive Links on Page', category: 'links', severity: 'warning' }, 'fail', `Page has ${ctx.totalLinks} links (exceeds 3,000 limit)`, {
429
+ value: ctx.totalLinks,
430
+ recommendation: 'Reduce the number of links to improve crawlability and page quality',
431
+ evidence: {
432
+ found: ctx.totalLinks,
433
+ expected: '<3,000 links',
434
+ impact: 'Search engines may consider pages with too many links as low-quality or spammy'
435
+ }
436
+ });
437
+ }
438
+ if (ctx.totalLinks > 1000) {
439
+ return createResult({ id: 'excessive-links', name: 'Excessive Links on Page', category: 'links', severity: 'warning' }, 'warn', `Page has ${ctx.totalLinks} links (consider reducing)`, {
440
+ value: ctx.totalLinks,
441
+ recommendation: 'Review and remove unnecessary links',
442
+ evidence: {
443
+ found: ctx.totalLinks,
444
+ expected: '<1,000 links recommended',
445
+ impact: 'Many links dilute page authority and can slow page load'
446
+ }
447
+ });
448
+ }
449
+ return createResult({ id: 'excessive-links', name: 'Excessive Links on Page', category: 'links', severity: 'warning' }, 'pass', `Links count is acceptable (${ctx.totalLinks} links)`, { value: ctx.totalLinks });
450
+ },
451
+ },
452
+ {
453
+ id: 'links-to-resources',
454
+ name: 'Links to Resources',
455
+ category: 'links',
456
+ severity: 'info',
457
+ description: 'Links should point to pages, not raw resources like images',
458
+ check: (ctx) => {
459
+ if (ctx.linksToResources === undefined) {
460
+ return createResult({ id: 'links-to-resources', name: 'Links to Resources', category: 'links', severity: 'info' }, 'info', 'Not applicable (resource links data unavailable)', { recommendation: 'This rule checks for links pointing to raw resources (images, PDFs) when link data is available' });
461
+ }
462
+ if (ctx.linksToResources > 0) {
463
+ return createResult({ id: 'links-to-resources', name: 'Links to Resources', category: 'links', severity: 'info' }, 'info', `${ctx.linksToResources} links point directly to resources (images, PDFs)`, {
464
+ value: ctx.linksToResources,
465
+ recommendation: 'Use appropriate tags (<img>, <embed>) instead of <a> for resources',
466
+ evidence: {
467
+ found: ctx.resourceLinkUrls?.slice(0, 5) || [],
468
+ impact: 'Links to raw resources may confuse crawlers about site architecture'
469
+ }
470
+ });
471
+ }
472
+ return createResult({ id: 'links-to-resources', name: 'Links to Resources', category: 'links', severity: 'info' }, 'pass', 'No direct links to raw resources detected');
473
+ },
474
+ },
475
+ {
476
+ id: 'links-403-forbidden',
477
+ name: '403 Forbidden Links',
478
+ category: 'links',
479
+ severity: 'warning',
480
+ description: 'External links should not return 403 Forbidden',
481
+ check: (ctx) => {
482
+ if (ctx.forbidden403Links === undefined) {
483
+ return createResult({ id: 'links-403-forbidden', name: '403 Forbidden Links', category: 'links', severity: 'warning' }, 'info', 'Not applicable (forbidden links data unavailable)', { recommendation: 'This rule checks for external links returning 403 Forbidden when link validation data is available' });
484
+ }
485
+ if (ctx.forbidden403Links > 0) {
486
+ return createResult({ id: 'links-403-forbidden', name: '403 Forbidden Links', category: 'links', severity: 'warning' }, 'warn', `${ctx.forbidden403Links} external links return 403 Forbidden`, {
487
+ value: ctx.forbidden403Links,
488
+ recommendation: 'Replace or remove links that return 403 errors',
489
+ evidence: {
490
+ found: ctx.forbidden403LinkUrls?.slice(0, 5) || [],
491
+ impact: 'Forbidden links negatively affect user experience'
492
+ }
493
+ });
494
+ }
495
+ return createResult({ id: 'links-403-forbidden', name: '403 Forbidden Links', category: 'links', severity: 'warning' }, 'pass', 'No forbidden (403) links detected');
496
+ },
497
+ },
498
+ ];
@@ -0,0 +1,2 @@
1
+ import { SeoRule } from './types.js';
2
+ export declare const localRules: SeoRule[];