recker 1.0.43 → 1.0.44

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (459) hide show
  1. package/README.md +47 -0
  2. package/dist/bin/recker-linux-x64 +0 -0
  3. package/dist/bin/recker-macos-x64 +0 -0
  4. package/dist/bin/recker-win-x64.exe +0 -0
  5. package/dist/bin/rek.cjs +85152 -100207
  6. package/dist/browser/ai/adaptive-timeout.d.ts +50 -0
  7. package/dist/browser/ai/adaptive-timeout.js +208 -0
  8. package/dist/browser/ai/client.d.ts +22 -0
  9. package/dist/browser/ai/client.js +294 -0
  10. package/dist/browser/ai/index.d.ts +14 -0
  11. package/dist/browser/ai/index.js +11 -0
  12. package/dist/browser/ai/providers/anthropic.d.ts +63 -0
  13. package/dist/browser/ai/providers/anthropic.js +370 -0
  14. package/dist/browser/ai/providers/base.d.ts +48 -0
  15. package/dist/browser/ai/providers/base.js +150 -0
  16. package/dist/browser/ai/providers/google.d.ts +59 -0
  17. package/dist/browser/ai/providers/google.js +305 -0
  18. package/dist/browser/ai/providers/ollama.d.ts +44 -0
  19. package/dist/browser/ai/providers/ollama.js +240 -0
  20. package/dist/browser/ai/providers/openai.d.ts +64 -0
  21. package/dist/browser/ai/providers/openai.js +298 -0
  22. package/dist/browser/ai/rate-limiter.d.ts +43 -0
  23. package/dist/browser/ai/rate-limiter.js +215 -0
  24. package/dist/browser/ai/vector/index.d.ts +2 -0
  25. package/dist/browser/ai/vector/index.js +2 -0
  26. package/dist/browser/ai/vector/similarity.d.ts +2 -0
  27. package/dist/browser/ai/vector/similarity.js +27 -0
  28. package/dist/browser/ai/vector/store.d.ts +27 -0
  29. package/dist/browser/ai/vector/store.js +82 -0
  30. package/dist/browser/browser/cache.d.ts +2 -40
  31. package/dist/browser/browser/cache.js +2 -199
  32. package/dist/browser/browser/index.d.ts +8 -0
  33. package/dist/browser/browser/index.js +8 -0
  34. package/dist/browser/browser/recker.d.ts +8 -1
  35. package/dist/browser/browser/recker.js +8 -2
  36. package/dist/browser/cache/indexed-db.d.ts +10 -0
  37. package/dist/browser/cache/indexed-db.js +88 -0
  38. package/dist/browser/cache/service-worker-cache.d.ts +18 -0
  39. package/dist/browser/cache/service-worker-cache.js +103 -0
  40. package/dist/browser/cache.d.ts +2 -40
  41. package/dist/browser/cache.js +2 -199
  42. package/dist/browser/constants/user-agents.d.ts +7 -0
  43. package/dist/browser/constants/user-agents.js +7 -0
  44. package/dist/browser/core/client.d.ts +2 -0
  45. package/dist/browser/core/client.js +19 -1
  46. package/dist/browser/index.d.ts +8 -0
  47. package/dist/browser/index.js +8 -0
  48. package/dist/browser/plugins/har-recorder.d.ts +40 -0
  49. package/dist/browser/plugins/har-recorder.js +120 -0
  50. package/dist/browser/plugins/network-simulation.d.ts +7 -0
  51. package/dist/browser/plugins/network-simulation.js +13 -0
  52. package/dist/browser/presets/android.d.ts +2 -0
  53. package/dist/browser/presets/android.js +16 -0
  54. package/dist/browser/presets/anthropic.d.ts +8 -0
  55. package/dist/browser/presets/anthropic.js +27 -0
  56. package/dist/browser/presets/aws.d.ts +19 -0
  57. package/dist/browser/presets/aws.js +68 -0
  58. package/dist/browser/presets/azure-openai.d.ts +10 -0
  59. package/dist/browser/presets/azure-openai.js +35 -0
  60. package/dist/browser/presets/azure.d.ts +41 -0
  61. package/dist/browser/presets/azure.js +104 -0
  62. package/dist/browser/presets/chaturbate.d.ts +2 -0
  63. package/dist/browser/presets/chaturbate.js +17 -0
  64. package/dist/browser/presets/cloudflare.d.ts +12 -0
  65. package/dist/browser/presets/cloudflare.js +39 -0
  66. package/dist/browser/presets/cohere.d.ts +7 -0
  67. package/dist/browser/presets/cohere.js +22 -0
  68. package/dist/browser/presets/deepseek.d.ts +7 -0
  69. package/dist/browser/presets/deepseek.js +22 -0
  70. package/dist/browser/presets/digitalocean.d.ts +5 -0
  71. package/dist/browser/presets/digitalocean.js +16 -0
  72. package/dist/browser/presets/discord.d.ts +6 -0
  73. package/dist/browser/presets/discord.js +17 -0
  74. package/dist/browser/presets/elevenlabs.d.ts +6 -0
  75. package/dist/browser/presets/elevenlabs.js +20 -0
  76. package/dist/browser/presets/enhancers.d.ts +20 -0
  77. package/dist/browser/presets/enhancers.js +85 -0
  78. package/dist/browser/presets/fireworks.d.ts +7 -0
  79. package/dist/browser/presets/fireworks.js +22 -0
  80. package/dist/browser/presets/gcp.d.ts +34 -0
  81. package/dist/browser/presets/gcp.js +91 -0
  82. package/dist/browser/presets/gemini.d.ts +7 -0
  83. package/dist/browser/presets/gemini.js +23 -0
  84. package/dist/browser/presets/github.d.ts +6 -0
  85. package/dist/browser/presets/github.js +17 -0
  86. package/dist/browser/presets/gitlab.d.ts +6 -0
  87. package/dist/browser/presets/gitlab.js +16 -0
  88. package/dist/browser/presets/groq.d.ts +7 -0
  89. package/dist/browser/presets/groq.js +22 -0
  90. package/dist/browser/presets/hubspot.d.ts +9 -0
  91. package/dist/browser/presets/hubspot.js +28 -0
  92. package/dist/browser/presets/huggingface.d.ts +7 -0
  93. package/dist/browser/presets/huggingface.js +23 -0
  94. package/dist/browser/presets/index.d.ts +47 -0
  95. package/dist/browser/presets/index.js +47 -0
  96. package/dist/browser/presets/ios.d.ts +2 -0
  97. package/dist/browser/presets/ios.js +13 -0
  98. package/dist/browser/presets/linear.d.ts +5 -0
  99. package/dist/browser/presets/linear.js +16 -0
  100. package/dist/browser/presets/mailgun.d.ts +7 -0
  101. package/dist/browser/presets/mailgun.js +20 -0
  102. package/dist/browser/presets/meta.d.ts +10 -0
  103. package/dist/browser/presets/meta.js +33 -0
  104. package/dist/browser/presets/mistral.d.ts +7 -0
  105. package/dist/browser/presets/mistral.js +22 -0
  106. package/dist/browser/presets/notion.d.ts +6 -0
  107. package/dist/browser/presets/notion.js +17 -0
  108. package/dist/browser/presets/openai.d.ts +9 -0
  109. package/dist/browser/presets/openai.js +30 -0
  110. package/dist/browser/presets/oracle.d.ts +19 -0
  111. package/dist/browser/presets/oracle.js +117 -0
  112. package/dist/browser/presets/perplexity.d.ts +7 -0
  113. package/dist/browser/presets/perplexity.js +22 -0
  114. package/dist/browser/presets/pinecone.d.ts +8 -0
  115. package/dist/browser/presets/pinecone.js +42 -0
  116. package/dist/browser/presets/registry.d.ts +23 -0
  117. package/dist/browser/presets/registry.js +519 -0
  118. package/dist/browser/presets/replicate.d.ts +7 -0
  119. package/dist/browser/presets/replicate.js +23 -0
  120. package/dist/browser/presets/sendgrid.d.ts +6 -0
  121. package/dist/browser/presets/sendgrid.js +20 -0
  122. package/dist/browser/presets/sentry.d.ts +11 -0
  123. package/dist/browser/presets/sentry.js +48 -0
  124. package/dist/browser/presets/sinch.d.ts +9 -0
  125. package/dist/browser/presets/sinch.js +39 -0
  126. package/dist/browser/presets/slack.d.ts +5 -0
  127. package/dist/browser/presets/slack.js +16 -0
  128. package/dist/browser/presets/square.d.ts +10 -0
  129. package/dist/browser/presets/square.js +33 -0
  130. package/dist/browser/presets/stripe.d.ts +7 -0
  131. package/dist/browser/presets/stripe.js +23 -0
  132. package/dist/browser/presets/supabase.d.ts +6 -0
  133. package/dist/browser/presets/supabase.js +18 -0
  134. package/dist/browser/presets/tiktok.d.ts +10 -0
  135. package/dist/browser/presets/tiktok.js +38 -0
  136. package/dist/browser/presets/together.d.ts +7 -0
  137. package/dist/browser/presets/together.js +22 -0
  138. package/dist/browser/presets/twilio.d.ts +6 -0
  139. package/dist/browser/presets/twilio.js +17 -0
  140. package/dist/browser/presets/vercel.d.ts +6 -0
  141. package/dist/browser/presets/vercel.js +23 -0
  142. package/dist/browser/presets/vultr.d.ts +5 -0
  143. package/dist/browser/presets/vultr.js +16 -0
  144. package/dist/browser/presets/xai.d.ts +8 -0
  145. package/dist/browser/presets/xai.js +23 -0
  146. package/dist/browser/presets/youtube.d.ts +5 -0
  147. package/dist/browser/presets/youtube.js +20 -0
  148. package/dist/browser/recker.d.ts +8 -1
  149. package/dist/browser/recker.js +8 -2
  150. package/dist/browser/scrape/document.d.ts +5 -4
  151. package/dist/browser/scrape/document.js +89 -76
  152. package/dist/browser/scrape/element.d.ts +10 -8
  153. package/dist/browser/scrape/element.js +295 -81
  154. package/dist/browser/scrape/extractors.d.ts +11 -11
  155. package/dist/browser/scrape/extractors.js +145 -113
  156. package/dist/browser/scrape/parser/back.d.ts +1 -0
  157. package/dist/browser/scrape/parser/back.js +3 -0
  158. package/dist/browser/scrape/parser/index.d.ts +20 -0
  159. package/dist/browser/scrape/parser/index.js +19 -0
  160. package/dist/browser/scrape/parser/matcher.d.ts +30 -0
  161. package/dist/browser/scrape/parser/matcher.js +99 -0
  162. package/dist/browser/scrape/parser/nodes/comment.d.ts +12 -0
  163. package/dist/browser/scrape/parser/nodes/comment.js +21 -0
  164. package/dist/browser/scrape/parser/nodes/html.d.ts +110 -0
  165. package/dist/browser/scrape/parser/nodes/html.js +978 -0
  166. package/dist/browser/scrape/parser/nodes/node.d.ts +18 -0
  167. package/dist/browser/scrape/parser/nodes/node.js +31 -0
  168. package/dist/browser/scrape/parser/nodes/text.d.ts +14 -0
  169. package/dist/browser/scrape/parser/nodes/text.js +30 -0
  170. package/dist/browser/scrape/parser/nodes/type.d.ts +6 -0
  171. package/dist/browser/scrape/parser/nodes/type.js +7 -0
  172. package/dist/browser/scrape/parser/parse.d.ts +1 -0
  173. package/dist/browser/scrape/parser/parse.js +1 -0
  174. package/dist/browser/scrape/parser/valid.d.ts +2 -0
  175. package/dist/browser/scrape/parser/valid.js +5 -0
  176. package/dist/browser/scrape/parser/void-tag.d.ts +7 -0
  177. package/dist/browser/scrape/parser/void-tag.js +43 -0
  178. package/dist/browser/scrape/types.d.ts +7 -0
  179. package/dist/browser/seo/analyzer.d.ts +59 -0
  180. package/dist/browser/seo/analyzer.js +1399 -0
  181. package/dist/browser/seo/keywords.d.ts +16 -0
  182. package/dist/browser/seo/keywords.js +55 -0
  183. package/dist/browser/seo/rules/accessibility.d.ts +2 -0
  184. package/dist/browser/seo/rules/accessibility.js +733 -0
  185. package/dist/browser/seo/rules/ai-search.d.ts +2 -0
  186. package/dist/browser/seo/rules/ai-search.js +436 -0
  187. package/dist/browser/seo/rules/analytics.d.ts +2 -0
  188. package/dist/browser/seo/rules/analytics.js +306 -0
  189. package/dist/browser/seo/rules/best-practices.d.ts +2 -0
  190. package/dist/browser/seo/rules/best-practices.js +195 -0
  191. package/dist/browser/seo/rules/canonical.d.ts +12 -0
  192. package/dist/browser/seo/rules/canonical.js +270 -0
  193. package/dist/browser/seo/rules/content.d.ts +2 -0
  194. package/dist/browser/seo/rules/content.js +522 -0
  195. package/dist/browser/seo/rules/crawl.d.ts +2 -0
  196. package/dist/browser/seo/rules/crawl.js +435 -0
  197. package/dist/browser/seo/rules/cwv.d.ts +2 -0
  198. package/dist/browser/seo/rules/cwv.js +248 -0
  199. package/dist/browser/seo/rules/ecommerce.d.ts +2 -0
  200. package/dist/browser/seo/rules/ecommerce.js +312 -0
  201. package/dist/browser/seo/rules/i18n.d.ts +2 -0
  202. package/dist/browser/seo/rules/i18n.js +288 -0
  203. package/dist/browser/seo/rules/images.d.ts +2 -0
  204. package/dist/browser/seo/rules/images.js +255 -0
  205. package/dist/browser/seo/rules/index.d.ts +52 -0
  206. package/dist/browser/seo/rules/index.js +159 -0
  207. package/dist/browser/seo/rules/internal-linking.d.ts +2 -0
  208. package/dist/browser/seo/rules/internal-linking.js +394 -0
  209. package/dist/browser/seo/rules/links.d.ts +2 -0
  210. package/dist/browser/seo/rules/links.js +498 -0
  211. package/dist/browser/seo/rules/local.d.ts +2 -0
  212. package/dist/browser/seo/rules/local.js +289 -0
  213. package/dist/browser/seo/rules/meta.d.ts +2 -0
  214. package/dist/browser/seo/rules/meta.js +805 -0
  215. package/dist/browser/seo/rules/mobile.d.ts +2 -0
  216. package/dist/browser/seo/rules/mobile.js +161 -0
  217. package/dist/browser/seo/rules/performance.d.ts +2 -0
  218. package/dist/browser/seo/rules/performance.js +738 -0
  219. package/dist/browser/seo/rules/pwa.d.ts +2 -0
  220. package/dist/browser/seo/rules/pwa.js +299 -0
  221. package/dist/browser/seo/rules/readability.d.ts +2 -0
  222. package/dist/browser/seo/rules/readability.js +264 -0
  223. package/dist/browser/seo/rules/redirects.d.ts +16 -0
  224. package/dist/browser/seo/rules/redirects.js +199 -0
  225. package/dist/browser/seo/rules/resources.d.ts +2 -0
  226. package/dist/browser/seo/rules/resources.js +390 -0
  227. package/dist/browser/seo/rules/schema.d.ts +2 -0
  228. package/dist/browser/seo/rules/schema.js +379 -0
  229. package/dist/browser/seo/rules/security.d.ts +2 -0
  230. package/dist/browser/seo/rules/security.js +877 -0
  231. package/dist/browser/seo/rules/social.d.ts +2 -0
  232. package/dist/browser/seo/rules/social.js +603 -0
  233. package/dist/browser/seo/rules/structural.d.ts +2 -0
  234. package/dist/browser/seo/rules/structural.js +223 -0
  235. package/dist/browser/seo/rules/technical-advanced.d.ts +10 -0
  236. package/dist/browser/seo/rules/technical-advanced.js +289 -0
  237. package/dist/browser/seo/rules/technical.d.ts +2 -0
  238. package/dist/browser/seo/rules/technical.js +480 -0
  239. package/dist/browser/seo/rules/thresholds.d.ts +196 -0
  240. package/dist/browser/seo/rules/thresholds.js +118 -0
  241. package/dist/browser/seo/rules/types.d.ts +498 -0
  242. package/dist/browser/seo/rules/types.js +11 -0
  243. package/dist/browser/seo/types.d.ts +211 -0
  244. package/dist/browser/seo/types.js +1 -0
  245. package/dist/browser/transport/curl.d.ts +4 -0
  246. package/dist/browser/transport/curl.js +101 -0
  247. package/dist/browser/transport/undici.js +1 -2
  248. package/dist/browser/transport/worker.d.ts +18 -0
  249. package/dist/browser/transport/worker.js +278 -0
  250. package/dist/browser/types/index.d.ts +4 -1
  251. package/dist/browser/utils/binary-manager.d.ts +4 -0
  252. package/dist/browser/utils/binary-manager.js +72 -0
  253. package/dist/browser/utils/user-agent.js +2 -13
  254. package/dist/cache/indexed-db.d.ts +10 -0
  255. package/dist/cache/indexed-db.js +88 -0
  256. package/dist/cache/service-worker-cache.d.ts +18 -0
  257. package/dist/cache/service-worker-cache.js +103 -0
  258. package/dist/cli/commands/ai.d.ts +2 -0
  259. package/dist/cli/commands/ai.js +162 -0
  260. package/dist/cli/commands/bench.d.ts +2 -0
  261. package/dist/cli/commands/bench.js +51 -0
  262. package/dist/cli/commands/dns.d.ts +2 -0
  263. package/dist/cli/commands/dns.js +295 -0
  264. package/dist/cli/commands/har.d.ts +2 -0
  265. package/dist/cli/commands/har.js +171 -0
  266. package/dist/cli/commands/hls.d.ts +2 -0
  267. package/dist/cli/commands/hls.js +192 -0
  268. package/dist/cli/commands/network.d.ts +2 -0
  269. package/dist/cli/commands/network.js +288 -0
  270. package/dist/cli/commands/protocols.d.ts +2 -0
  271. package/dist/cli/commands/protocols.js +344 -0
  272. package/dist/cli/commands/scrape.d.ts +2 -0
  273. package/dist/cli/commands/scrape.js +176 -0
  274. package/dist/cli/commands/security.d.ts +2 -0
  275. package/dist/cli/commands/security.js +57 -0
  276. package/dist/cli/commands/seo.d.ts +2 -0
  277. package/dist/cli/commands/seo.js +125 -0
  278. package/dist/cli/commands/serve.d.ts +2 -0
  279. package/dist/cli/commands/serve.js +531 -0
  280. package/dist/cli/commands/spider.d.ts +3 -0
  281. package/dist/cli/commands/spider.js +456 -0
  282. package/dist/cli/commands/utils.d.ts +2 -0
  283. package/dist/cli/commands/utils.js +176 -0
  284. package/dist/cli/commands/vector.d.ts +2 -0
  285. package/dist/cli/commands/vector.js +158 -0
  286. package/dist/cli/handler.d.ts +2 -2
  287. package/dist/cli/handler.js +6 -6
  288. package/dist/cli/helpers.d.ts +7 -0
  289. package/dist/cli/helpers.js +128 -0
  290. package/dist/cli/index.js +96 -5228
  291. package/dist/cli/parser/help.d.ts +2 -0
  292. package/dist/cli/parser/help.js +52 -0
  293. package/dist/cli/parser/index.d.ts +3 -0
  294. package/dist/cli/parser/index.js +3 -0
  295. package/dist/cli/parser/parser.d.ts +4 -0
  296. package/dist/cli/parser/parser.js +146 -0
  297. package/dist/cli/parser/types.d.ts +41 -0
  298. package/dist/cli/parser/types.js +1 -0
  299. package/dist/cli/presets.d.ts +1 -1
  300. package/dist/cli/presets.js +1 -1
  301. package/dist/cli/router.d.ts +36 -0
  302. package/dist/cli/router.js +195 -0
  303. package/dist/cli/tui/ai-chat.js +1 -1
  304. package/dist/cli/tui/commands/context.d.ts +9 -0
  305. package/dist/cli/tui/commands/context.js +1 -0
  306. package/dist/cli/tui/commands/dns.d.ts +10 -0
  307. package/dist/cli/tui/commands/dns.js +461 -0
  308. package/dist/cli/tui/commands/hls.d.ts +2 -0
  309. package/dist/cli/tui/commands/hls.js +162 -0
  310. package/dist/cli/tui/commands/ip.d.ts +2 -0
  311. package/dist/cli/tui/commands/ip.js +45 -0
  312. package/dist/cli/tui/commands/network.d.ts +3 -0
  313. package/dist/cli/tui/commands/network.js +81 -0
  314. package/dist/cli/tui/commands/protocols.d.ts +6 -0
  315. package/dist/cli/tui/commands/protocols.js +531 -0
  316. package/dist/cli/tui/commands/security.d.ts +2 -0
  317. package/dist/cli/tui/commands/security.js +48 -0
  318. package/dist/cli/tui/commands/seo.d.ts +2 -0
  319. package/dist/cli/tui/commands/seo.js +74 -0
  320. package/dist/cli/tui/context.d.ts +12 -0
  321. package/dist/cli/tui/context.js +1 -0
  322. package/dist/cli/tui/shell.d.ts +11 -20
  323. package/dist/cli/tui/shell.js +216 -1873
  324. package/dist/constants/user-agents.d.ts +7 -0
  325. package/dist/constants/user-agents.js +7 -0
  326. package/dist/core/client.d.ts +2 -0
  327. package/dist/core/client.js +19 -1
  328. package/dist/index.d.ts +1 -0
  329. package/dist/index.js +1 -0
  330. package/dist/mcp/cli.js +2 -3
  331. package/dist/mcp/data/embeddings.json +1 -1
  332. package/dist/mcp/tools/network.js +298 -158
  333. package/dist/plugins/har-player.d.ts +23 -0
  334. package/dist/plugins/har-player.js +49 -0
  335. package/dist/plugins/har-recorder.d.ts +37 -3
  336. package/dist/plugins/har-recorder.js +116 -63
  337. package/dist/plugins/network-simulation.d.ts +7 -0
  338. package/dist/plugins/network-simulation.js +13 -0
  339. package/dist/presets/android.d.ts +2 -0
  340. package/dist/presets/android.js +16 -0
  341. package/dist/presets/chaturbate.d.ts +2 -0
  342. package/dist/presets/chaturbate.js +17 -0
  343. package/dist/presets/elevenlabs.d.ts +6 -0
  344. package/dist/presets/elevenlabs.js +20 -0
  345. package/dist/presets/enhancers.d.ts +20 -0
  346. package/dist/presets/enhancers.js +85 -0
  347. package/dist/presets/hubspot.d.ts +9 -0
  348. package/dist/presets/hubspot.js +28 -0
  349. package/dist/presets/index.d.ts +10 -0
  350. package/dist/presets/index.js +10 -0
  351. package/dist/presets/ios.d.ts +2 -0
  352. package/dist/presets/ios.js +13 -0
  353. package/dist/presets/pinecone.d.ts +8 -0
  354. package/dist/presets/pinecone.js +42 -0
  355. package/dist/presets/registry.js +60 -0
  356. package/dist/presets/sendgrid.d.ts +6 -0
  357. package/dist/presets/sendgrid.js +20 -0
  358. package/dist/presets/sentry.d.ts +11 -0
  359. package/dist/presets/sentry.js +48 -0
  360. package/dist/presets/square.d.ts +10 -0
  361. package/dist/presets/square.js +33 -0
  362. package/dist/recker.d.ts +3 -0
  363. package/dist/recker.js +4 -0
  364. package/dist/scrape/document.d.ts +5 -4
  365. package/dist/scrape/document.js +89 -76
  366. package/dist/scrape/element.d.ts +10 -8
  367. package/dist/scrape/element.js +295 -81
  368. package/dist/scrape/extractors.d.ts +11 -11
  369. package/dist/scrape/extractors.js +145 -113
  370. package/dist/scrape/index.d.ts +2 -0
  371. package/dist/scrape/index.js +1 -0
  372. package/dist/scrape/parser/back.d.ts +1 -0
  373. package/dist/scrape/parser/back.js +3 -0
  374. package/dist/scrape/parser/index.d.ts +20 -0
  375. package/dist/scrape/parser/index.js +19 -0
  376. package/dist/scrape/parser/matcher.d.ts +30 -0
  377. package/dist/scrape/parser/matcher.js +99 -0
  378. package/dist/scrape/parser/nodes/comment.d.ts +12 -0
  379. package/dist/scrape/parser/nodes/comment.js +21 -0
  380. package/dist/scrape/parser/nodes/html.d.ts +110 -0
  381. package/dist/scrape/parser/nodes/html.js +978 -0
  382. package/dist/scrape/parser/nodes/node.d.ts +18 -0
  383. package/dist/scrape/parser/nodes/node.js +31 -0
  384. package/dist/scrape/parser/nodes/text.d.ts +14 -0
  385. package/dist/scrape/parser/nodes/text.js +30 -0
  386. package/dist/scrape/parser/nodes/type.d.ts +6 -0
  387. package/dist/scrape/parser/nodes/type.js +7 -0
  388. package/dist/scrape/parser/parse.d.ts +1 -0
  389. package/dist/scrape/parser/parse.js +1 -0
  390. package/dist/scrape/parser/valid.d.ts +2 -0
  391. package/dist/scrape/parser/valid.js +5 -0
  392. package/dist/scrape/parser/void-tag.d.ts +7 -0
  393. package/dist/scrape/parser/void-tag.js +43 -0
  394. package/dist/scrape/spider.d.ts +19 -0
  395. package/dist/scrape/spider.js +28 -3
  396. package/dist/scrape/types.d.ts +7 -0
  397. package/dist/seo/analyzer.d.ts +15 -5
  398. package/dist/seo/analyzer.js +636 -175
  399. package/dist/seo/formatter.d.ts +16 -0
  400. package/dist/seo/formatter.js +228 -0
  401. package/dist/seo/index.d.ts +2 -0
  402. package/dist/seo/index.js +1 -0
  403. package/dist/seo/keywords.d.ts +16 -0
  404. package/dist/seo/keywords.js +55 -0
  405. package/dist/seo/rules/accessibility.js +96 -57
  406. package/dist/seo/rules/ai-search.js +44 -31
  407. package/dist/seo/rules/analytics.d.ts +2 -0
  408. package/dist/seo/rules/analytics.js +306 -0
  409. package/dist/seo/rules/best-practices.js +21 -14
  410. package/dist/seo/rules/canonical.js +53 -32
  411. package/dist/seo/rules/content.js +317 -31
  412. package/dist/seo/rules/crawl.js +55 -40
  413. package/dist/seo/rules/cwv.js +21 -15
  414. package/dist/seo/rules/ecommerce.js +82 -22
  415. package/dist/seo/rules/i18n.js +75 -36
  416. package/dist/seo/rules/images.js +109 -30
  417. package/dist/seo/rules/index.js +2 -0
  418. package/dist/seo/rules/internal-linking.js +58 -39
  419. package/dist/seo/rules/links.js +79 -52
  420. package/dist/seo/rules/local.js +49 -25
  421. package/dist/seo/rules/meta.js +339 -81
  422. package/dist/seo/rules/mobile.js +112 -2
  423. package/dist/seo/rules/performance.js +434 -66
  424. package/dist/seo/rules/pwa.js +36 -39
  425. package/dist/seo/rules/readability.js +31 -22
  426. package/dist/seo/rules/redirects.js +21 -15
  427. package/dist/seo/rules/resources.js +59 -42
  428. package/dist/seo/rules/schema.js +333 -8
  429. package/dist/seo/rules/security.js +142 -80
  430. package/dist/seo/rules/social.js +277 -47
  431. package/dist/seo/rules/structural.js +87 -19
  432. package/dist/seo/rules/technical-advanced.js +30 -24
  433. package/dist/seo/rules/technical.js +243 -42
  434. package/dist/seo/rules/types.d.ts +53 -1
  435. package/dist/seo/seo-spider.d.ts +22 -0
  436. package/dist/seo/seo-spider.js +77 -13
  437. package/dist/seo/types.d.ts +8 -1
  438. package/dist/seo/validators/llms-txt.js +19 -0
  439. package/dist/seo/validators/rss.d.ts +11 -0
  440. package/dist/seo/validators/rss.js +93 -0
  441. package/dist/seo/validators/sitemap.js +36 -26
  442. package/dist/transport/curl.d.ts +4 -0
  443. package/dist/transport/curl.js +101 -0
  444. package/dist/transport/udp.js +0 -1
  445. package/dist/transport/undici.js +1 -2
  446. package/dist/transport/worker.d.ts +18 -0
  447. package/dist/transport/worker.js +278 -0
  448. package/dist/types/index.d.ts +4 -1
  449. package/dist/utils/binary-manager.d.ts +4 -0
  450. package/dist/utils/binary-manager.js +72 -0
  451. package/dist/utils/optional-require.d.ts +7 -8
  452. package/dist/utils/optional-require.js +2 -21
  453. package/dist/utils/upload.d.ts +6 -0
  454. package/dist/utils/upload.js +11 -0
  455. package/dist/utils/user-agent.js +2 -13
  456. package/dist/version.js +1 -1
  457. package/package.json +12 -6
  458. package/dist/browser/utils/optional-require.d.ts +0 -19
  459. package/dist/browser/utils/optional-require.js +0 -105
@@ -0,0 +1,2 @@
1
+ import { SeoRule } from './types.js';
2
+ export declare const socialRules: SeoRule[];
@@ -0,0 +1,603 @@
1
+ import { createResult } from './types.js';
2
+ export const socialRules = [
3
+ {
4
+ id: 'social-og-image-size',
5
+ name: 'OG Image Size',
6
+ category: 'og',
7
+ severity: 'warning',
8
+ description: 'Open Graph images should meet minimum size requirements',
9
+ check: (ctx) => {
10
+ if (!ctx.ogImage) {
11
+ return createResult({ id: 'social-og-image-size', name: 'OG Image Size', category: 'og', severity: 'warning' }, 'info', 'Not applicable (no og:image)', { recommendation: 'This rule checks og:image dimensions when present' });
12
+ }
13
+ if (!ctx.ogImageDimensions) {
14
+ return createResult({ id: 'social-og-image-size', name: 'OG Image Size', category: 'og', severity: 'warning' }, 'info', 'Not applicable (og:image dimensions not available)', { recommendation: 'This rule checks if og:image meets minimum size requirements' });
15
+ }
16
+ const { width, height } = ctx.ogImageDimensions;
17
+ const minWidth = 1200;
18
+ const minHeight = 630;
19
+ if (width < minWidth || height < minHeight) {
20
+ return createResult({ id: 'social-og-image-size', name: 'OG Image Size', category: 'og', severity: 'warning' }, 'warn', `OG image too small: ${width}x${height}`, {
21
+ recommendation: 'Use larger images for better social sharing',
22
+ evidence: {
23
+ found: `${width}x${height}px`,
24
+ expected: `Minimum ${minWidth}x${minHeight}px`,
25
+ impact: 'Small images may appear cropped or blurry on social platforms',
26
+ learnMore: 'https://developers.facebook.com/docs/sharing/best-practices/',
27
+ },
28
+ });
29
+ }
30
+ return createResult({ id: 'social-og-image-size', name: 'OG Image Size', category: 'og', severity: 'warning' }, 'pass', `OG image: ${width}x${height}px`);
31
+ },
32
+ },
33
+ {
34
+ id: 'social-og-image-aspect-ratio',
35
+ name: 'OG Image Aspect Ratio',
36
+ category: 'og',
37
+ severity: 'info',
38
+ description: 'Open Graph images should use optimal aspect ratio',
39
+ check: (ctx) => {
40
+ if (!ctx.ogImageDimensions) {
41
+ return createResult({ id: 'social-og-image-aspect-ratio', name: 'OG Image Aspect Ratio', category: 'og', severity: 'info' }, 'info', 'Not applicable (og:image dimensions not available)', { recommendation: 'This rule checks og:image aspect ratio when dimensions are available' });
42
+ }
43
+ const { width, height } = ctx.ogImageDimensions;
44
+ const ratio = width / height;
45
+ const optimalRatio = 1.91;
46
+ const tolerance = 0.1;
47
+ if (Math.abs(ratio - optimalRatio) > tolerance) {
48
+ return createResult({ id: 'social-og-image-aspect-ratio', name: 'OG Image Aspect Ratio', category: 'og', severity: 'info' }, 'info', `OG image ratio: ${ratio.toFixed(2)}:1`, {
49
+ recommendation: 'Use 1.91:1 aspect ratio for optimal display',
50
+ evidence: {
51
+ found: `${ratio.toFixed(2)}:1`,
52
+ expected: '1.91:1 (e.g., 1200x628)',
53
+ impact: 'Non-optimal ratios may be cropped on social platforms',
54
+ },
55
+ });
56
+ }
57
+ return createResult({ id: 'social-og-image-aspect-ratio', name: 'OG Image Aspect Ratio', category: 'og', severity: 'info' }, 'pass', 'OG image has optimal aspect ratio');
58
+ },
59
+ },
60
+ {
61
+ id: 'social-og-locale',
62
+ name: 'OG Locale',
63
+ category: 'og',
64
+ severity: 'info',
65
+ description: 'Open Graph should specify content locale',
66
+ check: (ctx) => {
67
+ if (ctx.ogLocale === undefined) {
68
+ return createResult({ id: 'social-og-locale', name: 'OG Locale', category: 'og', severity: 'info' }, 'info', 'Not applicable (og:locale property not checked)', { recommendation: 'This rule checks og:locale when locale data is available' });
69
+ }
70
+ if (!ctx.ogLocale) {
71
+ return createResult({ id: 'social-og-locale', name: 'OG Locale', category: 'og', severity: 'info' }, 'info', 'Missing og:locale', {
72
+ recommendation: 'Add og:locale for international content',
73
+ evidence: {
74
+ expected: '<meta property="og:locale" content="en_US">',
75
+ impact: 'Helps Facebook display content to correct language audience',
76
+ },
77
+ });
78
+ }
79
+ return createResult({ id: 'social-og-locale', name: 'OG Locale', category: 'og', severity: 'info' }, 'pass', `Locale: ${ctx.ogLocale}`);
80
+ },
81
+ },
82
+ {
83
+ id: 'social-og-locale-alternate',
84
+ name: 'OG Locale Alternates',
85
+ category: 'og',
86
+ severity: 'info',
87
+ description: 'Multi-language sites should specify alternate locales',
88
+ check: (ctx) => {
89
+ if (!ctx.ogLocale) {
90
+ return createResult({ id: 'social-og-locale-alternate', name: 'OG Locale Alternates', category: 'og', severity: 'info' }, 'info', 'Not applicable (no og:locale)', { recommendation: 'This rule checks for alternate locales when og:locale is present' });
91
+ }
92
+ if (!ctx.hreflangTags || ctx.hreflangTags.length <= 1) {
93
+ return createResult({ id: 'social-og-locale-alternate', name: 'OG Locale Alternates', category: 'og', severity: 'info' }, 'info', 'Not applicable (single language site)', { recommendation: 'This rule checks for og:locale:alternate on multi-language sites' });
94
+ }
95
+ if (ctx.ogLocaleAlternate === undefined) {
96
+ return createResult({ id: 'social-og-locale-alternate', name: 'OG Locale Alternates', category: 'og', severity: 'info' }, 'info', 'Not applicable (og:locale:alternate property not checked)', { recommendation: 'This rule checks og:locale:alternate when data is available' });
97
+ }
98
+ if (!ctx.ogLocaleAlternate || ctx.ogLocaleAlternate.length === 0) {
99
+ return createResult({ id: 'social-og-locale-alternate', name: 'OG Locale Alternates', category: 'og', severity: 'info' }, 'info', 'Missing og:locale:alternate for multi-language site', {
100
+ recommendation: 'Add og:locale:alternate for other language versions',
101
+ evidence: {
102
+ found: `${ctx.hreflangTags.length} languages but no og:locale:alternate`,
103
+ expected: '<meta property="og:locale:alternate" content="es_ES">',
104
+ impact: 'Helps Facebook understand available language versions',
105
+ },
106
+ });
107
+ }
108
+ return createResult({ id: 'social-og-locale-alternate', name: 'OG Locale Alternates', category: 'og', severity: 'info' }, 'pass', `${ctx.ogLocaleAlternate.length} alternate locale(s)`);
109
+ },
110
+ },
111
+ {
112
+ id: 'social-og-article-tags',
113
+ name: 'OG Article Tags',
114
+ category: 'og',
115
+ severity: 'info',
116
+ description: 'Article pages should include article-specific Open Graph tags',
117
+ check: (ctx) => {
118
+ if (ctx.ogType !== 'article') {
119
+ return createResult({ id: 'social-og-article-tags', name: 'OG Article Tags', category: 'og', severity: 'info' }, 'info', 'Not applicable (page is not an article)', { recommendation: 'This rule checks article-specific Open Graph tags for article pages' });
120
+ }
121
+ if (ctx.ogArticleTags === undefined) {
122
+ return createResult({ id: 'social-og-article-tags', name: 'OG Article Tags', category: 'og', severity: 'info' }, 'info', 'Not applicable (article tags property not checked)', { recommendation: 'This rule checks article-specific Open Graph tags when data is available' });
123
+ }
124
+ const missing = [];
125
+ if (!ctx.ogArticlePublishedTime)
126
+ missing.push('article:published_time');
127
+ if (!ctx.ogArticleAuthor)
128
+ missing.push('article:author');
129
+ if (missing.length > 0) {
130
+ return createResult({ id: 'social-og-article-tags', name: 'OG Article Tags', category: 'og', severity: 'info' }, 'info', `Missing article OG tags: ${missing.join(', ')}`, {
131
+ recommendation: 'Add article-specific Open Graph tags',
132
+ evidence: {
133
+ found: missing,
134
+ expected: ['article:published_time', 'article:author', 'article:section', 'article:tag'],
135
+ impact: 'Rich article metadata improves social sharing appearance',
136
+ },
137
+ });
138
+ }
139
+ return createResult({ id: 'social-og-article-tags', name: 'OG Article Tags', category: 'og', severity: 'info' }, 'pass', 'Article OG tags present');
140
+ },
141
+ },
142
+ {
143
+ id: 'social-twitter-large-image',
144
+ name: 'Twitter Large Image',
145
+ category: 'twitter',
146
+ severity: 'info',
147
+ description: 'Consider using summary_large_image for better visibility',
148
+ check: (ctx) => {
149
+ if (!ctx.twitterCard) {
150
+ return createResult({ id: 'social-twitter-large-image', name: 'Twitter Large Image', category: 'twitter', severity: 'info' }, 'info', 'Not applicable (no twitter:card)', { recommendation: 'This rule checks Twitter card type when present' });
151
+ }
152
+ if (ctx.twitterCard === 'summary') {
153
+ return createResult({ id: 'social-twitter-large-image', name: 'Twitter Large Image', category: 'twitter', severity: 'info' }, 'info', 'Using summary card (small image)', {
154
+ recommendation: 'Consider summary_large_image for more visual impact',
155
+ evidence: {
156
+ found: 'summary',
157
+ expected: 'summary_large_image for content-rich pages',
158
+ impact: 'Large images get 2x more engagement on Twitter',
159
+ },
160
+ });
161
+ }
162
+ return createResult({ id: 'social-twitter-large-image', name: 'Twitter Large Image', category: 'twitter', severity: 'info' }, 'pass', `Twitter card: ${ctx.twitterCard}`);
163
+ },
164
+ },
165
+ {
166
+ id: 'social-twitter-creator',
167
+ name: 'Twitter Creator',
168
+ category: 'twitter',
169
+ severity: 'info',
170
+ description: 'Article pages should include twitter:creator for attribution',
171
+ check: (ctx) => {
172
+ if (!ctx.twitterCard) {
173
+ return createResult({ id: 'social-twitter-creator', name: 'Twitter Creator', category: 'twitter', severity: 'info' }, 'info', 'Not applicable (no twitter:card)', { recommendation: 'This rule checks twitter:creator when Twitter Card is present' });
174
+ }
175
+ if (ctx.ogType !== 'article') {
176
+ return createResult({ id: 'social-twitter-creator', name: 'Twitter Creator', category: 'twitter', severity: 'info' }, 'info', 'Not applicable (page is not an article)', { recommendation: 'This rule checks twitter:creator for article pages' });
177
+ }
178
+ if (ctx.twitterCreator === undefined) {
179
+ return createResult({ id: 'social-twitter-creator', name: 'Twitter Creator', category: 'twitter', severity: 'info' }, 'info', 'Not applicable (twitter:creator property not checked)', { recommendation: 'This rule checks twitter:creator when data is available' });
180
+ }
181
+ if (!ctx.twitterCreator) {
182
+ return createResult({ id: 'social-twitter-creator', name: 'Twitter Creator', category: 'twitter', severity: 'info' }, 'info', 'Missing twitter:creator on article', {
183
+ recommendation: 'Add twitter:creator for author attribution',
184
+ evidence: {
185
+ expected: '<meta name="twitter:creator" content="@username">',
186
+ impact: 'Attributes content to author and enables analytics',
187
+ },
188
+ });
189
+ }
190
+ return createResult({ id: 'social-twitter-creator', name: 'Twitter Creator', category: 'twitter', severity: 'info' }, 'pass', `Creator: ${ctx.twitterCreator}`);
191
+ },
192
+ },
193
+ {
194
+ id: 'social-twitter-image-alt',
195
+ name: 'Twitter Image Alt',
196
+ category: 'twitter',
197
+ severity: 'info',
198
+ description: 'Twitter images should have alt text for accessibility',
199
+ check: (ctx) => {
200
+ if (!ctx.twitterImage) {
201
+ return createResult({ id: 'social-twitter-image-alt', name: 'Twitter Image Alt', category: 'twitter', severity: 'info' }, 'info', 'Not applicable (no twitter:image)', { recommendation: 'This rule checks twitter:image:alt when Twitter image is present' });
202
+ }
203
+ if (ctx.twitterImageAlt === undefined) {
204
+ return createResult({ id: 'social-twitter-image-alt', name: 'Twitter Image Alt', category: 'twitter', severity: 'info' }, 'info', 'Not applicable (twitter:image:alt property not checked)', { recommendation: 'This rule checks twitter:image:alt when data is available' });
205
+ }
206
+ if (!ctx.twitterImageAlt) {
207
+ return createResult({ id: 'social-twitter-image-alt', name: 'Twitter Image Alt', category: 'twitter', severity: 'info' }, 'info', 'Missing twitter:image:alt', {
208
+ recommendation: 'Add alt text for Twitter card image',
209
+ evidence: {
210
+ expected: '<meta name="twitter:image:alt" content="Description of image">',
211
+ impact: 'Improves accessibility for screen reader users',
212
+ },
213
+ });
214
+ }
215
+ return createResult({ id: 'social-twitter-image-alt', name: 'Twitter Image Alt', category: 'twitter', severity: 'info' }, 'pass', 'Twitter image has alt text');
216
+ },
217
+ },
218
+ {
219
+ id: 'social-linkedin-author',
220
+ name: 'LinkedIn Author',
221
+ category: 'og',
222
+ severity: 'info',
223
+ description: 'Professional content should include author information',
224
+ check: (ctx) => {
225
+ if (ctx.ogType !== 'article') {
226
+ return createResult({ id: 'social-linkedin-author', name: 'LinkedIn Author', category: 'og', severity: 'info' }, 'info', 'Not applicable (page is not an article)', { recommendation: 'This rule checks author information for article pages' });
227
+ }
228
+ if (ctx.linkedinAuthor === undefined && ctx.ogArticleAuthor === undefined) {
229
+ return createResult({ id: 'social-linkedin-author', name: 'LinkedIn Author', category: 'og', severity: 'info' }, 'info', 'Not applicable (author properties not checked)', { recommendation: 'This rule checks author information when data is available' });
230
+ }
231
+ if (!ctx.linkedinAuthor && !ctx.ogArticleAuthor) {
232
+ return createResult({ id: 'social-linkedin-author', name: 'LinkedIn Author', category: 'og', severity: 'info' }, 'info', 'No author specified for LinkedIn', {
233
+ recommendation: 'Add author for professional network sharing',
234
+ evidence: {
235
+ expected: '<meta property="article:author" content="https://linkedin.com/in/author">',
236
+ impact: 'LinkedIn uses author info for professional attribution',
237
+ },
238
+ });
239
+ }
240
+ return createResult({ id: 'social-linkedin-author', name: 'LinkedIn Author', category: 'og', severity: 'info' }, 'pass', 'Author information present');
241
+ },
242
+ },
243
+ {
244
+ id: 'social-pinterest-rich-pins',
245
+ name: 'Pinterest Rich Pins',
246
+ category: 'og',
247
+ severity: 'info',
248
+ description: 'E-commerce and recipe sites should support Pinterest Rich Pins',
249
+ check: (ctx) => {
250
+ if (!ctx.isProductPage && ctx.ogType !== 'recipe') {
251
+ return createResult({ id: 'social-pinterest-rich-pins', name: 'Pinterest Rich Pins', category: 'og', severity: 'info' }, 'info', 'Not applicable (page is not a product or recipe)', { recommendation: 'This rule checks Pinterest Rich Pin support for e-commerce and recipe sites' });
252
+ }
253
+ if (ctx.pinterestRichPinSupport === undefined) {
254
+ return createResult({ id: 'social-pinterest-rich-pins', name: 'Pinterest Rich Pins', category: 'og', severity: 'info' }, 'info', 'Not applicable (Pinterest Rich Pin support not checked)', { recommendation: 'This rule checks for structured data when data is available' });
255
+ }
256
+ if (!ctx.pinterestRichPinSupport) {
257
+ return createResult({ id: 'social-pinterest-rich-pins', name: 'Pinterest Rich Pins', category: 'og', severity: 'info' }, 'info', 'No Pinterest Rich Pin support detected', {
258
+ recommendation: 'Add structured data for Pinterest Rich Pins',
259
+ evidence: {
260
+ expected: 'Product or Recipe schema.org markup',
261
+ impact: 'Rich Pins show real-time pricing and availability',
262
+ learnMore: 'https://developers.pinterest.com/docs/rich-pins/overview/',
263
+ },
264
+ });
265
+ }
266
+ return createResult({ id: 'social-pinterest-rich-pins', name: 'Pinterest Rich Pins', category: 'og', severity: 'info' }, 'pass', 'Pinterest Rich Pins supported');
267
+ },
268
+ },
269
+ {
270
+ id: 'social-pinterest-nopin',
271
+ name: 'Pinterest Nopin',
272
+ category: 'og',
273
+ severity: 'info',
274
+ description: 'Check for intentional Pinterest blocking',
275
+ check: (ctx) => {
276
+ if (ctx.hasPinterestNopin === undefined) {
277
+ return createResult({ id: 'social-pinterest-nopin', name: 'Pinterest Nopin', category: 'og', severity: 'info' }, 'info', 'Not applicable (Pinterest nopin property not checked)', { recommendation: 'This rule checks for Pinterest blocking when data is available' });
278
+ }
279
+ if (ctx.hasPinterestNopin) {
280
+ return createResult({ id: 'social-pinterest-nopin', name: 'Pinterest Nopin', category: 'og', severity: 'info' }, 'info', 'Pinterest pinning is disabled', {
281
+ evidence: {
282
+ found: 'data-pin-nopin or <meta name="pinterest" content="nopin">',
283
+ impact: 'Images cannot be pinned to Pinterest',
284
+ },
285
+ });
286
+ }
287
+ return createResult({ id: 'social-pinterest-nopin', name: 'Pinterest Nopin', category: 'og', severity: 'info' }, 'info', 'Not applicable (Pinterest pinning is allowed)', { recommendation: 'This rule checks for intentional Pinterest blocking' });
288
+ },
289
+ },
290
+ {
291
+ id: 'social-share-completeness',
292
+ name: 'Social Share Completeness',
293
+ category: 'og',
294
+ severity: 'warning',
295
+ description: 'Pages should have complete social sharing metadata',
296
+ check: (ctx) => {
297
+ const hasOg = ctx.ogTitle && ctx.ogDescription && ctx.ogImage;
298
+ const hasTwitter = ctx.twitterCard && (ctx.twitterTitle || ctx.ogTitle);
299
+ if (!hasOg && !hasTwitter) {
300
+ return createResult({ id: 'social-share-completeness', name: 'Social Share Completeness', category: 'og', severity: 'warning' }, 'warn', 'Missing social sharing metadata', {
301
+ recommendation: 'Add Open Graph and Twitter Card tags',
302
+ evidence: {
303
+ expected: ['og:title', 'og:description', 'og:image', 'twitter:card'],
304
+ impact: 'Without metadata, social platforms use generic previews',
305
+ },
306
+ });
307
+ }
308
+ if (hasOg && !hasTwitter) {
309
+ return createResult({ id: 'social-share-completeness', name: 'Social Share Completeness', category: 'og', severity: 'warning' }, 'info', 'Has Open Graph but missing Twitter Card', {
310
+ recommendation: 'Add twitter:card for better Twitter previews',
311
+ });
312
+ }
313
+ return createResult({ id: 'social-share-completeness', name: 'Social Share Completeness', category: 'og', severity: 'warning' }, 'pass', 'Complete social metadata');
314
+ },
315
+ },
316
+ {
317
+ id: 'social-og-title-length',
318
+ name: 'OG Title Length',
319
+ category: 'og',
320
+ severity: 'info',
321
+ description: 'Open Graph titles should be optimized for social platforms',
322
+ check: (ctx) => {
323
+ if (!ctx.ogTitle) {
324
+ return createResult({ id: 'social-og-title-length', name: 'OG Title Length', category: 'og', severity: 'info' }, 'info', 'Not applicable (no og:title)', { recommendation: 'This rule checks og:title length for social platforms when present' });
325
+ }
326
+ const length = ctx.ogTitle.length;
327
+ const maxLength = 60;
328
+ if (length > maxLength) {
329
+ return createResult({ id: 'social-og-title-length', name: 'OG Title Length', category: 'og', severity: 'info' }, 'info', `OG title too long: ${length} chars`, {
330
+ recommendation: 'Shorten og:title for better display',
331
+ evidence: {
332
+ found: length,
333
+ expected: `Under ${maxLength} characters`,
334
+ impact: 'Long titles may be truncated on social platforms',
335
+ },
336
+ });
337
+ }
338
+ return createResult({ id: 'social-og-title-length', name: 'OG Title Length', category: 'og', severity: 'info' }, 'pass', `OG title: ${length} chars`);
339
+ },
340
+ },
341
+ {
342
+ id: 'social-og-description-length',
343
+ name: 'OG Description Length',
344
+ category: 'og',
345
+ severity: 'info',
346
+ description: 'Open Graph descriptions should be optimized for social platforms',
347
+ check: (ctx) => {
348
+ if (!ctx.ogDescription) {
349
+ return createResult({ id: 'social-og-description-length', name: 'OG Description Length', category: 'og', severity: 'info' }, 'info', 'Not applicable (no og:description)', { recommendation: 'This rule checks og:description length for social platforms when present' });
350
+ }
351
+ const length = ctx.ogDescription.length;
352
+ const minLength = 55;
353
+ const maxLength = 200;
354
+ if (length < minLength) {
355
+ return createResult({ id: 'social-og-description-length', name: 'OG Description Length', category: 'og', severity: 'info' }, 'info', `OG description too short: ${length} chars`, {
356
+ recommendation: 'Expand og:description for better context',
357
+ evidence: {
358
+ found: length,
359
+ expected: `${minLength}-${maxLength} characters`,
360
+ },
361
+ });
362
+ }
363
+ if (length > maxLength) {
364
+ return createResult({ id: 'social-og-description-length', name: 'OG Description Length', category: 'og', severity: 'info' }, 'info', `OG description long: ${length} chars`, {
365
+ evidence: {
366
+ found: length,
367
+ expected: `${minLength}-${maxLength} characters (may be truncated)`,
368
+ },
369
+ });
370
+ }
371
+ return createResult({ id: 'social-og-description-length', name: 'OG Description Length', category: 'og', severity: 'info' }, 'pass', `OG description: ${length} chars`);
372
+ },
373
+ },
374
+ {
375
+ id: 'social-fb-app-id',
376
+ name: 'Facebook App ID',
377
+ category: 'og',
378
+ severity: 'info',
379
+ description: 'Facebook App ID enables Insights and domain verification',
380
+ check: (ctx) => {
381
+ if (ctx.fbAppId === undefined) {
382
+ return createResult({ id: 'social-fb-app-id', name: 'Facebook App ID', category: 'og', severity: 'info' }, 'info', 'Not applicable (Facebook App ID property not checked)', { recommendation: 'This rule checks for fb:app_id when data is available' });
383
+ }
384
+ if (!ctx.fbAppId) {
385
+ return createResult({ id: 'social-fb-app-id', name: 'Facebook App ID', category: 'og', severity: 'info' }, 'info', 'No Facebook App ID', {
386
+ recommendation: 'Add fb:app_id for Facebook Insights',
387
+ evidence: {
388
+ expected: '<meta property="fb:app_id" content="your-app-id">',
389
+ impact: 'Enables Facebook Insights and domain verification',
390
+ },
391
+ });
392
+ }
393
+ return createResult({ id: 'social-fb-app-id', name: 'Facebook App ID', category: 'og', severity: 'info' }, 'pass', 'Facebook App ID present');
394
+ },
395
+ },
396
+ {
397
+ id: 'social-links-presence',
398
+ name: 'Social Media Links',
399
+ category: 'og',
400
+ severity: 'info',
401
+ description: 'Site should link to social media profiles',
402
+ check: (ctx) => {
403
+ if (ctx.socialLinksFound && ctx.socialLinksFound.length > 0) {
404
+ return createResult({ id: 'social-links-presence', name: 'Social Media Links', category: 'og', severity: 'info' }, 'pass', `Found ${ctx.socialLinksFound.length} social media profile link(s)`, { value: ctx.socialLinksFound.length, evidence: { found: ctx.socialLinksFound.slice(0, 5) } });
405
+ }
406
+ return createResult({ id: 'social-links-presence', name: 'Social Media Links', category: 'og', severity: 'info' }, 'info', 'No social media profile links found', { recommendation: 'Link to your Facebook, Twitter, Instagram, etc. profiles to build trust.' });
407
+ },
408
+ },
409
+ {
410
+ id: 'social-links-accessibility',
411
+ name: 'Social Links Accessibility',
412
+ category: 'accessibility',
413
+ severity: 'warning',
414
+ description: 'Social media links should have accessible labels for screen readers',
415
+ check: (ctx) => {
416
+ if (!ctx.totalSocialLinks || ctx.totalSocialLinks === 0) {
417
+ return createResult({ id: 'social-links-accessibility', name: 'Social Links Accessibility', category: 'accessibility', severity: 'warning' }, 'info', 'Not applicable (no social links found)', { recommendation: 'This rule checks accessibility of social media links when present' });
418
+ }
419
+ if (ctx.socialLinksWithoutAccessibility === undefined) {
420
+ return createResult({ id: 'social-links-accessibility', name: 'Social Links Accessibility', category: 'accessibility', severity: 'warning' }, 'info', 'Not applicable (social link accessibility not checked)', { recommendation: 'This rule checks for accessible labels on social links when data is available' });
421
+ }
422
+ if (ctx.socialLinksWithoutAccessibility > 0) {
423
+ const inaccessibleLinks = ctx.socialLinkDetails
424
+ ?.filter(l => !l.hasAccessibility)
425
+ .map(l => l.platform)
426
+ .slice(0, 5);
427
+ return createResult({ id: 'social-links-accessibility', name: 'Social Links Accessibility', category: 'accessibility', severity: 'warning' }, 'warn', `${ctx.socialLinksWithoutAccessibility} social link(s) missing accessible labels`, {
428
+ recommendation: 'Add aria-label, title, or visible text to social media links',
429
+ evidence: {
430
+ found: ctx.socialLinksWithoutAccessibility,
431
+ expected: 'All social links should have accessible labels',
432
+ impact: 'Screen reader users cannot identify the purpose of icon-only social links',
433
+ example: '<a href="https://twitter.com/..." aria-label="Follow us on Twitter">',
434
+ issue: inaccessibleLinks?.length ? `Inaccessible: ${inaccessibleLinks.join(', ')}` : undefined,
435
+ },
436
+ });
437
+ }
438
+ return createResult({ id: 'social-links-accessibility', name: 'Social Links Accessibility', category: 'accessibility', severity: 'warning' }, 'pass', 'All social links have accessible labels');
439
+ },
440
+ },
441
+ {
442
+ id: 'social-links-security',
443
+ name: 'Social Links Security',
444
+ category: 'security',
445
+ severity: 'warning',
446
+ description: 'External social links should use rel="noopener" to prevent security vulnerabilities',
447
+ check: (ctx) => {
448
+ if (!ctx.totalSocialLinks || ctx.totalSocialLinks === 0) {
449
+ return createResult({ id: 'social-links-security', name: 'Social Links Security', category: 'security', severity: 'warning' }, 'info', 'Not applicable (no social links found)', { recommendation: 'This rule checks security attributes of social media links when present' });
450
+ }
451
+ if (ctx.socialLinksWithoutNoopener === undefined) {
452
+ return createResult({ id: 'social-links-security', name: 'Social Links Security', category: 'security', severity: 'warning' }, 'info', 'Not applicable (social link security not checked)', { recommendation: 'This rule checks for rel="noopener" on social links when data is available' });
453
+ }
454
+ const linksWithNewTab = ctx.socialLinkDetails?.filter(l => l.hasNewTab) || [];
455
+ if (linksWithNewTab.length === 0) {
456
+ return createResult({ id: 'social-links-security', name: 'Social Links Security', category: 'security', severity: 'warning' }, 'info', 'Not applicable (no social links with target="_blank")', { recommendation: 'This rule checks security for social links that open in new tabs' });
457
+ }
458
+ const insecureLinks = linksWithNewTab.filter(l => !l.hasNoopener);
459
+ if (insecureLinks.length > 0) {
460
+ return createResult({ id: 'social-links-security', name: 'Social Links Security', category: 'security', severity: 'warning' }, 'warn', `${insecureLinks.length} social link(s) with target="_blank" missing rel="noopener"`, {
461
+ recommendation: 'Add rel="noopener noreferrer" to external links with target="_blank"',
462
+ evidence: {
463
+ found: insecureLinks.length,
464
+ expected: 'All target="_blank" links should have rel="noopener"',
465
+ impact: 'Without noopener, the opened page can access window.opener and potentially redirect your page (tabnabbing attack)',
466
+ example: '<a href="https://twitter.com/..." target="_blank" rel="noopener noreferrer">',
467
+ issue: insecureLinks.slice(0, 3).map(l => l.platform).join(', '),
468
+ },
469
+ });
470
+ }
471
+ return createResult({ id: 'social-links-security', name: 'Social Links Security', category: 'security', severity: 'warning' }, 'pass', 'All social links opening in new tabs have proper security attributes');
472
+ },
473
+ },
474
+ {
475
+ id: 'social-links-new-tab',
476
+ name: 'Social Links New Tab',
477
+ category: 'og',
478
+ severity: 'info',
479
+ description: 'Social media links should open in a new tab to keep users on your site',
480
+ check: (ctx) => {
481
+ if (!ctx.totalSocialLinks || ctx.totalSocialLinks === 0) {
482
+ return createResult({ id: 'social-links-new-tab', name: 'Social Links New Tab', category: 'og', severity: 'info' }, 'info', 'Not applicable (no social links found)', { recommendation: 'This rule checks if social links open in new tabs when present' });
483
+ }
484
+ if (ctx.socialLinksWithoutNewTab === undefined) {
485
+ return createResult({ id: 'social-links-new-tab', name: 'Social Links New Tab', category: 'og', severity: 'info' }, 'info', 'Not applicable (social link new tab property not checked)', { recommendation: 'This rule checks target="_blank" on social links when data is available' });
486
+ }
487
+ if (ctx.socialLinksWithoutNewTab > 0) {
488
+ const linksWithoutNewTab = ctx.socialLinkDetails
489
+ ?.filter(l => !l.hasNewTab)
490
+ .map(l => l.platform)
491
+ .slice(0, 5);
492
+ return createResult({ id: 'social-links-new-tab', name: 'Social Links New Tab', category: 'og', severity: 'info' }, 'info', `${ctx.socialLinksWithoutNewTab} social link(s) don't open in new tab`, {
493
+ recommendation: 'Consider using target="_blank" for social links to keep users on your site',
494
+ evidence: {
495
+ found: ctx.socialLinksWithoutNewTab,
496
+ expected: 'Social links typically open in new tabs',
497
+ impact: 'Users leaving your site may not return; opening in new tab preserves their session',
498
+ example: '<a href="https://twitter.com/..." target="_blank" rel="noopener noreferrer">',
499
+ issue: linksWithoutNewTab?.length ? `Without new tab: ${linksWithoutNewTab.join(', ')}` : undefined,
500
+ },
501
+ });
502
+ }
503
+ return createResult({ id: 'social-links-new-tab', name: 'Social Links New Tab', category: 'og', severity: 'info' }, 'pass', 'All social links open in new tabs');
504
+ },
505
+ },
506
+ {
507
+ id: 'social-links-placement',
508
+ name: 'Social Links Placement',
509
+ category: 'og',
510
+ severity: 'info',
511
+ description: 'Social links should be placed in header or footer for easy discovery',
512
+ check: (ctx) => {
513
+ if (!ctx.totalSocialLinks || ctx.totalSocialLinks === 0) {
514
+ return createResult({ id: 'social-links-placement', name: 'Social Links Placement', category: 'og', severity: 'info' }, 'info', 'Not applicable (no social links found)', { recommendation: 'This rule checks social link placement when present' });
515
+ }
516
+ if (ctx.socialLinksInHeader === undefined && ctx.socialLinksInFooter === undefined) {
517
+ return createResult({ id: 'social-links-placement', name: 'Social Links Placement', category: 'og', severity: 'info' }, 'info', 'Not applicable (social link placement not checked)', { recommendation: 'This rule checks social link placement when data is available' });
518
+ }
519
+ const inHeaderOrFooter = (ctx.socialLinksInHeader || 0) + (ctx.socialLinksInFooter || 0);
520
+ if (inHeaderOrFooter === 0) {
521
+ return createResult({ id: 'social-links-placement', name: 'Social Links Placement', category: 'og', severity: 'info' }, 'info', 'Social links not found in header or footer', {
522
+ recommendation: 'Place social media links in header or footer for consistent visibility',
523
+ evidence: {
524
+ found: 'Social links only in body content',
525
+ expected: 'Social links in header and/or footer',
526
+ impact: 'Users expect to find social links in standard locations; hidden links reduce engagement',
527
+ },
528
+ });
529
+ }
530
+ const locations = [];
531
+ if (ctx.socialLinksInHeader && ctx.socialLinksInHeader > 0)
532
+ locations.push(`header (${ctx.socialLinksInHeader})`);
533
+ if (ctx.socialLinksInFooter && ctx.socialLinksInFooter > 0)
534
+ locations.push(`footer (${ctx.socialLinksInFooter})`);
535
+ return createResult({ id: 'social-links-placement', name: 'Social Links Placement', category: 'og', severity: 'info' }, 'pass', `Social links found in ${locations.join(' and ')}`);
536
+ },
537
+ },
538
+ {
539
+ id: 'social-links-diversity',
540
+ name: 'Social Platform Diversity',
541
+ category: 'og',
542
+ severity: 'info',
543
+ description: 'Consider linking to multiple social platforms for broader reach',
544
+ check: (ctx) => {
545
+ if (!ctx.platformsFound || ctx.platformsFound.length === 0) {
546
+ return createResult({ id: 'social-links-diversity', name: 'Social Platform Diversity', category: 'og', severity: 'info' }, 'info', 'Not applicable (no social platforms found)', { recommendation: 'This rule checks social platform diversity when social links are present' });
547
+ }
548
+ const majorPlatforms = ['facebook', 'twitter', 'instagram', 'linkedin', 'youtube'];
549
+ const foundMajor = ctx.platformsFound.filter(p => majorPlatforms.includes(p));
550
+ if (ctx.platformsFound.length === 1) {
551
+ return createResult({ id: 'social-links-diversity', name: 'Social Platform Diversity', category: 'og', severity: 'info' }, 'info', `Only 1 social platform linked: ${ctx.platformsFound[0]}`, {
552
+ recommendation: 'Consider linking to additional relevant social platforms',
553
+ evidence: {
554
+ found: ctx.platformsFound,
555
+ expected: 'Multiple social platforms for broader audience reach',
556
+ impact: 'Different audiences prefer different platforms; diversification increases reach',
557
+ },
558
+ });
559
+ }
560
+ if (foundMajor.length >= 2) {
561
+ return createResult({ id: 'social-links-diversity', name: 'Social Platform Diversity', category: 'og', severity: 'info' }, 'pass', `Found ${ctx.platformsFound.length} social platform(s): ${ctx.platformsFound.slice(0, 5).join(', ')}`, { evidence: { found: ctx.platformsFound } });
562
+ }
563
+ return createResult({ id: 'social-links-diversity', name: 'Social Platform Diversity', category: 'og', severity: 'info' }, 'info', `${ctx.platformsFound.length} social platform(s) linked`, {
564
+ evidence: { found: ctx.platformsFound },
565
+ recommendation: 'Consider adding major platforms like Facebook, Twitter/X, LinkedIn, or Instagram',
566
+ });
567
+ },
568
+ },
569
+ {
570
+ id: 'social-profile-consistency',
571
+ name: 'Social Profile Consistency',
572
+ category: 'og',
573
+ severity: 'info',
574
+ description: 'Social meta tags should be consistent with actual social profile links',
575
+ check: (ctx) => {
576
+ if (!ctx.twitterSite && !ctx.platformsFound?.includes('twitter')) {
577
+ return createResult({ id: 'social-profile-consistency', name: 'Social Profile Consistency', category: 'og', severity: 'info' }, 'info', 'Not applicable (no Twitter meta or links)', { recommendation: 'This rule checks consistency between social meta tags and profile links' });
578
+ }
579
+ const hasTwitterMeta = !!ctx.twitterSite;
580
+ const hasTwitterLink = ctx.platformsFound?.includes('twitter');
581
+ if (hasTwitterMeta && !hasTwitterLink) {
582
+ return createResult({ id: 'social-profile-consistency', name: 'Social Profile Consistency', category: 'og', severity: 'info' }, 'info', 'twitter:site is set but no Twitter profile link found', {
583
+ recommendation: 'Add a link to your Twitter profile for consistency',
584
+ evidence: {
585
+ found: `twitter:site: ${ctx.twitterSite}`,
586
+ expected: 'Matching Twitter profile link on page',
587
+ impact: 'Users may want to follow your Twitter account directly from your site',
588
+ },
589
+ });
590
+ }
591
+ if (!hasTwitterMeta && hasTwitterLink) {
592
+ return createResult({ id: 'social-profile-consistency', name: 'Social Profile Consistency', category: 'og', severity: 'info' }, 'info', 'Twitter profile linked but twitter:site meta tag is missing', {
593
+ recommendation: 'Add twitter:site meta tag for proper Twitter Card attribution',
594
+ evidence: {
595
+ expected: '<meta name="twitter:site" content="@yourusername">',
596
+ impact: 'Twitter Cards will not show your @username when shared',
597
+ },
598
+ });
599
+ }
600
+ return createResult({ id: 'social-profile-consistency', name: 'Social Profile Consistency', category: 'og', severity: 'info' }, 'pass', 'Social meta tags are consistent with profile links');
601
+ },
602
+ },
603
+ ];
@@ -0,0 +1,2 @@
1
+ import { SeoRule } from './types.js';
2
+ export declare const structuralRules: SeoRule[];