recker 1.0.42 → 1.0.43-next.7a08602

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (458) hide show
  1. package/dist/bin/recker-linux-x64 +0 -0
  2. package/dist/bin/recker-macos-x64 +0 -0
  3. package/dist/bin/recker-win-x64.exe +0 -0
  4. package/dist/bin/rek.cjs +93958 -0
  5. package/dist/browser/ai/adaptive-timeout.d.ts +50 -0
  6. package/dist/browser/ai/adaptive-timeout.js +208 -0
  7. package/dist/browser/ai/client.d.ts +22 -0
  8. package/dist/browser/ai/client.js +294 -0
  9. package/dist/browser/ai/index.d.ts +14 -0
  10. package/dist/browser/ai/index.js +11 -0
  11. package/dist/browser/ai/providers/anthropic.d.ts +63 -0
  12. package/dist/browser/ai/providers/anthropic.js +370 -0
  13. package/dist/browser/ai/providers/base.d.ts +48 -0
  14. package/dist/browser/ai/providers/base.js +150 -0
  15. package/dist/browser/ai/providers/google.d.ts +59 -0
  16. package/dist/browser/ai/providers/google.js +305 -0
  17. package/dist/browser/ai/providers/ollama.d.ts +44 -0
  18. package/dist/browser/ai/providers/ollama.js +240 -0
  19. package/dist/browser/ai/providers/openai.d.ts +64 -0
  20. package/dist/browser/ai/providers/openai.js +298 -0
  21. package/dist/browser/ai/rate-limiter.d.ts +43 -0
  22. package/dist/browser/ai/rate-limiter.js +215 -0
  23. package/dist/browser/ai/vector/index.d.ts +2 -0
  24. package/dist/browser/ai/vector/index.js +2 -0
  25. package/dist/browser/ai/vector/similarity.d.ts +2 -0
  26. package/dist/browser/ai/vector/similarity.js +27 -0
  27. package/dist/browser/ai/vector/store.d.ts +27 -0
  28. package/dist/browser/ai/vector/store.js +82 -0
  29. package/dist/browser/browser/cache.d.ts +2 -40
  30. package/dist/browser/browser/cache.js +2 -199
  31. package/dist/browser/browser/index.d.ts +8 -0
  32. package/dist/browser/browser/index.js +8 -0
  33. package/dist/browser/browser/recker.d.ts +8 -1
  34. package/dist/browser/browser/recker.js +8 -2
  35. package/dist/browser/cache/indexed-db.d.ts +10 -0
  36. package/dist/browser/cache/indexed-db.js +88 -0
  37. package/dist/browser/cache/service-worker-cache.d.ts +18 -0
  38. package/dist/browser/cache/service-worker-cache.js +103 -0
  39. package/dist/browser/cache.d.ts +2 -40
  40. package/dist/browser/cache.js +2 -199
  41. package/dist/browser/constants/user-agents.d.ts +7 -0
  42. package/dist/browser/constants/user-agents.js +7 -0
  43. package/dist/browser/core/client.d.ts +2 -0
  44. package/dist/browser/core/client.js +19 -1
  45. package/dist/browser/index.d.ts +8 -0
  46. package/dist/browser/index.js +8 -0
  47. package/dist/browser/plugins/har-recorder.d.ts +40 -0
  48. package/dist/browser/plugins/har-recorder.js +120 -0
  49. package/dist/browser/plugins/network-simulation.d.ts +7 -0
  50. package/dist/browser/plugins/network-simulation.js +13 -0
  51. package/dist/browser/presets/android.d.ts +2 -0
  52. package/dist/browser/presets/android.js +16 -0
  53. package/dist/browser/presets/anthropic.d.ts +8 -0
  54. package/dist/browser/presets/anthropic.js +27 -0
  55. package/dist/browser/presets/aws.d.ts +19 -0
  56. package/dist/browser/presets/aws.js +68 -0
  57. package/dist/browser/presets/azure-openai.d.ts +10 -0
  58. package/dist/browser/presets/azure-openai.js +35 -0
  59. package/dist/browser/presets/azure.d.ts +41 -0
  60. package/dist/browser/presets/azure.js +104 -0
  61. package/dist/browser/presets/chaturbate.d.ts +2 -0
  62. package/dist/browser/presets/chaturbate.js +17 -0
  63. package/dist/browser/presets/cloudflare.d.ts +12 -0
  64. package/dist/browser/presets/cloudflare.js +39 -0
  65. package/dist/browser/presets/cohere.d.ts +7 -0
  66. package/dist/browser/presets/cohere.js +22 -0
  67. package/dist/browser/presets/deepseek.d.ts +7 -0
  68. package/dist/browser/presets/deepseek.js +22 -0
  69. package/dist/browser/presets/digitalocean.d.ts +5 -0
  70. package/dist/browser/presets/digitalocean.js +16 -0
  71. package/dist/browser/presets/discord.d.ts +6 -0
  72. package/dist/browser/presets/discord.js +17 -0
  73. package/dist/browser/presets/elevenlabs.d.ts +6 -0
  74. package/dist/browser/presets/elevenlabs.js +20 -0
  75. package/dist/browser/presets/enhancers.d.ts +20 -0
  76. package/dist/browser/presets/enhancers.js +85 -0
  77. package/dist/browser/presets/fireworks.d.ts +7 -0
  78. package/dist/browser/presets/fireworks.js +22 -0
  79. package/dist/browser/presets/gcp.d.ts +34 -0
  80. package/dist/browser/presets/gcp.js +91 -0
  81. package/dist/browser/presets/gemini.d.ts +7 -0
  82. package/dist/browser/presets/gemini.js +23 -0
  83. package/dist/browser/presets/github.d.ts +6 -0
  84. package/dist/browser/presets/github.js +17 -0
  85. package/dist/browser/presets/gitlab.d.ts +6 -0
  86. package/dist/browser/presets/gitlab.js +16 -0
  87. package/dist/browser/presets/groq.d.ts +7 -0
  88. package/dist/browser/presets/groq.js +22 -0
  89. package/dist/browser/presets/hubspot.d.ts +9 -0
  90. package/dist/browser/presets/hubspot.js +28 -0
  91. package/dist/browser/presets/huggingface.d.ts +7 -0
  92. package/dist/browser/presets/huggingface.js +23 -0
  93. package/dist/browser/presets/index.d.ts +47 -0
  94. package/dist/browser/presets/index.js +47 -0
  95. package/dist/browser/presets/ios.d.ts +2 -0
  96. package/dist/browser/presets/ios.js +13 -0
  97. package/dist/browser/presets/linear.d.ts +5 -0
  98. package/dist/browser/presets/linear.js +16 -0
  99. package/dist/browser/presets/mailgun.d.ts +7 -0
  100. package/dist/browser/presets/mailgun.js +20 -0
  101. package/dist/browser/presets/meta.d.ts +10 -0
  102. package/dist/browser/presets/meta.js +33 -0
  103. package/dist/browser/presets/mistral.d.ts +7 -0
  104. package/dist/browser/presets/mistral.js +22 -0
  105. package/dist/browser/presets/notion.d.ts +6 -0
  106. package/dist/browser/presets/notion.js +17 -0
  107. package/dist/browser/presets/openai.d.ts +9 -0
  108. package/dist/browser/presets/openai.js +30 -0
  109. package/dist/browser/presets/oracle.d.ts +19 -0
  110. package/dist/browser/presets/oracle.js +117 -0
  111. package/dist/browser/presets/perplexity.d.ts +7 -0
  112. package/dist/browser/presets/perplexity.js +22 -0
  113. package/dist/browser/presets/pinecone.d.ts +8 -0
  114. package/dist/browser/presets/pinecone.js +42 -0
  115. package/dist/browser/presets/registry.d.ts +23 -0
  116. package/dist/browser/presets/registry.js +519 -0
  117. package/dist/browser/presets/replicate.d.ts +7 -0
  118. package/dist/browser/presets/replicate.js +23 -0
  119. package/dist/browser/presets/sendgrid.d.ts +6 -0
  120. package/dist/browser/presets/sendgrid.js +20 -0
  121. package/dist/browser/presets/sentry.d.ts +11 -0
  122. package/dist/browser/presets/sentry.js +48 -0
  123. package/dist/browser/presets/sinch.d.ts +9 -0
  124. package/dist/browser/presets/sinch.js +39 -0
  125. package/dist/browser/presets/slack.d.ts +5 -0
  126. package/dist/browser/presets/slack.js +16 -0
  127. package/dist/browser/presets/square.d.ts +10 -0
  128. package/dist/browser/presets/square.js +33 -0
  129. package/dist/browser/presets/stripe.d.ts +7 -0
  130. package/dist/browser/presets/stripe.js +23 -0
  131. package/dist/browser/presets/supabase.d.ts +6 -0
  132. package/dist/browser/presets/supabase.js +18 -0
  133. package/dist/browser/presets/tiktok.d.ts +10 -0
  134. package/dist/browser/presets/tiktok.js +38 -0
  135. package/dist/browser/presets/together.d.ts +7 -0
  136. package/dist/browser/presets/together.js +22 -0
  137. package/dist/browser/presets/twilio.d.ts +6 -0
  138. package/dist/browser/presets/twilio.js +17 -0
  139. package/dist/browser/presets/vercel.d.ts +6 -0
  140. package/dist/browser/presets/vercel.js +23 -0
  141. package/dist/browser/presets/vultr.d.ts +5 -0
  142. package/dist/browser/presets/vultr.js +16 -0
  143. package/dist/browser/presets/xai.d.ts +8 -0
  144. package/dist/browser/presets/xai.js +23 -0
  145. package/dist/browser/presets/youtube.d.ts +5 -0
  146. package/dist/browser/presets/youtube.js +20 -0
  147. package/dist/browser/recker.d.ts +8 -1
  148. package/dist/browser/recker.js +8 -2
  149. package/dist/browser/scrape/document.d.ts +5 -4
  150. package/dist/browser/scrape/document.js +89 -76
  151. package/dist/browser/scrape/element.d.ts +10 -8
  152. package/dist/browser/scrape/element.js +295 -81
  153. package/dist/browser/scrape/extractors.d.ts +11 -11
  154. package/dist/browser/scrape/extractors.js +145 -113
  155. package/dist/browser/scrape/parser/back.d.ts +1 -0
  156. package/dist/browser/scrape/parser/back.js +3 -0
  157. package/dist/browser/scrape/parser/index.d.ts +20 -0
  158. package/dist/browser/scrape/parser/index.js +19 -0
  159. package/dist/browser/scrape/parser/matcher.d.ts +30 -0
  160. package/dist/browser/scrape/parser/matcher.js +99 -0
  161. package/dist/browser/scrape/parser/nodes/comment.d.ts +12 -0
  162. package/dist/browser/scrape/parser/nodes/comment.js +21 -0
  163. package/dist/browser/scrape/parser/nodes/html.d.ts +110 -0
  164. package/dist/browser/scrape/parser/nodes/html.js +978 -0
  165. package/dist/browser/scrape/parser/nodes/node.d.ts +18 -0
  166. package/dist/browser/scrape/parser/nodes/node.js +31 -0
  167. package/dist/browser/scrape/parser/nodes/text.d.ts +14 -0
  168. package/dist/browser/scrape/parser/nodes/text.js +30 -0
  169. package/dist/browser/scrape/parser/nodes/type.d.ts +6 -0
  170. package/dist/browser/scrape/parser/nodes/type.js +7 -0
  171. package/dist/browser/scrape/parser/parse.d.ts +1 -0
  172. package/dist/browser/scrape/parser/parse.js +1 -0
  173. package/dist/browser/scrape/parser/valid.d.ts +2 -0
  174. package/dist/browser/scrape/parser/valid.js +5 -0
  175. package/dist/browser/scrape/parser/void-tag.d.ts +7 -0
  176. package/dist/browser/scrape/parser/void-tag.js +43 -0
  177. package/dist/browser/scrape/types.d.ts +7 -0
  178. package/dist/browser/seo/analyzer.d.ts +59 -0
  179. package/dist/browser/seo/analyzer.js +1399 -0
  180. package/dist/browser/seo/keywords.d.ts +16 -0
  181. package/dist/browser/seo/keywords.js +55 -0
  182. package/dist/browser/seo/rules/accessibility.d.ts +2 -0
  183. package/dist/browser/seo/rules/accessibility.js +722 -0
  184. package/dist/browser/seo/rules/ai-search.d.ts +2 -0
  185. package/dist/browser/seo/rules/ai-search.js +436 -0
  186. package/dist/browser/seo/rules/analytics.d.ts +2 -0
  187. package/dist/browser/seo/rules/analytics.js +306 -0
  188. package/dist/browser/seo/rules/best-practices.d.ts +2 -0
  189. package/dist/browser/seo/rules/best-practices.js +195 -0
  190. package/dist/browser/seo/rules/canonical.d.ts +12 -0
  191. package/dist/browser/seo/rules/canonical.js +258 -0
  192. package/dist/browser/seo/rules/content.d.ts +2 -0
  193. package/dist/browser/seo/rules/content.js +424 -0
  194. package/dist/browser/seo/rules/crawl.d.ts +2 -0
  195. package/dist/browser/seo/rules/crawl.js +435 -0
  196. package/dist/browser/seo/rules/cwv.d.ts +2 -0
  197. package/dist/browser/seo/rules/cwv.js +248 -0
  198. package/dist/browser/seo/rules/ecommerce.d.ts +2 -0
  199. package/dist/browser/seo/rules/ecommerce.js +283 -0
  200. package/dist/browser/seo/rules/i18n.d.ts +2 -0
  201. package/dist/browser/seo/rules/i18n.js +277 -0
  202. package/dist/browser/seo/rules/images.d.ts +2 -0
  203. package/dist/browser/seo/rules/images.js +235 -0
  204. package/dist/browser/seo/rules/index.d.ts +52 -0
  205. package/dist/browser/seo/rules/index.js +159 -0
  206. package/dist/browser/seo/rules/internal-linking.d.ts +2 -0
  207. package/dist/browser/seo/rules/internal-linking.js +394 -0
  208. package/dist/browser/seo/rules/links.d.ts +2 -0
  209. package/dist/browser/seo/rules/links.js +490 -0
  210. package/dist/browser/seo/rules/local.d.ts +2 -0
  211. package/dist/browser/seo/rules/local.js +289 -0
  212. package/dist/browser/seo/rules/meta.d.ts +2 -0
  213. package/dist/browser/seo/rules/meta.js +646 -0
  214. package/dist/browser/seo/rules/mobile.d.ts +2 -0
  215. package/dist/browser/seo/rules/mobile.js +161 -0
  216. package/dist/browser/seo/rules/performance.d.ts +2 -0
  217. package/dist/browser/seo/rules/performance.js +625 -0
  218. package/dist/browser/seo/rules/pwa.d.ts +2 -0
  219. package/dist/browser/seo/rules/pwa.js +299 -0
  220. package/dist/browser/seo/rules/readability.d.ts +2 -0
  221. package/dist/browser/seo/rules/readability.js +264 -0
  222. package/dist/browser/seo/rules/redirects.d.ts +16 -0
  223. package/dist/browser/seo/rules/redirects.js +199 -0
  224. package/dist/browser/seo/rules/resources.d.ts +2 -0
  225. package/dist/browser/seo/rules/resources.js +390 -0
  226. package/dist/browser/seo/rules/schema.d.ts +2 -0
  227. package/dist/browser/seo/rules/schema.js +379 -0
  228. package/dist/browser/seo/rules/security.d.ts +2 -0
  229. package/dist/browser/seo/rules/security.js +877 -0
  230. package/dist/browser/seo/rules/social.d.ts +2 -0
  231. package/dist/browser/seo/rules/social.js +603 -0
  232. package/dist/browser/seo/rules/structural.d.ts +2 -0
  233. package/dist/browser/seo/rules/structural.js +179 -0
  234. package/dist/browser/seo/rules/technical-advanced.d.ts +10 -0
  235. package/dist/browser/seo/rules/technical-advanced.js +288 -0
  236. package/dist/browser/seo/rules/technical.d.ts +2 -0
  237. package/dist/browser/seo/rules/technical.js +480 -0
  238. package/dist/browser/seo/rules/thresholds.d.ts +196 -0
  239. package/dist/browser/seo/rules/thresholds.js +118 -0
  240. package/dist/browser/seo/rules/types.d.ts +498 -0
  241. package/dist/browser/seo/rules/types.js +11 -0
  242. package/dist/browser/seo/types.d.ts +211 -0
  243. package/dist/browser/seo/types.js +1 -0
  244. package/dist/browser/transport/curl.d.ts +4 -0
  245. package/dist/browser/transport/curl.js +101 -0
  246. package/dist/browser/transport/undici.js +1 -2
  247. package/dist/browser/transport/worker.d.ts +18 -0
  248. package/dist/browser/transport/worker.js +278 -0
  249. package/dist/browser/types/index.d.ts +4 -1
  250. package/dist/browser/utils/binary-manager.d.ts +4 -0
  251. package/dist/browser/utils/binary-manager.js +72 -0
  252. package/dist/browser/utils/user-agent.js +2 -13
  253. package/dist/cache/indexed-db.d.ts +10 -0
  254. package/dist/cache/indexed-db.js +88 -0
  255. package/dist/cache/service-worker-cache.d.ts +18 -0
  256. package/dist/cache/service-worker-cache.js +103 -0
  257. package/dist/cli/commands/ai.d.ts +2 -0
  258. package/dist/cli/commands/ai.js +162 -0
  259. package/dist/cli/commands/bench.d.ts +2 -0
  260. package/dist/cli/commands/bench.js +51 -0
  261. package/dist/cli/commands/dns.d.ts +2 -0
  262. package/dist/cli/commands/dns.js +295 -0
  263. package/dist/cli/commands/har.d.ts +2 -0
  264. package/dist/cli/commands/har.js +171 -0
  265. package/dist/cli/commands/hls.d.ts +2 -0
  266. package/dist/cli/commands/hls.js +192 -0
  267. package/dist/cli/commands/network.d.ts +2 -0
  268. package/dist/cli/commands/network.js +288 -0
  269. package/dist/cli/commands/protocols.d.ts +2 -0
  270. package/dist/cli/commands/protocols.js +344 -0
  271. package/dist/cli/commands/scrape.d.ts +2 -0
  272. package/dist/cli/commands/scrape.js +176 -0
  273. package/dist/cli/commands/security.d.ts +2 -0
  274. package/dist/cli/commands/security.js +57 -0
  275. package/dist/cli/commands/seo.d.ts +2 -0
  276. package/dist/cli/commands/seo.js +125 -0
  277. package/dist/cli/commands/serve.d.ts +2 -0
  278. package/dist/cli/commands/serve.js +531 -0
  279. package/dist/cli/commands/spider.d.ts +3 -0
  280. package/dist/cli/commands/spider.js +456 -0
  281. package/dist/cli/commands/utils.d.ts +2 -0
  282. package/dist/cli/commands/utils.js +176 -0
  283. package/dist/cli/commands/vector.d.ts +2 -0
  284. package/dist/cli/commands/vector.js +158 -0
  285. package/dist/cli/handler.d.ts +2 -2
  286. package/dist/cli/handler.js +6 -6
  287. package/dist/cli/helpers.d.ts +7 -0
  288. package/dist/cli/helpers.js +128 -0
  289. package/dist/cli/index.js +96 -5228
  290. package/dist/cli/parser/help.d.ts +2 -0
  291. package/dist/cli/parser/help.js +52 -0
  292. package/dist/cli/parser/index.d.ts +3 -0
  293. package/dist/cli/parser/index.js +3 -0
  294. package/dist/cli/parser/parser.d.ts +4 -0
  295. package/dist/cli/parser/parser.js +146 -0
  296. package/dist/cli/parser/types.d.ts +41 -0
  297. package/dist/cli/parser/types.js +1 -0
  298. package/dist/cli/presets.d.ts +1 -1
  299. package/dist/cli/presets.js +1 -1
  300. package/dist/cli/router.d.ts +36 -0
  301. package/dist/cli/router.js +195 -0
  302. package/dist/cli/tui/ai-chat.js +1 -1
  303. package/dist/cli/tui/commands/context.d.ts +9 -0
  304. package/dist/cli/tui/commands/context.js +1 -0
  305. package/dist/cli/tui/commands/dns.d.ts +10 -0
  306. package/dist/cli/tui/commands/dns.js +461 -0
  307. package/dist/cli/tui/commands/hls.d.ts +2 -0
  308. package/dist/cli/tui/commands/hls.js +162 -0
  309. package/dist/cli/tui/commands/ip.d.ts +2 -0
  310. package/dist/cli/tui/commands/ip.js +45 -0
  311. package/dist/cli/tui/commands/network.d.ts +3 -0
  312. package/dist/cli/tui/commands/network.js +81 -0
  313. package/dist/cli/tui/commands/protocols.d.ts +6 -0
  314. package/dist/cli/tui/commands/protocols.js +531 -0
  315. package/dist/cli/tui/commands/security.d.ts +2 -0
  316. package/dist/cli/tui/commands/security.js +48 -0
  317. package/dist/cli/tui/commands/seo.d.ts +2 -0
  318. package/dist/cli/tui/commands/seo.js +74 -0
  319. package/dist/cli/tui/context.d.ts +12 -0
  320. package/dist/cli/tui/context.js +1 -0
  321. package/dist/cli/tui/shell.d.ts +11 -20
  322. package/dist/cli/tui/shell.js +216 -1873
  323. package/dist/constants/user-agents.d.ts +7 -0
  324. package/dist/constants/user-agents.js +7 -0
  325. package/dist/core/client.d.ts +2 -0
  326. package/dist/core/client.js +19 -1
  327. package/dist/index.d.ts +1 -0
  328. package/dist/index.js +1 -0
  329. package/dist/mcp/cli.js +2 -3
  330. package/dist/mcp/data/embeddings.json +1 -0
  331. package/dist/mcp/tools/network.js +298 -158
  332. package/dist/plugins/har-player.d.ts +23 -0
  333. package/dist/plugins/har-player.js +49 -0
  334. package/dist/plugins/har-recorder.d.ts +37 -3
  335. package/dist/plugins/har-recorder.js +116 -63
  336. package/dist/plugins/network-simulation.d.ts +7 -0
  337. package/dist/plugins/network-simulation.js +13 -0
  338. package/dist/presets/android.d.ts +2 -0
  339. package/dist/presets/android.js +16 -0
  340. package/dist/presets/chaturbate.d.ts +2 -0
  341. package/dist/presets/chaturbate.js +17 -0
  342. package/dist/presets/elevenlabs.d.ts +6 -0
  343. package/dist/presets/elevenlabs.js +20 -0
  344. package/dist/presets/enhancers.d.ts +20 -0
  345. package/dist/presets/enhancers.js +85 -0
  346. package/dist/presets/hubspot.d.ts +9 -0
  347. package/dist/presets/hubspot.js +28 -0
  348. package/dist/presets/index.d.ts +10 -0
  349. package/dist/presets/index.js +10 -0
  350. package/dist/presets/ios.d.ts +2 -0
  351. package/dist/presets/ios.js +13 -0
  352. package/dist/presets/pinecone.d.ts +8 -0
  353. package/dist/presets/pinecone.js +42 -0
  354. package/dist/presets/registry.js +60 -0
  355. package/dist/presets/sendgrid.d.ts +6 -0
  356. package/dist/presets/sendgrid.js +20 -0
  357. package/dist/presets/sentry.d.ts +11 -0
  358. package/dist/presets/sentry.js +48 -0
  359. package/dist/presets/square.d.ts +10 -0
  360. package/dist/presets/square.js +33 -0
  361. package/dist/recker.d.ts +3 -0
  362. package/dist/recker.js +4 -0
  363. package/dist/scrape/document.d.ts +5 -4
  364. package/dist/scrape/document.js +89 -76
  365. package/dist/scrape/element.d.ts +10 -8
  366. package/dist/scrape/element.js +295 -81
  367. package/dist/scrape/extractors.d.ts +11 -11
  368. package/dist/scrape/extractors.js +145 -113
  369. package/dist/scrape/index.d.ts +2 -0
  370. package/dist/scrape/index.js +1 -0
  371. package/dist/scrape/parser/back.d.ts +1 -0
  372. package/dist/scrape/parser/back.js +3 -0
  373. package/dist/scrape/parser/index.d.ts +20 -0
  374. package/dist/scrape/parser/index.js +19 -0
  375. package/dist/scrape/parser/matcher.d.ts +30 -0
  376. package/dist/scrape/parser/matcher.js +99 -0
  377. package/dist/scrape/parser/nodes/comment.d.ts +12 -0
  378. package/dist/scrape/parser/nodes/comment.js +21 -0
  379. package/dist/scrape/parser/nodes/html.d.ts +110 -0
  380. package/dist/scrape/parser/nodes/html.js +978 -0
  381. package/dist/scrape/parser/nodes/node.d.ts +18 -0
  382. package/dist/scrape/parser/nodes/node.js +31 -0
  383. package/dist/scrape/parser/nodes/text.d.ts +14 -0
  384. package/dist/scrape/parser/nodes/text.js +30 -0
  385. package/dist/scrape/parser/nodes/type.d.ts +6 -0
  386. package/dist/scrape/parser/nodes/type.js +7 -0
  387. package/dist/scrape/parser/parse.d.ts +1 -0
  388. package/dist/scrape/parser/parse.js +1 -0
  389. package/dist/scrape/parser/valid.d.ts +2 -0
  390. package/dist/scrape/parser/valid.js +5 -0
  391. package/dist/scrape/parser/void-tag.d.ts +7 -0
  392. package/dist/scrape/parser/void-tag.js +43 -0
  393. package/dist/scrape/spider.d.ts +19 -0
  394. package/dist/scrape/spider.js +28 -3
  395. package/dist/scrape/types.d.ts +7 -0
  396. package/dist/seo/analyzer.d.ts +15 -5
  397. package/dist/seo/analyzer.js +636 -175
  398. package/dist/seo/formatter.d.ts +16 -0
  399. package/dist/seo/formatter.js +228 -0
  400. package/dist/seo/index.d.ts +2 -0
  401. package/dist/seo/index.js +1 -0
  402. package/dist/seo/keywords.d.ts +16 -0
  403. package/dist/seo/keywords.js +55 -0
  404. package/dist/seo/rules/accessibility.js +85 -57
  405. package/dist/seo/rules/ai-search.js +44 -31
  406. package/dist/seo/rules/analytics.d.ts +2 -0
  407. package/dist/seo/rules/analytics.js +306 -0
  408. package/dist/seo/rules/best-practices.js +21 -14
  409. package/dist/seo/rules/canonical.js +31 -22
  410. package/dist/seo/rules/content.js +207 -19
  411. package/dist/seo/rules/crawl.js +55 -40
  412. package/dist/seo/rules/cwv.js +21 -15
  413. package/dist/seo/rules/ecommerce.js +51 -20
  414. package/dist/seo/rules/i18n.js +61 -33
  415. package/dist/seo/rules/images.js +87 -28
  416. package/dist/seo/rules/index.js +2 -0
  417. package/dist/seo/rules/internal-linking.js +58 -39
  418. package/dist/seo/rules/links.js +70 -51
  419. package/dist/seo/rules/local.js +49 -25
  420. package/dist/seo/rules/meta.js +161 -62
  421. package/dist/seo/rules/mobile.js +112 -2
  422. package/dist/seo/rules/performance.js +309 -54
  423. package/dist/seo/rules/pwa.js +36 -39
  424. package/dist/seo/rules/readability.js +31 -22
  425. package/dist/seo/rules/redirects.js +21 -15
  426. package/dist/seo/rules/resources.js +59 -42
  427. package/dist/seo/rules/schema.js +333 -8
  428. package/dist/seo/rules/security.js +142 -80
  429. package/dist/seo/rules/social.js +277 -47
  430. package/dist/seo/rules/structural.js +40 -16
  431. package/dist/seo/rules/technical-advanced.js +27 -22
  432. package/dist/seo/rules/technical.js +243 -42
  433. package/dist/seo/rules/types.d.ts +53 -1
  434. package/dist/seo/seo-spider.d.ts +22 -0
  435. package/dist/seo/seo-spider.js +77 -13
  436. package/dist/seo/types.d.ts +8 -1
  437. package/dist/seo/validators/llms-txt.js +19 -0
  438. package/dist/seo/validators/rss.d.ts +11 -0
  439. package/dist/seo/validators/rss.js +93 -0
  440. package/dist/seo/validators/sitemap.js +36 -26
  441. package/dist/transport/curl.d.ts +4 -0
  442. package/dist/transport/curl.js +101 -0
  443. package/dist/transport/udp.js +0 -1
  444. package/dist/transport/undici.js +1 -2
  445. package/dist/transport/worker.d.ts +18 -0
  446. package/dist/transport/worker.js +278 -0
  447. package/dist/types/index.d.ts +4 -1
  448. package/dist/utils/binary-manager.d.ts +4 -0
  449. package/dist/utils/binary-manager.js +72 -0
  450. package/dist/utils/optional-require.d.ts +7 -8
  451. package/dist/utils/optional-require.js +2 -21
  452. package/dist/utils/upload.d.ts +6 -0
  453. package/dist/utils/upload.js +11 -0
  454. package/dist/utils/user-agent.js +2 -13
  455. package/dist/version.js +1 -1
  456. package/package.json +14 -5
  457. package/dist/browser/utils/optional-require.d.ts +0 -19
  458. package/dist/browser/utils/optional-require.js +0 -105
@@ -8,13 +8,24 @@ export const imageRules = [
8
8
  severity: 'error',
9
9
  description: 'All images must have alt text',
10
10
  check: (ctx) => {
11
- if (ctx.totalImages === undefined || ctx.totalImages === 0)
12
- return null;
11
+ if (ctx.totalImages === undefined || ctx.totalImages === 0) {
12
+ return createResult({ id: 'images-alt-text', name: 'Image Alt Text', category: 'images', severity: 'error' }, 'info', 'Not applicable (no images detected)', { recommendation: 'This rule ensures all images have descriptive alt text for accessibility and SEO' });
13
+ }
13
14
  const withoutAlt = ctx.imagesWithoutAlt ?? 0;
14
15
  if (withoutAlt > 0) {
15
16
  const percentage = Math.round((withoutAlt / ctx.totalImages) * 100);
16
17
  const severity = withoutAlt > ctx.totalImages / 2 ? 'fail' : 'warn';
17
- return createResult({ id: 'images-alt-text', name: 'Image Alt Text', category: 'images', severity: 'error' }, severity, `${withoutAlt} of ${ctx.totalImages} images missing alt text (${percentage}%)`, { value: withoutAlt, recommendation: 'Add descriptive alt text to all images' });
18
+ return createResult({ id: 'images-alt-text', name: 'Image Alt Text', category: 'images', severity: 'error' }, severity, `${withoutAlt} of ${ctx.totalImages} images missing alt text (${percentage}%)`, {
19
+ value: withoutAlt,
20
+ recommendation: 'Add descriptive alt text to all images. Describe what the image shows and its relevance to the content.',
21
+ evidence: {
22
+ found: `${withoutAlt} images without alt attribute`,
23
+ expected: 'All images should have meaningful alt text',
24
+ impact: 'Missing alt text hurts accessibility (screen readers) and prevents images from appearing in Google Image Search. Alt text is also used as anchor text when images are linked.',
25
+ example: '<img src="product.jpg" alt="Red leather wallet with zipper closure - front view">',
26
+ learnMore: 'https://developers.google.com/search/docs/appearance/google-images#use-descriptive-alt-text'
27
+ }
28
+ });
18
29
  }
19
30
  return createResult({ id: 'images-alt-text', name: 'Image Alt Text', category: 'images', severity: 'error' }, 'pass', 'All images have alt text');
20
31
  },
@@ -26,11 +37,23 @@ export const imageRules = [
26
37
  severity: 'warning',
27
38
  description: 'Images should have width and height attributes to prevent CLS',
28
39
  check: (ctx) => {
29
- if (ctx.totalImages === undefined || ctx.totalImages === 0)
30
- return null;
40
+ if (ctx.totalImages === undefined || ctx.totalImages === 0) {
41
+ return createResult({ id: 'images-dimensions', name: 'Image Dimensions', category: 'images', severity: 'warning' }, 'info', 'Not applicable (no images detected)', { recommendation: 'This rule checks that images have width and height attributes to prevent layout shifts' });
42
+ }
31
43
  const missing = ctx.imagesMissingDimensions ?? 0;
32
44
  if (missing > 0) {
33
- return createResult({ id: 'images-dimensions', name: 'Image Dimensions', category: 'images', severity: 'warning' }, 'warn', `${missing} images missing width/height attributes`, { value: missing, recommendation: 'Add width and height to prevent layout shifts (CLS)' });
45
+ const percentage = Math.round((missing / ctx.totalImages) * 100);
46
+ return createResult({ id: 'images-dimensions', name: 'Image Dimensions', category: 'images', severity: 'warning' }, 'warn', `${missing} of ${ctx.totalImages} images missing width/height (${percentage}%)`, {
47
+ value: missing,
48
+ recommendation: 'Add explicit width and height attributes to all images to reserve space and prevent layout shifts.',
49
+ evidence: {
50
+ found: `${missing} images without dimensions`,
51
+ expected: 'All images should have width and height attributes',
52
+ impact: 'Images without dimensions cause Cumulative Layout Shift (CLS), which negatively affects Core Web Vitals and user experience.',
53
+ example: '<img src="photo.jpg" width="800" height="600" alt="Description">',
54
+ learnMore: 'https://web.dev/optimize-cls/#images-without-dimensions'
55
+ }
56
+ });
34
57
  }
35
58
  return createResult({ id: 'images-dimensions', name: 'Image Dimensions', category: 'images', severity: 'warning' }, 'pass', 'All images have dimensions defined');
36
59
  },
@@ -42,11 +65,21 @@ export const imageRules = [
42
65
  severity: 'info',
43
66
  description: 'Below-the-fold images should use lazy loading',
44
67
  check: (ctx) => {
45
- if (ctx.totalImages === undefined || ctx.totalImages <= 3)
46
- return null;
68
+ if (ctx.totalImages === undefined || ctx.totalImages <= 3) {
69
+ return createResult({ id: 'images-lazy-loading', name: 'Lazy Loading', category: 'images', severity: 'info' }, 'info', 'Not applicable (too few images to require lazy loading)', { recommendation: 'This rule checks for lazy loading on pages with multiple images' });
70
+ }
47
71
  const lazy = ctx.imagesWithLazyLoad ?? 0;
48
72
  if (lazy === 0) {
49
- return createResult({ id: 'images-lazy-loading', name: 'Lazy Loading', category: 'images', severity: 'info' }, 'info', 'No images use lazy loading', { recommendation: 'Add loading="lazy" to below-the-fold images' });
73
+ return createResult({ id: 'images-lazy-loading', name: 'Lazy Loading', category: 'images', severity: 'info' }, 'info', 'No images use lazy loading', {
74
+ recommendation: 'Add loading="lazy" to below-the-fold images to defer loading until they are near the viewport.',
75
+ evidence: {
76
+ found: 'No images with loading="lazy"',
77
+ expected: 'Below-the-fold images should use lazy loading',
78
+ impact: 'Lazy loading reduces initial page load time, saves bandwidth, and improves Core Web Vitals (LCP).',
79
+ example: '<img src="photo.jpg" loading="lazy" alt="Description">',
80
+ learnMore: 'https://web.dev/browser-level-image-lazy-loading/'
81
+ }
82
+ });
50
83
  }
51
84
  return createResult({ id: 'images-lazy-loading', name: 'Lazy Loading', category: 'images', severity: 'info' }, 'pass', `${lazy} images use lazy loading`);
52
85
  },
@@ -58,11 +91,22 @@ export const imageRules = [
58
91
  severity: 'info',
59
92
  description: 'Images should use modern formats like WebP or AVIF',
60
93
  check: (ctx) => {
61
- if (ctx.totalImages === undefined || ctx.totalImages === 0)
62
- return null;
94
+ if (ctx.totalImages === undefined || ctx.totalImages === 0) {
95
+ return createResult({ id: 'images-format-modern', name: 'Modern Image Formats', category: 'images', severity: 'info' }, 'info', 'Not applicable (no images detected)', { recommendation: 'This rule checks for modern image formats like WebP and AVIF for better performance' });
96
+ }
63
97
  const modern = ctx.imagesUsingModernFormats ?? 0;
64
98
  if (ctx.totalImages > 0 && modern === 0) {
65
- return createResult({ id: 'images-format-modern', name: 'Modern Image Formats', category: 'images', severity: 'info' }, 'info', 'No images using modern formats (WebP/AVIF)', { value: modern, recommendation: 'Serve images in WebP or AVIF format for better compression' });
99
+ return createResult({ id: 'images-format-modern', name: 'Modern Image Formats', category: 'images', severity: 'info' }, 'info', 'No images using modern formats (WebP/AVIF)', {
100
+ value: modern,
101
+ recommendation: 'Convert images to WebP or AVIF format for 25-50% smaller file sizes with same quality.',
102
+ evidence: {
103
+ found: 'Only traditional formats (JPEG, PNG, GIF)',
104
+ expected: 'At least some images in WebP or AVIF format',
105
+ impact: 'Modern formats significantly reduce page weight and improve load times. WebP has 94% browser support.',
106
+ example: '<picture>\n <source srcset="image.avif" type="image/avif">\n <source srcset="image.webp" type="image/webp">\n <img src="image.jpg" alt="Description">\n</picture>',
107
+ learnMore: 'https://web.dev/uses-webp-images/'
108
+ }
109
+ });
66
110
  }
67
111
  return createResult({ id: 'images-format-modern', name: 'Modern Image Formats', category: 'images', severity: 'info' }, 'pass', `${modern} images using modern formats`, { value: modern });
68
112
  },
@@ -78,7 +122,7 @@ export const imageRules = [
78
122
  if (emptyAlt > 0) {
79
123
  return createResult({ id: 'images-empty-alt', name: 'Empty Alt Text', category: 'images', severity: 'info' }, 'info', `${emptyAlt} image(s) with empty alt="" (decorative)`, { value: emptyAlt, recommendation: 'Ensure these images are truly decorative' });
80
124
  }
81
- return null;
125
+ return createResult({ id: 'images-empty-alt', name: 'Empty Alt Text', category: 'images', severity: 'info' }, 'pass', 'No images with empty alt attributes');
82
126
  },
83
127
  },
84
128
  {
@@ -88,8 +132,9 @@ export const imageRules = [
88
132
  severity: 'warning',
89
133
  description: 'Alt text should be descriptive (ideal 80-120, max 150 chars)',
90
134
  check: (ctx) => {
91
- if (!ctx.altTextLengths || ctx.altTextLengths.length === 0)
92
- return null;
135
+ if (!ctx.altTextLengths || ctx.altTextLengths.length === 0) {
136
+ return createResult({ id: 'images-alt-length', name: 'Alt Text Length', category: 'images', severity: 'warning' }, 'info', 'Not applicable (no alt text data available)', { recommendation: 'This rule checks alt text length to ensure descriptions are meaningful' });
137
+ }
93
138
  const { minLength, idealLength, maxLength } = SEO_THRESHOLDS.images.alt;
94
139
  let shortAlts = 0;
95
140
  let longAlts = 0;
@@ -111,7 +156,7 @@ export const imageRules = [
111
156
  if (nonIdealAlts > 0) {
112
157
  return createResult({ id: 'images-alt-length', name: 'Alt Text Length', category: 'images', severity: 'info' }, 'info', `${nonIdealAlts} alt text(s) are not in the ideal length range (${idealLength.min}-${idealLength.max} chars)`, { value: nonIdealAlts, recommendation: `Aim for alt texts between ${idealLength.min} and ${idealLength.max} characters for best results.` });
113
158
  }
114
- return null;
159
+ return createResult({ id: 'images-alt-length', name: 'Alt Text Length', category: 'images', severity: 'warning' }, 'pass', 'All alt text lengths are within ideal range');
115
160
  },
116
161
  },
117
162
  {
@@ -121,15 +166,26 @@ export const imageRules = [
121
166
  severity: 'info',
122
167
  description: 'Image filenames should be descriptive and use keywords, not generic names.',
123
168
  check: (ctx) => {
124
- if (!ctx.imageFilenames || ctx.imageFilenames.length === 0)
125
- return null;
169
+ if (!ctx.imageFilenames || ctx.imageFilenames.length === 0) {
170
+ return createResult({ id: 'images-clean-filenames', name: 'Image Filenames', category: 'images', severity: 'info' }, 'info', 'Not applicable (no image filename data available)', { recommendation: 'This rule checks for descriptive image filenames with keywords' });
171
+ }
126
172
  const genericFilenames = ctx.imageFilenames.filter(name => /^(img|image|photo|pic)\d*\.(jpg|jpeg|png|webp|avif|gif)$/i.test(name) ||
127
173
  /^screenshot_\d*\.(jpg|jpeg|png)$/i.test(name) ||
128
174
  /^untitled-\d*\.(jpg|jpeg|png)$/i.test(name));
129
175
  if (genericFilenames.length > 0) {
130
- return createResult({ id: 'images-clean-filenames', name: 'Image Filenames', category: 'images', severity: 'warning' }, 'warn', `${genericFilenames.length} image(s) have generic filenames (e.g., IMG_1234.jpg)`, { value: genericFilenames.length, recommendation: 'Rename image files to be descriptive and include keywords (e.g., open-graph-example.jpg).' });
176
+ return createResult({ id: 'images-clean-filenames', name: 'Image Filenames', category: 'images', severity: 'warning' }, 'warn', `${genericFilenames.length} image(s) have generic filenames`, {
177
+ value: genericFilenames.length,
178
+ recommendation: 'Rename image files to be descriptive and include relevant keywords.',
179
+ evidence: {
180
+ found: genericFilenames.slice(0, 5),
181
+ expected: 'Descriptive filenames with keywords (e.g., red-leather-wallet-front.jpg)',
182
+ impact: 'Image filenames are used by search engines to understand image content and can appear in Google Image Search.',
183
+ example: 'Instead of "IMG_1234.jpg", use "blue-running-shoes-nike-air.jpg"',
184
+ learnMore: 'https://developers.google.com/search/docs/appearance/google-images#descriptive-titles-captions-filenames'
185
+ }
186
+ });
131
187
  }
132
- return null;
188
+ return createResult({ id: 'images-clean-filenames', name: 'Image Filenames', category: 'images', severity: 'info' }, 'pass', 'All image filenames are descriptive');
133
189
  },
134
190
  },
135
191
  {
@@ -139,15 +195,17 @@ export const imageRules = [
139
195
  severity: 'info',
140
196
  description: 'Use decoding="async" for non-critical images to improve rendering performance.',
141
197
  check: (ctx) => {
142
- if (ctx.totalImages === undefined || ctx.totalImages === 0)
143
- return null;
144
- if (ctx.imagesWithAsyncDecoding === undefined)
145
- return null;
198
+ if (ctx.totalImages === undefined || ctx.totalImages === 0) {
199
+ return createResult({ id: 'images-decoding-async', name: 'Image Decoding Async', category: 'images', severity: 'info' }, 'info', 'Not applicable (no images detected)', { recommendation: 'This rule checks for async decoding on images for better rendering performance' });
200
+ }
201
+ if (ctx.imagesWithAsyncDecoding === undefined) {
202
+ return createResult({ id: 'images-decoding-async', name: 'Image Decoding Async', category: 'images', severity: 'info' }, 'info', 'Not applicable (async decoding data unavailable)', { recommendation: 'This rule checks for async decoding on images for better rendering performance' });
203
+ }
146
204
  const nonAsync = ctx.totalImages - ctx.imagesWithAsyncDecoding;
147
205
  if (nonAsync > 0 && ctx.totalImages > 3) {
148
206
  return createResult({ id: 'images-decoding-async', name: 'Image Decoding Async', category: 'images', severity: 'info' }, 'info', `${nonAsync} image(s) do not use decoding="async"`, { value: nonAsync, recommendation: 'Consider adding decoding="async" to non-critical images for performance benefits.' });
149
207
  }
150
- return null;
208
+ return createResult({ id: 'images-decoding-async', name: 'Image Decoding Async', category: 'images', severity: 'info' }, 'pass', 'All images use async decoding or too few images to require it');
151
209
  },
152
210
  },
153
211
  {
@@ -157,8 +215,9 @@ export const imageRules = [
157
215
  severity: 'warning',
158
216
  description: 'External images should be accessible',
159
217
  check: (ctx) => {
160
- if (ctx.brokenExternalImages === undefined)
161
- return null;
218
+ if (ctx.brokenExternalImages === undefined) {
219
+ return createResult({ id: 'broken-external-images', name: 'Broken External Images', category: 'images', severity: 'warning' }, 'info', 'Not applicable (broken external images data unavailable)', { recommendation: 'This rule checks for broken external image references' });
220
+ }
162
221
  if (ctx.brokenExternalImages > 0) {
163
222
  return createResult({ id: 'broken-external-images', name: 'Broken External Images', category: 'images', severity: 'warning' }, 'warn', `${ctx.brokenExternalImages} broken external images`, {
164
223
  value: ctx.brokenExternalImages,
@@ -170,7 +229,7 @@ export const imageRules = [
170
229
  }
171
230
  });
172
231
  }
173
- return null;
232
+ return createResult({ id: 'broken-external-images', name: 'Broken External Images', category: 'images', severity: 'warning' }, 'pass', 'No broken external images detected');
174
233
  },
175
234
  },
176
235
  ];
@@ -24,6 +24,7 @@ import { resourceRules } from './resources.js';
24
24
  import { technicalAdvancedRules } from './technical-advanced.js';
25
25
  import { redirectRules } from './redirects.js';
26
26
  import { canonicalRules } from './canonical.js';
27
+ import { analyticsRules } from './analytics.js';
27
28
  export * from './types.js';
28
29
  export * from './thresholds.js';
29
30
  export const ALL_SEO_RULES = [
@@ -53,6 +54,7 @@ export const ALL_SEO_RULES = [
53
54
  ...technicalAdvancedRules,
54
55
  ...redirectRules,
55
56
  ...canonicalRules,
57
+ ...analyticsRules,
56
58
  ];
57
59
  export const SCORING_WEIGHTS = {
58
60
  severity: {
@@ -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`, {