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
@@ -7,8 +7,9 @@ export const internalLinkingRules = [
7
7
  severity: 'warning',
8
8
  description: 'Pages should have a healthy number of internal links',
9
9
  check: (ctx) => {
10
- if (ctx.internalLinks === undefined)
11
- return null;
10
+ if (ctx.internalLinks === undefined) {
11
+ return createResult({ id: 'linking-internal-count', name: 'Internal Link Count', category: 'links', severity: 'warning' }, 'info', 'Not applicable (internal links data unavailable)', { recommendation: 'This rule checks internal link count to ensure proper site navigation and link equity distribution' });
12
+ }
12
13
  const count = ctx.internalLinks;
13
14
  if (count === 0) {
14
15
  return createResult({ id: 'linking-internal-count', name: 'Internal Link Count', category: 'links', severity: 'warning' }, 'warn', 'No internal links found', {
@@ -39,10 +40,12 @@ export const internalLinkingRules = [
39
40
  severity: 'info',
40
41
  description: 'Pages should have more internal than external links',
41
42
  check: (ctx) => {
42
- if (ctx.internalLinks === undefined || ctx.externalLinks === undefined)
43
- return null;
44
- if (ctx.totalLinks === undefined || ctx.totalLinks === 0)
45
- return null;
43
+ if (ctx.internalLinks === undefined || ctx.externalLinks === undefined) {
44
+ return createResult({ id: 'linking-internal-ratio', name: 'Internal/External Link Ratio', category: 'links', severity: 'info' }, 'info', 'Not applicable (link data unavailable)', { recommendation: 'This rule checks the balance between internal and external links' });
45
+ }
46
+ if (ctx.totalLinks === undefined || ctx.totalLinks === 0) {
47
+ return createResult({ id: 'linking-internal-ratio', name: 'Internal/External Link Ratio', category: 'links', severity: 'info' }, 'info', 'Not applicable (no links detected)', { recommendation: 'This rule requires at least some links to analyze the internal/external ratio' });
48
+ }
46
49
  const internal = ctx.internalLinks;
47
50
  const external = ctx.externalLinks;
48
51
  const ratio = internal / (external || 1);
@@ -66,11 +69,13 @@ export const internalLinkingRules = [
66
69
  severity: 'info',
67
70
  description: 'Internal links should use diverse, descriptive anchor text',
68
71
  check: (ctx) => {
69
- if (!ctx.allLinks || ctx.allLinks.length === 0)
70
- return null;
72
+ if (!ctx.allLinks || ctx.allLinks.length === 0) {
73
+ return createResult({ id: 'linking-anchor-diversity', name: 'Anchor Text Diversity', category: 'links', severity: 'info' }, 'info', 'Not applicable (no links detected)', { recommendation: 'This rule checks anchor text diversity to prevent over-optimization' });
74
+ }
71
75
  const internalLinks = ctx.allLinks.filter(l => l.type === 'internal');
72
- if (internalLinks.length < 3)
73
- return null;
76
+ if (internalLinks.length < 3) {
77
+ return createResult({ id: 'linking-anchor-diversity', name: 'Anchor Text Diversity', category: 'links', severity: 'info' }, 'info', 'Not applicable (insufficient internal links)', { recommendation: 'This rule requires at least 3 internal links to analyze anchor text diversity' });
78
+ }
74
79
  const anchorCounts = {};
75
80
  for (const link of internalLinks) {
76
81
  const anchor = (link.text || '').toLowerCase().trim();
@@ -100,11 +105,13 @@ export const internalLinkingRules = [
100
105
  severity: 'info',
101
106
  description: 'Pages should link to deep content, not just homepage',
102
107
  check: (ctx) => {
103
- if (!ctx.allLinks || ctx.allLinks.length === 0)
104
- return null;
108
+ if (!ctx.allLinks || ctx.allLinks.length === 0) {
109
+ return createResult({ id: 'linking-deep-links', name: 'Deep Linking', category: 'links', severity: 'info' }, 'info', 'Not applicable (no links detected)', { recommendation: 'This rule checks for deep linking patterns to ensure inner pages are properly linked' });
110
+ }
105
111
  const internalLinks = ctx.allLinks.filter(l => l.type === 'internal');
106
- if (internalLinks.length === 0)
107
- return null;
112
+ if (internalLinks.length === 0) {
113
+ return createResult({ id: 'linking-deep-links', name: 'Deep Linking', category: 'links', severity: 'info' }, 'info', 'Not applicable (no internal links detected)', { recommendation: 'This rule requires internal links to analyze deep linking patterns' });
114
+ }
108
115
  let rootLinks = 0;
109
116
  let deepLinks = 0;
110
117
  for (const link of internalLinks) {
@@ -142,10 +149,12 @@ export const internalLinkingRules = [
142
149
  severity: 'info',
143
150
  description: 'Check for proper navigation link structure',
144
151
  check: (ctx) => {
145
- if (!ctx.hasNav)
146
- return null;
147
- if (ctx.navLinkCount === undefined)
148
- return null;
152
+ if (!ctx.hasNav) {
153
+ return createResult({ id: 'linking-nav-links', name: 'Navigation Links', category: 'links', severity: 'info' }, 'info', 'Not applicable (no navigation element detected)', { recommendation: 'This rule checks navigation link structure when a <nav> element is present' });
154
+ }
155
+ if (ctx.navLinkCount === undefined) {
156
+ return createResult({ id: 'linking-nav-links', name: 'Navigation Links', category: 'links', severity: 'info' }, 'info', 'Not applicable (navigation link count unavailable)', { recommendation: 'This rule checks the number of links in navigation elements' });
157
+ }
149
158
  if (ctx.navLinkCount === 0) {
150
159
  return createResult({ id: 'linking-nav-links', name: 'Navigation Links', category: 'links', severity: 'info' }, 'warn', 'Navigation element has no links', {
151
160
  recommendation: 'Add links to navigation for user experience and SEO',
@@ -175,10 +184,12 @@ export const internalLinkingRules = [
175
184
  severity: 'info',
176
185
  description: 'Footer should contain important site-wide links',
177
186
  check: (ctx) => {
178
- if (!ctx.hasFooter)
179
- return null;
180
- if (ctx.footerLinkCount === undefined)
181
- return null;
187
+ if (!ctx.hasFooter) {
188
+ return createResult({ id: 'linking-footer-links', name: 'Footer Links', category: 'links', severity: 'info' }, 'info', 'Not applicable (no footer element detected)', { recommendation: 'This rule checks footer link structure when a <footer> element is present' });
189
+ }
190
+ if (ctx.footerLinkCount === undefined) {
191
+ return createResult({ id: 'linking-footer-links', name: 'Footer Links', category: 'links', severity: 'info' }, 'info', 'Not applicable (footer link count unavailable)', { recommendation: 'This rule checks the number of links in footer elements' });
192
+ }
182
193
  if (ctx.footerLinkCount === 0) {
183
194
  return createResult({ id: 'linking-footer-links', name: 'Footer Links', category: 'links', severity: 'info' }, 'info', 'Footer has no links', {
184
195
  recommendation: 'Add important links to footer',
@@ -206,10 +217,12 @@ export const internalLinkingRules = [
206
217
  severity: 'info',
207
218
  description: 'Check for in-content contextual links',
208
219
  check: (ctx) => {
209
- if (ctx.contextualLinkCount === undefined)
210
- return null;
211
- if (!ctx.wordCount || ctx.wordCount < 300)
212
- return null;
220
+ if (ctx.contextualLinkCount === undefined) {
221
+ return createResult({ id: 'linking-contextual', name: 'Contextual Links', category: 'links', severity: 'info' }, 'info', 'Not applicable (contextual link count unavailable)', { recommendation: 'This rule checks for in-content contextual links that pass more link equity' });
222
+ }
223
+ if (!ctx.wordCount || ctx.wordCount < 300) {
224
+ return createResult({ id: 'linking-contextual', name: 'Contextual Links', category: 'links', severity: 'info' }, 'info', 'Not applicable (insufficient content for contextual link analysis)', { recommendation: 'This rule requires at least 300 words to analyze contextual link patterns' });
225
+ }
213
226
  const count = ctx.contextualLinkCount;
214
227
  if (count === 0) {
215
228
  return createResult({ id: 'linking-contextual', name: 'Contextual Links', category: 'links', severity: 'info' }, 'info', 'No contextual links in content', {
@@ -240,8 +253,9 @@ export const internalLinkingRules = [
240
253
  severity: 'warning',
241
254
  description: 'Pages should be linked from other pages on the site',
242
255
  check: (ctx) => {
243
- if (ctx.incomingInternalLinks === undefined)
244
- return null;
256
+ if (ctx.incomingInternalLinks === undefined) {
257
+ return createResult({ id: 'linking-orphan-page', name: 'Orphan Page Detection', category: 'links', severity: 'warning' }, 'info', 'Not applicable (incoming internal links data unavailable)', { recommendation: 'This rule checks if pages receive incoming internal links to prevent orphan pages' });
258
+ }
245
259
  if (ctx.incomingInternalLinks === 0) {
246
260
  return createResult({ id: 'linking-orphan-page', name: 'Orphan Page Detection', category: 'links', severity: 'warning' }, 'warn', 'Page may be an orphan (no incoming internal links)', {
247
261
  recommendation: 'Link to this page from other pages on your site',
@@ -262,8 +276,9 @@ export const internalLinkingRules = [
262
276
  severity: 'info',
263
277
  description: 'Avoid excessive self-referencing links',
264
278
  check: (ctx) => {
265
- if (ctx.selfReferencingLinks === undefined)
266
- return null;
279
+ if (ctx.selfReferencingLinks === undefined) {
280
+ return createResult({ id: 'linking-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 that waste crawl budget' });
281
+ }
267
282
  if (ctx.selfReferencingLinks > 3) {
268
283
  return createResult({ id: 'linking-self-referencing', name: 'Self-Referencing Links', category: 'links', severity: 'info' }, 'info', `${ctx.selfReferencingLinks} self-referencing links`, {
269
284
  recommendation: 'Reduce links that point to the current page',
@@ -274,7 +289,7 @@ export const internalLinkingRules = [
274
289
  },
275
290
  });
276
291
  }
277
- return null;
292
+ return createResult({ id: 'linking-self-referencing', name: 'Self-Referencing Links', category: 'links', severity: 'info' }, 'info', 'Not applicable (acceptable self-referencing link count)', { recommendation: 'Self-referencing links are acceptable at current level (0-3 links)' });
278
293
  },
279
294
  },
280
295
  {
@@ -284,8 +299,9 @@ export const internalLinkingRules = [
284
299
  severity: 'error',
285
300
  description: 'Internal links should not be broken',
286
301
  check: (ctx) => {
287
- if (ctx.brokenInternalLinks === undefined)
288
- return null;
302
+ if (ctx.brokenInternalLinks === undefined) {
303
+ return createResult({ id: 'linking-broken-internal', name: 'Broken Internal Links', category: 'links', severity: 'error' }, 'info', 'Not applicable (broken internal links data unavailable)', { recommendation: 'This rule checks for broken internal links that harm user experience and waste crawl budget' });
304
+ }
289
305
  if (ctx.brokenInternalLinks.length > 0) {
290
306
  return createResult({ id: 'linking-broken-internal', name: 'Broken Internal Links', category: 'links', severity: 'error' }, 'fail', `${ctx.brokenInternalLinks.length} broken internal link(s)`, {
291
307
  recommendation: 'Fix or remove broken internal links',
@@ -306,8 +322,9 @@ export const internalLinkingRules = [
306
322
  severity: 'warning',
307
323
  description: 'Internal links should not go through redirect chains',
308
324
  check: (ctx) => {
309
- if (ctx.redirectChainLinks === undefined)
310
- return null;
325
+ if (ctx.redirectChainLinks === undefined) {
326
+ return createResult({ id: 'linking-redirect-chains', name: 'Redirect Chains', category: 'links', severity: 'warning' }, 'info', 'Not applicable (redirect chain data unavailable)', { recommendation: 'This rule checks for redirect chains that slow crawling and lose link equity' });
327
+ }
311
328
  if (ctx.redirectChainLinks.length > 0) {
312
329
  return createResult({ id: 'linking-redirect-chains', name: 'Redirect Chains', category: 'links', severity: 'warning' }, 'warn', `${ctx.redirectChainLinks.length} link(s) go through redirects`, {
313
330
  recommendation: 'Update links to point to final destination URLs',
@@ -328,8 +345,9 @@ export const internalLinkingRules = [
328
345
  severity: 'warning',
329
346
  description: 'Internal links should not use nofollow',
330
347
  check: (ctx) => {
331
- if (!ctx.allLinks)
332
- return null;
348
+ if (!ctx.allLinks) {
349
+ return createResult({ id: 'linking-nofollow-internal', name: 'Nofollow Internal Links', category: 'links', severity: 'warning' }, 'info', 'Not applicable (links data unavailable)', { recommendation: 'This rule checks for nofollow attributes on internal links which waste PageRank' });
350
+ }
333
351
  const nofollowInternal = ctx.allLinks.filter(l => l.type === 'internal' && l.rel?.includes('nofollow'));
334
352
  if (nofollowInternal.length > 0) {
335
353
  return createResult({ id: 'linking-nofollow-internal', name: 'Nofollow Internal Links', category: 'links', severity: 'warning' }, 'warn', `${nofollowInternal.length} internal link(s) have nofollow`, {
@@ -351,8 +369,9 @@ export const internalLinkingRules = [
351
369
  severity: 'info',
352
370
  description: 'Important pages should be reachable in few clicks',
353
371
  check: (ctx) => {
354
- if (ctx.pageClickDepth === undefined)
355
- return null;
372
+ if (ctx.pageClickDepth === undefined) {
373
+ return createResult({ id: 'linking-click-depth', name: 'Click Depth', category: 'links', severity: 'info' }, 'info', 'Not applicable (click depth data unavailable)', { recommendation: 'This rule checks how many clicks from homepage, affecting crawl priority and link equity' });
374
+ }
356
375
  const depth = ctx.pageClickDepth;
357
376
  if (depth > 4) {
358
377
  return createResult({ id: 'linking-click-depth', name: 'Click Depth', category: 'links', severity: 'info' }, 'warn', `Page is ${depth} clicks from homepage`, {
@@ -36,8 +36,9 @@ export const linkRules = [
36
36
  severity: 'warning',
37
37
  description: 'Links should have descriptive anchor text',
38
38
  check: (ctx) => {
39
- if (ctx.totalLinks === undefined || ctx.totalLinks === 0)
40
- return null;
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
+ }
41
42
  const withoutText = ctx.problematicLinks?.withoutText ?? [];
42
43
  if (withoutText.length > 0) {
43
44
  const examples = withoutText.slice(0, 3).map((l) => l.href).join(', ');
@@ -73,7 +74,7 @@ export const linkRules = [
73
74
  },
74
75
  });
75
76
  }
76
- return null;
77
+ return createResult({ id: 'links-generic-text', name: 'Generic Link Text', category: 'links', severity: 'warning' }, 'pass', 'All links have descriptive anchor text');
77
78
  },
78
79
  },
79
80
  {
@@ -83,8 +84,9 @@ export const linkRules = [
83
84
  severity: 'info',
84
85
  description: 'Page should have at least 3 internal links',
85
86
  check: (ctx) => {
86
- if (ctx.internalLinks === undefined)
87
- return null;
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
+ }
88
90
  const min = SEO_THRESHOLDS.links.minInternal;
89
91
  if (ctx.internalLinks < min) {
90
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` });
@@ -99,13 +101,22 @@ export const linkRules = [
99
101
  severity: 'info',
100
102
  description: 'Page should not have too many external links',
101
103
  check: (ctx) => {
102
- if (ctx.externalLinks === undefined)
103
- return null;
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
+ }
104
107
  const max = SEO_THRESHOLDS.links.maxExternal;
105
108
  if (ctx.externalLinks > max) {
106
- return createResult({ id: 'links-external-count', name: 'External Links', category: 'links', severity: 'info' }, 'warn', `Too many external links (${ctx.externalLinks})`, { value: ctx.externalLinks, recommendation: `Reduce external links to under ${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
+ });
107
118
  }
108
- return null;
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 });
109
120
  },
110
121
  },
111
122
  {
@@ -128,7 +139,7 @@ export const linkRules = [
128
139
  },
129
140
  });
130
141
  }
131
- return null;
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');
132
143
  },
133
144
  },
134
145
  {
@@ -150,7 +161,7 @@ export const linkRules = [
150
161
  },
151
162
  });
152
163
  }
153
- return null;
164
+ return createResult({ id: 'links-external-noreferrer', name: 'External Links Noreferrer', category: 'security', severity: 'info' }, 'pass', 'External links have appropriate privacy attributes');
154
165
  },
155
166
  },
156
167
  {
@@ -160,8 +171,9 @@ export const linkRules = [
160
171
  severity: 'info',
161
172
  description: 'Rel attributes `sponsored` and `ugc` should be used for paid or user-generated content links.',
162
173
  check: (ctx) => {
163
- if (!ctx.totalLinks)
164
- return null;
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
+ }
165
177
  let messages = [];
166
178
  if (ctx.sponsoredLinks && ctx.sponsoredLinks > 0) {
167
179
  messages.push(`${ctx.sponsoredLinks} link(s) with rel="sponsored".`);
@@ -172,7 +184,7 @@ export const linkRules = [
172
184
  if (messages.length > 0) {
173
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.' });
174
186
  }
175
- return null;
187
+ return createResult({ id: 'links-sponsored-ugc-directives', name: 'Sponsored/UGC Links', category: 'links', severity: 'info' }, 'pass', 'No sponsored or UGC links detected');
176
188
  },
177
189
  },
178
190
  {
@@ -182,8 +194,9 @@ export const linkRules = [
182
194
  severity: 'warning',
183
195
  description: 'Anchor text should describe the link destination',
184
196
  check: (ctx) => {
185
- if (!ctx.allLinks || ctx.allLinks.length === 0)
186
- return null;
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
+ }
187
200
  const nonDescriptive = ctx.allLinks.filter((link) => link.text && isGenericAnchorText(link.text));
188
201
  if (nonDescriptive.length > 0) {
189
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`, {
@@ -196,7 +209,7 @@ export const linkRules = [
196
209
  },
197
210
  });
198
211
  }
199
- return null;
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');
200
213
  },
201
214
  },
202
215
  {
@@ -206,8 +219,9 @@ export const linkRules = [
206
219
  severity: 'info',
207
220
  description: 'Pages should not link to themselves excessively',
208
221
  check: (ctx) => {
209
- if (!ctx.selfReferencingLinks)
210
- return null;
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
+ }
211
225
  if (ctx.selfReferencingLinks > 3) {
212
226
  return createResult({ id: 'links-self-referencing', name: 'Self-Referencing Links', category: 'links', severity: 'info' }, 'info', `${ctx.selfReferencingLinks} self-referencing link(s)`, {
213
227
  value: ctx.selfReferencingLinks,
@@ -217,7 +231,7 @@ export const linkRules = [
217
231
  },
218
232
  });
219
233
  }
220
- return null;
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 });
221
235
  },
222
236
  },
223
237
  {
@@ -227,8 +241,9 @@ export const linkRules = [
227
241
  severity: 'error',
228
242
  description: 'Internal links should not return 4xx errors',
229
243
  check: (ctx) => {
230
- if (!ctx.brokenInternalLinks || ctx.brokenInternalLinks.length === 0)
231
- return null;
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
+ }
232
247
  return createResult({ id: 'links-broken-internal', name: 'Broken Internal Links', category: 'links', severity: 'error' }, 'fail', `${ctx.brokenInternalLinks.length} broken internal link(s)`, {
233
248
  value: ctx.brokenInternalLinks.length,
234
249
  recommendation: 'Fix or remove broken internal links',
@@ -246,8 +261,9 @@ export const linkRules = [
246
261
  severity: 'warning',
247
262
  description: 'External links should not return 4xx errors',
248
263
  check: (ctx) => {
249
- if (!ctx.brokenExternalLinks || ctx.brokenExternalLinks.length === 0)
250
- return null;
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
+ }
251
267
  return createResult({ id: 'links-broken-external', name: 'Broken External Links', category: 'links', severity: 'warning' }, 'warn', `${ctx.brokenExternalLinks.length} broken external link(s)`, {
252
268
  value: ctx.brokenExternalLinks.length,
253
269
  recommendation: 'Update or remove broken external links',
@@ -265,8 +281,9 @@ export const linkRules = [
265
281
  severity: 'warning',
266
282
  description: 'Internal links should not point to redirect chains',
267
283
  check: (ctx) => {
268
- if (!ctx.redirectChainLinks || ctx.redirectChainLinks.length === 0)
269
- return null;
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
+ }
270
287
  return createResult({ id: 'links-redirect-chains', name: 'Redirect Chain Links', category: 'links', severity: 'warning' }, 'warn', `${ctx.redirectChainLinks.length} link(s) with redirect chains`, {
271
288
  value: ctx.redirectChainLinks.length,
272
289
  recommendation: 'Update links to point directly to final destination',
@@ -284,8 +301,9 @@ export const linkRules = [
284
301
  severity: 'warning',
285
302
  description: 'Pages should have at least 3 internal links',
286
303
  check: (ctx) => {
287
- if (ctx.internalLinks === undefined)
288
- return null;
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
+ }
289
307
  if (ctx.internalLinks < 3) {
290
308
  return createResult({ id: 'links-few-internal', name: 'Pages with Few Internal Links', category: 'links', severity: 'warning' }, 'warn', `Only ${ctx.internalLinks} internal link(s)`, {
291
309
  value: ctx.internalLinks,
@@ -297,7 +315,7 @@ export const linkRules = [
297
315
  },
298
316
  });
299
317
  }
300
- return null;
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 });
301
319
  },
302
320
  },
303
321
  {
@@ -307,10 +325,12 @@ export const linkRules = [
307
325
  severity: 'warning',
308
326
  description: 'Pages should have incoming internal links',
309
327
  check: (ctx) => {
310
- if (ctx.incomingInternalLinks === undefined)
311
- return null;
312
- if (ctx.isStartPage)
313
- return null;
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
+ }
314
334
  if (ctx.incomingInternalLinks === 0) {
315
335
  return createResult({ id: 'links-orphan-page', name: 'Orphan Page Detection', category: 'links', severity: 'warning' }, 'warn', 'Page has no incoming internal links (orphan)', {
316
336
  value: 0,
@@ -320,7 +340,7 @@ export const linkRules = [
320
340
  },
321
341
  });
322
342
  }
323
- return null;
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 });
324
344
  },
325
345
  },
326
346
  {
@@ -330,8 +350,9 @@ export const linkRules = [
330
350
  severity: 'warning',
331
351
  description: 'Important pages should be within 3 clicks from homepage',
332
352
  check: (ctx) => {
333
- if (ctx.clickDepth === undefined)
334
- return null;
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
+ }
335
356
  if (ctx.clickDepth > 3) {
336
357
  return createResult({ id: 'links-click-depth', name: 'Page Click Depth', category: 'links', severity: 'warning' }, 'warn', `Page is ${ctx.clickDepth} clicks from homepage`, {
337
358
  value: ctx.clickDepth,
@@ -353,10 +374,12 @@ export const linkRules = [
353
374
  severity: 'info',
354
375
  description: 'Pages should have more internal than external links',
355
376
  check: (ctx) => {
356
- if (ctx.internalLinks === undefined || ctx.externalLinks === undefined)
357
- return null;
358
- if (ctx.totalLinks === undefined || ctx.totalLinks === 0)
359
- return null;
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
+ }
360
383
  const externalRatio = (ctx.externalLinks / ctx.totalLinks) * 100;
361
384
  if (externalRatio > 70) {
362
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)}%)`, {
@@ -369,7 +392,7 @@ export const linkRules = [
369
392
  },
370
393
  });
371
394
  }
372
- return null;
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) });
373
396
  },
374
397
  },
375
398
  {
@@ -379,8 +402,9 @@ export const linkRules = [
379
402
  severity: 'info',
380
403
  description: 'Internal links with nofollow prevent PageRank flow',
381
404
  check: (ctx) => {
382
- if (!ctx.nofollowInternalLinks || ctx.nofollowInternalLinks === 0)
383
- return null;
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
+ }
384
408
  return createResult({ id: 'links-nofollow-internal', name: 'Internal Nofollow Links', category: 'links', severity: 'info' }, 'info', `${ctx.nofollowInternalLinks} internal link(s) with nofollow`, {
385
409
  value: ctx.nofollowInternalLinks,
386
410
  recommendation: 'Remove nofollow from internal links unless intentional',
@@ -397,8 +421,9 @@ export const linkRules = [
397
421
  severity: 'warning',
398
422
  description: 'Pages should not have more than 3,000 links',
399
423
  check: (ctx) => {
400
- if (ctx.totalLinks === undefined)
401
- return null;
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
+ }
402
427
  if (ctx.totalLinks > 3000) {
403
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)`, {
404
429
  value: ctx.totalLinks,
@@ -421,7 +446,7 @@ export const linkRules = [
421
446
  }
422
447
  });
423
448
  }
424
- return null;
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 });
425
450
  },
426
451
  },
427
452
  {
@@ -431,8 +456,9 @@ export const linkRules = [
431
456
  severity: 'info',
432
457
  description: 'Links should point to pages, not raw resources like images',
433
458
  check: (ctx) => {
434
- if (ctx.linksToResources === undefined)
435
- return null;
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
+ }
436
462
  if (ctx.linksToResources > 0) {
437
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)`, {
438
464
  value: ctx.linksToResources,
@@ -443,7 +469,7 @@ export const linkRules = [
443
469
  }
444
470
  });
445
471
  }
446
- return null;
472
+ return createResult({ id: 'links-to-resources', name: 'Links to Resources', category: 'links', severity: 'info' }, 'pass', 'No direct links to raw resources detected');
447
473
  },
448
474
  },
449
475
  {
@@ -453,8 +479,9 @@ export const linkRules = [
453
479
  severity: 'warning',
454
480
  description: 'External links should not return 403 Forbidden',
455
481
  check: (ctx) => {
456
- if (ctx.forbidden403Links === undefined)
457
- return null;
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
+ }
458
485
  if (ctx.forbidden403Links > 0) {
459
486
  return createResult({ id: 'links-403-forbidden', name: '403 Forbidden Links', category: 'links', severity: 'warning' }, 'warn', `${ctx.forbidden403Links} external links return 403 Forbidden`, {
460
487
  value: ctx.forbidden403Links,
@@ -465,7 +492,7 @@ export const linkRules = [
465
492
  }
466
493
  });
467
494
  }
468
- return null;
495
+ return createResult({ id: 'links-403-forbidden', name: '403 Forbidden Links', category: 'links', severity: 'warning' }, 'pass', 'No forbidden (403) links detected');
469
496
  },
470
497
  },
471
498
  ];