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.
- package/README.md +47 -0
- package/dist/bin/recker-linux-x64 +0 -0
- package/dist/bin/recker-macos-x64 +0 -0
- package/dist/bin/recker-win-x64.exe +0 -0
- package/dist/bin/rek.cjs +85152 -100207
- package/dist/browser/ai/adaptive-timeout.d.ts +50 -0
- package/dist/browser/ai/adaptive-timeout.js +208 -0
- package/dist/browser/ai/client.d.ts +22 -0
- package/dist/browser/ai/client.js +294 -0
- package/dist/browser/ai/index.d.ts +14 -0
- package/dist/browser/ai/index.js +11 -0
- package/dist/browser/ai/providers/anthropic.d.ts +63 -0
- package/dist/browser/ai/providers/anthropic.js +370 -0
- package/dist/browser/ai/providers/base.d.ts +48 -0
- package/dist/browser/ai/providers/base.js +150 -0
- package/dist/browser/ai/providers/google.d.ts +59 -0
- package/dist/browser/ai/providers/google.js +305 -0
- package/dist/browser/ai/providers/ollama.d.ts +44 -0
- package/dist/browser/ai/providers/ollama.js +240 -0
- package/dist/browser/ai/providers/openai.d.ts +64 -0
- package/dist/browser/ai/providers/openai.js +298 -0
- package/dist/browser/ai/rate-limiter.d.ts +43 -0
- package/dist/browser/ai/rate-limiter.js +215 -0
- package/dist/browser/ai/vector/index.d.ts +2 -0
- package/dist/browser/ai/vector/index.js +2 -0
- package/dist/browser/ai/vector/similarity.d.ts +2 -0
- package/dist/browser/ai/vector/similarity.js +27 -0
- package/dist/browser/ai/vector/store.d.ts +27 -0
- package/dist/browser/ai/vector/store.js +82 -0
- package/dist/browser/browser/cache.d.ts +2 -40
- package/dist/browser/browser/cache.js +2 -199
- package/dist/browser/browser/index.d.ts +8 -0
- package/dist/browser/browser/index.js +8 -0
- package/dist/browser/browser/recker.d.ts +8 -1
- package/dist/browser/browser/recker.js +8 -2
- package/dist/browser/cache/indexed-db.d.ts +10 -0
- package/dist/browser/cache/indexed-db.js +88 -0
- package/dist/browser/cache/service-worker-cache.d.ts +18 -0
- package/dist/browser/cache/service-worker-cache.js +103 -0
- package/dist/browser/cache.d.ts +2 -40
- package/dist/browser/cache.js +2 -199
- package/dist/browser/constants/user-agents.d.ts +7 -0
- package/dist/browser/constants/user-agents.js +7 -0
- package/dist/browser/core/client.d.ts +2 -0
- package/dist/browser/core/client.js +19 -1
- package/dist/browser/index.d.ts +8 -0
- package/dist/browser/index.js +8 -0
- package/dist/browser/plugins/har-recorder.d.ts +40 -0
- package/dist/browser/plugins/har-recorder.js +120 -0
- package/dist/browser/plugins/network-simulation.d.ts +7 -0
- package/dist/browser/plugins/network-simulation.js +13 -0
- package/dist/browser/presets/android.d.ts +2 -0
- package/dist/browser/presets/android.js +16 -0
- package/dist/browser/presets/anthropic.d.ts +8 -0
- package/dist/browser/presets/anthropic.js +27 -0
- package/dist/browser/presets/aws.d.ts +19 -0
- package/dist/browser/presets/aws.js +68 -0
- package/dist/browser/presets/azure-openai.d.ts +10 -0
- package/dist/browser/presets/azure-openai.js +35 -0
- package/dist/browser/presets/azure.d.ts +41 -0
- package/dist/browser/presets/azure.js +104 -0
- package/dist/browser/presets/chaturbate.d.ts +2 -0
- package/dist/browser/presets/chaturbate.js +17 -0
- package/dist/browser/presets/cloudflare.d.ts +12 -0
- package/dist/browser/presets/cloudflare.js +39 -0
- package/dist/browser/presets/cohere.d.ts +7 -0
- package/dist/browser/presets/cohere.js +22 -0
- package/dist/browser/presets/deepseek.d.ts +7 -0
- package/dist/browser/presets/deepseek.js +22 -0
- package/dist/browser/presets/digitalocean.d.ts +5 -0
- package/dist/browser/presets/digitalocean.js +16 -0
- package/dist/browser/presets/discord.d.ts +6 -0
- package/dist/browser/presets/discord.js +17 -0
- package/dist/browser/presets/elevenlabs.d.ts +6 -0
- package/dist/browser/presets/elevenlabs.js +20 -0
- package/dist/browser/presets/enhancers.d.ts +20 -0
- package/dist/browser/presets/enhancers.js +85 -0
- package/dist/browser/presets/fireworks.d.ts +7 -0
- package/dist/browser/presets/fireworks.js +22 -0
- package/dist/browser/presets/gcp.d.ts +34 -0
- package/dist/browser/presets/gcp.js +91 -0
- package/dist/browser/presets/gemini.d.ts +7 -0
- package/dist/browser/presets/gemini.js +23 -0
- package/dist/browser/presets/github.d.ts +6 -0
- package/dist/browser/presets/github.js +17 -0
- package/dist/browser/presets/gitlab.d.ts +6 -0
- package/dist/browser/presets/gitlab.js +16 -0
- package/dist/browser/presets/groq.d.ts +7 -0
- package/dist/browser/presets/groq.js +22 -0
- package/dist/browser/presets/hubspot.d.ts +9 -0
- package/dist/browser/presets/hubspot.js +28 -0
- package/dist/browser/presets/huggingface.d.ts +7 -0
- package/dist/browser/presets/huggingface.js +23 -0
- package/dist/browser/presets/index.d.ts +47 -0
- package/dist/browser/presets/index.js +47 -0
- package/dist/browser/presets/ios.d.ts +2 -0
- package/dist/browser/presets/ios.js +13 -0
- package/dist/browser/presets/linear.d.ts +5 -0
- package/dist/browser/presets/linear.js +16 -0
- package/dist/browser/presets/mailgun.d.ts +7 -0
- package/dist/browser/presets/mailgun.js +20 -0
- package/dist/browser/presets/meta.d.ts +10 -0
- package/dist/browser/presets/meta.js +33 -0
- package/dist/browser/presets/mistral.d.ts +7 -0
- package/dist/browser/presets/mistral.js +22 -0
- package/dist/browser/presets/notion.d.ts +6 -0
- package/dist/browser/presets/notion.js +17 -0
- package/dist/browser/presets/openai.d.ts +9 -0
- package/dist/browser/presets/openai.js +30 -0
- package/dist/browser/presets/oracle.d.ts +19 -0
- package/dist/browser/presets/oracle.js +117 -0
- package/dist/browser/presets/perplexity.d.ts +7 -0
- package/dist/browser/presets/perplexity.js +22 -0
- package/dist/browser/presets/pinecone.d.ts +8 -0
- package/dist/browser/presets/pinecone.js +42 -0
- package/dist/browser/presets/registry.d.ts +23 -0
- package/dist/browser/presets/registry.js +519 -0
- package/dist/browser/presets/replicate.d.ts +7 -0
- package/dist/browser/presets/replicate.js +23 -0
- package/dist/browser/presets/sendgrid.d.ts +6 -0
- package/dist/browser/presets/sendgrid.js +20 -0
- package/dist/browser/presets/sentry.d.ts +11 -0
- package/dist/browser/presets/sentry.js +48 -0
- package/dist/browser/presets/sinch.d.ts +9 -0
- package/dist/browser/presets/sinch.js +39 -0
- package/dist/browser/presets/slack.d.ts +5 -0
- package/dist/browser/presets/slack.js +16 -0
- package/dist/browser/presets/square.d.ts +10 -0
- package/dist/browser/presets/square.js +33 -0
- package/dist/browser/presets/stripe.d.ts +7 -0
- package/dist/browser/presets/stripe.js +23 -0
- package/dist/browser/presets/supabase.d.ts +6 -0
- package/dist/browser/presets/supabase.js +18 -0
- package/dist/browser/presets/tiktok.d.ts +10 -0
- package/dist/browser/presets/tiktok.js +38 -0
- package/dist/browser/presets/together.d.ts +7 -0
- package/dist/browser/presets/together.js +22 -0
- package/dist/browser/presets/twilio.d.ts +6 -0
- package/dist/browser/presets/twilio.js +17 -0
- package/dist/browser/presets/vercel.d.ts +6 -0
- package/dist/browser/presets/vercel.js +23 -0
- package/dist/browser/presets/vultr.d.ts +5 -0
- package/dist/browser/presets/vultr.js +16 -0
- package/dist/browser/presets/xai.d.ts +8 -0
- package/dist/browser/presets/xai.js +23 -0
- package/dist/browser/presets/youtube.d.ts +5 -0
- package/dist/browser/presets/youtube.js +20 -0
- package/dist/browser/recker.d.ts +8 -1
- package/dist/browser/recker.js +8 -2
- package/dist/browser/scrape/document.d.ts +5 -4
- package/dist/browser/scrape/document.js +89 -76
- package/dist/browser/scrape/element.d.ts +10 -8
- package/dist/browser/scrape/element.js +295 -81
- package/dist/browser/scrape/extractors.d.ts +11 -11
- package/dist/browser/scrape/extractors.js +145 -113
- package/dist/browser/scrape/parser/back.d.ts +1 -0
- package/dist/browser/scrape/parser/back.js +3 -0
- package/dist/browser/scrape/parser/index.d.ts +20 -0
- package/dist/browser/scrape/parser/index.js +19 -0
- package/dist/browser/scrape/parser/matcher.d.ts +30 -0
- package/dist/browser/scrape/parser/matcher.js +99 -0
- package/dist/browser/scrape/parser/nodes/comment.d.ts +12 -0
- package/dist/browser/scrape/parser/nodes/comment.js +21 -0
- package/dist/browser/scrape/parser/nodes/html.d.ts +110 -0
- package/dist/browser/scrape/parser/nodes/html.js +978 -0
- package/dist/browser/scrape/parser/nodes/node.d.ts +18 -0
- package/dist/browser/scrape/parser/nodes/node.js +31 -0
- package/dist/browser/scrape/parser/nodes/text.d.ts +14 -0
- package/dist/browser/scrape/parser/nodes/text.js +30 -0
- package/dist/browser/scrape/parser/nodes/type.d.ts +6 -0
- package/dist/browser/scrape/parser/nodes/type.js +7 -0
- package/dist/browser/scrape/parser/parse.d.ts +1 -0
- package/dist/browser/scrape/parser/parse.js +1 -0
- package/dist/browser/scrape/parser/valid.d.ts +2 -0
- package/dist/browser/scrape/parser/valid.js +5 -0
- package/dist/browser/scrape/parser/void-tag.d.ts +7 -0
- package/dist/browser/scrape/parser/void-tag.js +43 -0
- package/dist/browser/scrape/types.d.ts +7 -0
- package/dist/browser/seo/analyzer.d.ts +59 -0
- package/dist/browser/seo/analyzer.js +1399 -0
- package/dist/browser/seo/keywords.d.ts +16 -0
- package/dist/browser/seo/keywords.js +55 -0
- package/dist/browser/seo/rules/accessibility.d.ts +2 -0
- package/dist/browser/seo/rules/accessibility.js +733 -0
- package/dist/browser/seo/rules/ai-search.d.ts +2 -0
- package/dist/browser/seo/rules/ai-search.js +436 -0
- package/dist/browser/seo/rules/analytics.d.ts +2 -0
- package/dist/browser/seo/rules/analytics.js +306 -0
- package/dist/browser/seo/rules/best-practices.d.ts +2 -0
- package/dist/browser/seo/rules/best-practices.js +195 -0
- package/dist/browser/seo/rules/canonical.d.ts +12 -0
- package/dist/browser/seo/rules/canonical.js +270 -0
- package/dist/browser/seo/rules/content.d.ts +2 -0
- package/dist/browser/seo/rules/content.js +522 -0
- package/dist/browser/seo/rules/crawl.d.ts +2 -0
- package/dist/browser/seo/rules/crawl.js +435 -0
- package/dist/browser/seo/rules/cwv.d.ts +2 -0
- package/dist/browser/seo/rules/cwv.js +248 -0
- package/dist/browser/seo/rules/ecommerce.d.ts +2 -0
- package/dist/browser/seo/rules/ecommerce.js +312 -0
- package/dist/browser/seo/rules/i18n.d.ts +2 -0
- package/dist/browser/seo/rules/i18n.js +288 -0
- package/dist/browser/seo/rules/images.d.ts +2 -0
- package/dist/browser/seo/rules/images.js +255 -0
- package/dist/browser/seo/rules/index.d.ts +52 -0
- package/dist/browser/seo/rules/index.js +159 -0
- package/dist/browser/seo/rules/internal-linking.d.ts +2 -0
- package/dist/browser/seo/rules/internal-linking.js +394 -0
- package/dist/browser/seo/rules/links.d.ts +2 -0
- package/dist/browser/seo/rules/links.js +498 -0
- package/dist/browser/seo/rules/local.d.ts +2 -0
- package/dist/browser/seo/rules/local.js +289 -0
- package/dist/browser/seo/rules/meta.d.ts +2 -0
- package/dist/browser/seo/rules/meta.js +805 -0
- package/dist/browser/seo/rules/mobile.d.ts +2 -0
- package/dist/browser/seo/rules/mobile.js +161 -0
- package/dist/browser/seo/rules/performance.d.ts +2 -0
- package/dist/browser/seo/rules/performance.js +738 -0
- package/dist/browser/seo/rules/pwa.d.ts +2 -0
- package/dist/browser/seo/rules/pwa.js +299 -0
- package/dist/browser/seo/rules/readability.d.ts +2 -0
- package/dist/browser/seo/rules/readability.js +264 -0
- package/dist/browser/seo/rules/redirects.d.ts +16 -0
- package/dist/browser/seo/rules/redirects.js +199 -0
- package/dist/browser/seo/rules/resources.d.ts +2 -0
- package/dist/browser/seo/rules/resources.js +390 -0
- package/dist/browser/seo/rules/schema.d.ts +2 -0
- package/dist/browser/seo/rules/schema.js +379 -0
- package/dist/browser/seo/rules/security.d.ts +2 -0
- package/dist/browser/seo/rules/security.js +877 -0
- package/dist/browser/seo/rules/social.d.ts +2 -0
- package/dist/browser/seo/rules/social.js +603 -0
- package/dist/browser/seo/rules/structural.d.ts +2 -0
- package/dist/browser/seo/rules/structural.js +223 -0
- package/dist/browser/seo/rules/technical-advanced.d.ts +10 -0
- package/dist/browser/seo/rules/technical-advanced.js +289 -0
- package/dist/browser/seo/rules/technical.d.ts +2 -0
- package/dist/browser/seo/rules/technical.js +480 -0
- package/dist/browser/seo/rules/thresholds.d.ts +196 -0
- package/dist/browser/seo/rules/thresholds.js +118 -0
- package/dist/browser/seo/rules/types.d.ts +498 -0
- package/dist/browser/seo/rules/types.js +11 -0
- package/dist/browser/seo/types.d.ts +211 -0
- package/dist/browser/seo/types.js +1 -0
- package/dist/browser/transport/curl.d.ts +4 -0
- package/dist/browser/transport/curl.js +101 -0
- package/dist/browser/transport/undici.js +1 -2
- package/dist/browser/transport/worker.d.ts +18 -0
- package/dist/browser/transport/worker.js +278 -0
- package/dist/browser/types/index.d.ts +4 -1
- package/dist/browser/utils/binary-manager.d.ts +4 -0
- package/dist/browser/utils/binary-manager.js +72 -0
- package/dist/browser/utils/user-agent.js +2 -13
- package/dist/cache/indexed-db.d.ts +10 -0
- package/dist/cache/indexed-db.js +88 -0
- package/dist/cache/service-worker-cache.d.ts +18 -0
- package/dist/cache/service-worker-cache.js +103 -0
- package/dist/cli/commands/ai.d.ts +2 -0
- package/dist/cli/commands/ai.js +162 -0
- package/dist/cli/commands/bench.d.ts +2 -0
- package/dist/cli/commands/bench.js +51 -0
- package/dist/cli/commands/dns.d.ts +2 -0
- package/dist/cli/commands/dns.js +295 -0
- package/dist/cli/commands/har.d.ts +2 -0
- package/dist/cli/commands/har.js +171 -0
- package/dist/cli/commands/hls.d.ts +2 -0
- package/dist/cli/commands/hls.js +192 -0
- package/dist/cli/commands/network.d.ts +2 -0
- package/dist/cli/commands/network.js +288 -0
- package/dist/cli/commands/protocols.d.ts +2 -0
- package/dist/cli/commands/protocols.js +344 -0
- package/dist/cli/commands/scrape.d.ts +2 -0
- package/dist/cli/commands/scrape.js +176 -0
- package/dist/cli/commands/security.d.ts +2 -0
- package/dist/cli/commands/security.js +57 -0
- package/dist/cli/commands/seo.d.ts +2 -0
- package/dist/cli/commands/seo.js +125 -0
- package/dist/cli/commands/serve.d.ts +2 -0
- package/dist/cli/commands/serve.js +531 -0
- package/dist/cli/commands/spider.d.ts +3 -0
- package/dist/cli/commands/spider.js +456 -0
- package/dist/cli/commands/utils.d.ts +2 -0
- package/dist/cli/commands/utils.js +176 -0
- package/dist/cli/commands/vector.d.ts +2 -0
- package/dist/cli/commands/vector.js +158 -0
- package/dist/cli/handler.d.ts +2 -2
- package/dist/cli/handler.js +6 -6
- package/dist/cli/helpers.d.ts +7 -0
- package/dist/cli/helpers.js +128 -0
- package/dist/cli/index.js +96 -5228
- package/dist/cli/parser/help.d.ts +2 -0
- package/dist/cli/parser/help.js +52 -0
- package/dist/cli/parser/index.d.ts +3 -0
- package/dist/cli/parser/index.js +3 -0
- package/dist/cli/parser/parser.d.ts +4 -0
- package/dist/cli/parser/parser.js +146 -0
- package/dist/cli/parser/types.d.ts +41 -0
- package/dist/cli/parser/types.js +1 -0
- package/dist/cli/presets.d.ts +1 -1
- package/dist/cli/presets.js +1 -1
- package/dist/cli/router.d.ts +36 -0
- package/dist/cli/router.js +195 -0
- package/dist/cli/tui/ai-chat.js +1 -1
- package/dist/cli/tui/commands/context.d.ts +9 -0
- package/dist/cli/tui/commands/context.js +1 -0
- package/dist/cli/tui/commands/dns.d.ts +10 -0
- package/dist/cli/tui/commands/dns.js +461 -0
- package/dist/cli/tui/commands/hls.d.ts +2 -0
- package/dist/cli/tui/commands/hls.js +162 -0
- package/dist/cli/tui/commands/ip.d.ts +2 -0
- package/dist/cli/tui/commands/ip.js +45 -0
- package/dist/cli/tui/commands/network.d.ts +3 -0
- package/dist/cli/tui/commands/network.js +81 -0
- package/dist/cli/tui/commands/protocols.d.ts +6 -0
- package/dist/cli/tui/commands/protocols.js +531 -0
- package/dist/cli/tui/commands/security.d.ts +2 -0
- package/dist/cli/tui/commands/security.js +48 -0
- package/dist/cli/tui/commands/seo.d.ts +2 -0
- package/dist/cli/tui/commands/seo.js +74 -0
- package/dist/cli/tui/context.d.ts +12 -0
- package/dist/cli/tui/context.js +1 -0
- package/dist/cli/tui/shell.d.ts +11 -20
- package/dist/cli/tui/shell.js +216 -1873
- package/dist/constants/user-agents.d.ts +7 -0
- package/dist/constants/user-agents.js +7 -0
- package/dist/core/client.d.ts +2 -0
- package/dist/core/client.js +19 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/mcp/cli.js +2 -3
- package/dist/mcp/data/embeddings.json +1 -1
- package/dist/mcp/tools/network.js +298 -158
- package/dist/plugins/har-player.d.ts +23 -0
- package/dist/plugins/har-player.js +49 -0
- package/dist/plugins/har-recorder.d.ts +37 -3
- package/dist/plugins/har-recorder.js +116 -63
- package/dist/plugins/network-simulation.d.ts +7 -0
- package/dist/plugins/network-simulation.js +13 -0
- package/dist/presets/android.d.ts +2 -0
- package/dist/presets/android.js +16 -0
- package/dist/presets/chaturbate.d.ts +2 -0
- package/dist/presets/chaturbate.js +17 -0
- package/dist/presets/elevenlabs.d.ts +6 -0
- package/dist/presets/elevenlabs.js +20 -0
- package/dist/presets/enhancers.d.ts +20 -0
- package/dist/presets/enhancers.js +85 -0
- package/dist/presets/hubspot.d.ts +9 -0
- package/dist/presets/hubspot.js +28 -0
- package/dist/presets/index.d.ts +10 -0
- package/dist/presets/index.js +10 -0
- package/dist/presets/ios.d.ts +2 -0
- package/dist/presets/ios.js +13 -0
- package/dist/presets/pinecone.d.ts +8 -0
- package/dist/presets/pinecone.js +42 -0
- package/dist/presets/registry.js +60 -0
- package/dist/presets/sendgrid.d.ts +6 -0
- package/dist/presets/sendgrid.js +20 -0
- package/dist/presets/sentry.d.ts +11 -0
- package/dist/presets/sentry.js +48 -0
- package/dist/presets/square.d.ts +10 -0
- package/dist/presets/square.js +33 -0
- package/dist/recker.d.ts +3 -0
- package/dist/recker.js +4 -0
- package/dist/scrape/document.d.ts +5 -4
- package/dist/scrape/document.js +89 -76
- package/dist/scrape/element.d.ts +10 -8
- package/dist/scrape/element.js +295 -81
- package/dist/scrape/extractors.d.ts +11 -11
- package/dist/scrape/extractors.js +145 -113
- package/dist/scrape/index.d.ts +2 -0
- package/dist/scrape/index.js +1 -0
- package/dist/scrape/parser/back.d.ts +1 -0
- package/dist/scrape/parser/back.js +3 -0
- package/dist/scrape/parser/index.d.ts +20 -0
- package/dist/scrape/parser/index.js +19 -0
- package/dist/scrape/parser/matcher.d.ts +30 -0
- package/dist/scrape/parser/matcher.js +99 -0
- package/dist/scrape/parser/nodes/comment.d.ts +12 -0
- package/dist/scrape/parser/nodes/comment.js +21 -0
- package/dist/scrape/parser/nodes/html.d.ts +110 -0
- package/dist/scrape/parser/nodes/html.js +978 -0
- package/dist/scrape/parser/nodes/node.d.ts +18 -0
- package/dist/scrape/parser/nodes/node.js +31 -0
- package/dist/scrape/parser/nodes/text.d.ts +14 -0
- package/dist/scrape/parser/nodes/text.js +30 -0
- package/dist/scrape/parser/nodes/type.d.ts +6 -0
- package/dist/scrape/parser/nodes/type.js +7 -0
- package/dist/scrape/parser/parse.d.ts +1 -0
- package/dist/scrape/parser/parse.js +1 -0
- package/dist/scrape/parser/valid.d.ts +2 -0
- package/dist/scrape/parser/valid.js +5 -0
- package/dist/scrape/parser/void-tag.d.ts +7 -0
- package/dist/scrape/parser/void-tag.js +43 -0
- package/dist/scrape/spider.d.ts +19 -0
- package/dist/scrape/spider.js +28 -3
- package/dist/scrape/types.d.ts +7 -0
- package/dist/seo/analyzer.d.ts +15 -5
- package/dist/seo/analyzer.js +636 -175
- package/dist/seo/formatter.d.ts +16 -0
- package/dist/seo/formatter.js +228 -0
- package/dist/seo/index.d.ts +2 -0
- package/dist/seo/index.js +1 -0
- package/dist/seo/keywords.d.ts +16 -0
- package/dist/seo/keywords.js +55 -0
- package/dist/seo/rules/accessibility.js +96 -57
- package/dist/seo/rules/ai-search.js +44 -31
- package/dist/seo/rules/analytics.d.ts +2 -0
- package/dist/seo/rules/analytics.js +306 -0
- package/dist/seo/rules/best-practices.js +21 -14
- package/dist/seo/rules/canonical.js +53 -32
- package/dist/seo/rules/content.js +317 -31
- package/dist/seo/rules/crawl.js +55 -40
- package/dist/seo/rules/cwv.js +21 -15
- package/dist/seo/rules/ecommerce.js +82 -22
- package/dist/seo/rules/i18n.js +75 -36
- package/dist/seo/rules/images.js +109 -30
- package/dist/seo/rules/index.js +2 -0
- package/dist/seo/rules/internal-linking.js +58 -39
- package/dist/seo/rules/links.js +79 -52
- package/dist/seo/rules/local.js +49 -25
- package/dist/seo/rules/meta.js +339 -81
- package/dist/seo/rules/mobile.js +112 -2
- package/dist/seo/rules/performance.js +434 -66
- package/dist/seo/rules/pwa.js +36 -39
- package/dist/seo/rules/readability.js +31 -22
- package/dist/seo/rules/redirects.js +21 -15
- package/dist/seo/rules/resources.js +59 -42
- package/dist/seo/rules/schema.js +333 -8
- package/dist/seo/rules/security.js +142 -80
- package/dist/seo/rules/social.js +277 -47
- package/dist/seo/rules/structural.js +87 -19
- package/dist/seo/rules/technical-advanced.js +30 -24
- package/dist/seo/rules/technical.js +243 -42
- package/dist/seo/rules/types.d.ts +53 -1
- package/dist/seo/seo-spider.d.ts +22 -0
- package/dist/seo/seo-spider.js +77 -13
- package/dist/seo/types.d.ts +8 -1
- package/dist/seo/validators/llms-txt.js +19 -0
- package/dist/seo/validators/rss.d.ts +11 -0
- package/dist/seo/validators/rss.js +93 -0
- package/dist/seo/validators/sitemap.js +36 -26
- package/dist/transport/curl.d.ts +4 -0
- package/dist/transport/curl.js +101 -0
- package/dist/transport/udp.js +0 -1
- package/dist/transport/undici.js +1 -2
- package/dist/transport/worker.d.ts +18 -0
- package/dist/transport/worker.js +278 -0
- package/dist/types/index.d.ts +4 -1
- package/dist/utils/binary-manager.d.ts +4 -0
- package/dist/utils/binary-manager.js +72 -0
- package/dist/utils/optional-require.d.ts +7 -8
- package/dist/utils/optional-require.js +2 -21
- package/dist/utils/upload.d.ts +6 -0
- package/dist/utils/upload.js +11 -0
- package/dist/utils/user-agent.js +2 -13
- package/dist/version.js +1 -1
- package/package.json +12 -6
- package/dist/browser/utils/optional-require.d.ts +0 -19
- package/dist/browser/utils/optional-require.js +0 -105
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { metaRules } from './meta.js';
|
|
2
|
+
import { structuralRules } from './structural.js';
|
|
3
|
+
import { contentRules } from './content.js';
|
|
4
|
+
import { imageRules } from './images.js';
|
|
5
|
+
import { linkRules } from './links.js';
|
|
6
|
+
import { performanceRules } from './performance.js';
|
|
7
|
+
import { technicalRules } from './technical.js';
|
|
8
|
+
import { securityRules } from './security.js';
|
|
9
|
+
import { schemaRules } from './schema.js';
|
|
10
|
+
import { accessibilityRules } from './accessibility.js';
|
|
11
|
+
import { mobileRules } from './mobile.js';
|
|
12
|
+
import { i18nRules } from './i18n.js';
|
|
13
|
+
import { ecommerceRules } from './ecommerce.js';
|
|
14
|
+
import { localRules } from './local.js';
|
|
15
|
+
import { cwvRules } from './cwv.js';
|
|
16
|
+
import { crawlRules } from './crawl.js';
|
|
17
|
+
import { readabilityRules } from './readability.js';
|
|
18
|
+
import { pwaRules } from './pwa.js';
|
|
19
|
+
import { socialRules } from './social.js';
|
|
20
|
+
import { internalLinkingRules } from './internal-linking.js';
|
|
21
|
+
import { bestPracticesRules } from './best-practices.js';
|
|
22
|
+
import { aiSearchRules } from './ai-search.js';
|
|
23
|
+
import { resourceRules } from './resources.js';
|
|
24
|
+
import { technicalAdvancedRules } from './technical-advanced.js';
|
|
25
|
+
import { redirectRules } from './redirects.js';
|
|
26
|
+
import { canonicalRules } from './canonical.js';
|
|
27
|
+
import { analyticsRules } from './analytics.js';
|
|
28
|
+
export * from './types.js';
|
|
29
|
+
export * from './thresholds.js';
|
|
30
|
+
export const ALL_SEO_RULES = [
|
|
31
|
+
...metaRules,
|
|
32
|
+
...structuralRules,
|
|
33
|
+
...contentRules,
|
|
34
|
+
...imageRules,
|
|
35
|
+
...linkRules,
|
|
36
|
+
...performanceRules,
|
|
37
|
+
...technicalRules,
|
|
38
|
+
...securityRules,
|
|
39
|
+
...schemaRules,
|
|
40
|
+
...accessibilityRules,
|
|
41
|
+
...mobileRules,
|
|
42
|
+
...i18nRules,
|
|
43
|
+
...ecommerceRules,
|
|
44
|
+
...localRules,
|
|
45
|
+
...cwvRules,
|
|
46
|
+
...crawlRules,
|
|
47
|
+
...readabilityRules,
|
|
48
|
+
...pwaRules,
|
|
49
|
+
...socialRules,
|
|
50
|
+
...internalLinkingRules,
|
|
51
|
+
...bestPracticesRules,
|
|
52
|
+
...aiSearchRules,
|
|
53
|
+
...resourceRules,
|
|
54
|
+
...technicalAdvancedRules,
|
|
55
|
+
...redirectRules,
|
|
56
|
+
...canonicalRules,
|
|
57
|
+
...analyticsRules,
|
|
58
|
+
];
|
|
59
|
+
export const SCORING_WEIGHTS = {
|
|
60
|
+
severity: {
|
|
61
|
+
error: { pass: 10, fail: -15, warn: -10, info: 0 },
|
|
62
|
+
warning: { pass: 5, fail: -8, warn: -5, info: 0 },
|
|
63
|
+
info: { pass: 2, fail: -3, warn: -2, info: 0 },
|
|
64
|
+
},
|
|
65
|
+
category: {
|
|
66
|
+
title: 1.5,
|
|
67
|
+
meta: 1.3,
|
|
68
|
+
og: 1.0,
|
|
69
|
+
twitter: 0.8,
|
|
70
|
+
headings: 1.2,
|
|
71
|
+
images: 1.0,
|
|
72
|
+
links: 1.1,
|
|
73
|
+
content: 1.2,
|
|
74
|
+
technical: 1.3,
|
|
75
|
+
security: 0.9,
|
|
76
|
+
mobile: 1.2,
|
|
77
|
+
'structured-data': 1.0,
|
|
78
|
+
performance: 1.4,
|
|
79
|
+
accessibility: 0.8,
|
|
80
|
+
'ai-search': 0.7,
|
|
81
|
+
resources: 1.1,
|
|
82
|
+
crawlability: 1.3,
|
|
83
|
+
canonicalization: 1.2,
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
export function calculateWeightedScore(results) {
|
|
87
|
+
const categoryScores = {};
|
|
88
|
+
for (const result of results) {
|
|
89
|
+
const severityWeight = SCORING_WEIGHTS.severity[result.severity];
|
|
90
|
+
const statusScore = severityWeight[result.status] ?? 0;
|
|
91
|
+
if (!categoryScores[result.category]) {
|
|
92
|
+
categoryScores[result.category] = { score: 0, count: 0 };
|
|
93
|
+
}
|
|
94
|
+
categoryScores[result.category].score += statusScore;
|
|
95
|
+
categoryScores[result.category].count++;
|
|
96
|
+
}
|
|
97
|
+
let totalScore = 0;
|
|
98
|
+
let totalWeight = 0;
|
|
99
|
+
const details = [];
|
|
100
|
+
for (const [cat, data] of Object.entries(categoryScores)) {
|
|
101
|
+
const category = cat;
|
|
102
|
+
const categoryWeight = SCORING_WEIGHTS.category[category] ?? 1;
|
|
103
|
+
const normalizedScore = data.count > 0 ? data.score / data.count : 0;
|
|
104
|
+
const weightedScore = normalizedScore * categoryWeight;
|
|
105
|
+
details.push({ category, score: normalizedScore, weight: categoryWeight });
|
|
106
|
+
totalScore += weightedScore;
|
|
107
|
+
totalWeight += categoryWeight;
|
|
108
|
+
}
|
|
109
|
+
const maxPossible = 100;
|
|
110
|
+
const baseScore = 70;
|
|
111
|
+
const adjustedScore = Math.max(0, Math.min(100, baseScore + totalScore));
|
|
112
|
+
return { score: Math.round(adjustedScore), maxPossible, details };
|
|
113
|
+
}
|
|
114
|
+
export class SeoRulesEngine {
|
|
115
|
+
rules;
|
|
116
|
+
constructor(options = {}) {
|
|
117
|
+
let rules = [...ALL_SEO_RULES];
|
|
118
|
+
if (options.categories?.length) {
|
|
119
|
+
rules = rules.filter((r) => options.categories.includes(r.category));
|
|
120
|
+
}
|
|
121
|
+
if (options.excludeCategories?.length) {
|
|
122
|
+
rules = rules.filter((r) => !options.excludeCategories.includes(r.category));
|
|
123
|
+
}
|
|
124
|
+
if (options.rules?.length) {
|
|
125
|
+
rules = rules.filter((r) => options.rules.includes(r.id));
|
|
126
|
+
}
|
|
127
|
+
if (options.excludeRules?.length) {
|
|
128
|
+
rules = rules.filter((r) => !options.excludeRules.includes(r.id));
|
|
129
|
+
}
|
|
130
|
+
if (options.minSeverity) {
|
|
131
|
+
const severityOrder = ['info', 'warning', 'error'];
|
|
132
|
+
const minIndex = severityOrder.indexOf(options.minSeverity);
|
|
133
|
+
rules = rules.filter((r) => severityOrder.indexOf(r.severity) >= minIndex);
|
|
134
|
+
}
|
|
135
|
+
this.rules = rules;
|
|
136
|
+
}
|
|
137
|
+
evaluate(context) {
|
|
138
|
+
const results = [];
|
|
139
|
+
for (const rule of this.rules) {
|
|
140
|
+
const result = rule.check(context);
|
|
141
|
+
if (result) {
|
|
142
|
+
results.push(result);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return results;
|
|
146
|
+
}
|
|
147
|
+
getRules() {
|
|
148
|
+
return [...this.rules];
|
|
149
|
+
}
|
|
150
|
+
getRulesByCategory(category) {
|
|
151
|
+
return this.rules.filter((r) => r.category === category);
|
|
152
|
+
}
|
|
153
|
+
getCategories() {
|
|
154
|
+
return [...new Set(this.rules.map((r) => r.category))];
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
export function createRulesEngine(options) {
|
|
158
|
+
return new SeoRulesEngine(options);
|
|
159
|
+
}
|
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
import { createResult } from './types.js';
|
|
2
|
+
export const internalLinkingRules = [
|
|
3
|
+
{
|
|
4
|
+
id: 'linking-internal-count',
|
|
5
|
+
name: 'Internal Link Count',
|
|
6
|
+
category: 'links',
|
|
7
|
+
severity: 'warning',
|
|
8
|
+
description: 'Pages should have a healthy number of internal links',
|
|
9
|
+
check: (ctx) => {
|
|
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
|
+
}
|
|
13
|
+
const count = ctx.internalLinks;
|
|
14
|
+
if (count === 0) {
|
|
15
|
+
return createResult({ id: 'linking-internal-count', name: 'Internal Link Count', category: 'links', severity: 'warning' }, 'warn', 'No internal links found', {
|
|
16
|
+
recommendation: 'Add internal links to improve navigation and crawlability',
|
|
17
|
+
evidence: {
|
|
18
|
+
found: 0,
|
|
19
|
+
expected: 'At least 3-5 internal links per page',
|
|
20
|
+
impact: 'Pages without internal links are harder to discover and may not pass link equity',
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
if (count < 3) {
|
|
25
|
+
return createResult({ id: 'linking-internal-count', name: 'Internal Link Count', category: 'links', severity: 'warning' }, 'info', `Only ${count} internal link(s)`, {
|
|
26
|
+
recommendation: 'Consider adding more internal links',
|
|
27
|
+
evidence: {
|
|
28
|
+
found: count,
|
|
29
|
+
expected: 'At least 3-5 internal links',
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
return createResult({ id: 'linking-internal-count', name: 'Internal Link Count', category: 'links', severity: 'warning' }, 'pass', `${count} internal links`);
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
id: 'linking-internal-ratio',
|
|
38
|
+
name: 'Internal/External Link Ratio',
|
|
39
|
+
category: 'links',
|
|
40
|
+
severity: 'info',
|
|
41
|
+
description: 'Pages should have more internal than external links',
|
|
42
|
+
check: (ctx) => {
|
|
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
|
+
}
|
|
49
|
+
const internal = ctx.internalLinks;
|
|
50
|
+
const external = ctx.externalLinks;
|
|
51
|
+
const ratio = internal / (external || 1);
|
|
52
|
+
if (external > internal && external > 5) {
|
|
53
|
+
return createResult({ id: 'linking-internal-ratio', name: 'Internal/External Link Ratio', category: 'links', severity: 'info' }, 'info', `More external (${external}) than internal (${internal}) links`, {
|
|
54
|
+
recommendation: 'Consider adding more internal links for better link equity',
|
|
55
|
+
evidence: {
|
|
56
|
+
found: `${internal} internal, ${external} external`,
|
|
57
|
+
expected: 'Internal links should exceed external links',
|
|
58
|
+
impact: 'External links pass PageRank to other sites',
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
return createResult({ id: 'linking-internal-ratio', name: 'Internal/External Link Ratio', category: 'links', severity: 'info' }, 'pass', `Ratio: ${ratio.toFixed(1)}:1 (internal:external)`);
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
id: 'linking-anchor-diversity',
|
|
67
|
+
name: 'Anchor Text Diversity',
|
|
68
|
+
category: 'links',
|
|
69
|
+
severity: 'info',
|
|
70
|
+
description: 'Internal links should use diverse, descriptive anchor text',
|
|
71
|
+
check: (ctx) => {
|
|
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
|
+
}
|
|
75
|
+
const internalLinks = ctx.allLinks.filter(l => l.type === 'internal');
|
|
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
|
+
}
|
|
79
|
+
const anchorCounts = {};
|
|
80
|
+
for (const link of internalLinks) {
|
|
81
|
+
const anchor = (link.text || '').toLowerCase().trim();
|
|
82
|
+
if (anchor && anchor.length > 2) {
|
|
83
|
+
anchorCounts[anchor] = (anchorCounts[anchor] || 0) + 1;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
const overused = Object.entries(anchorCounts)
|
|
87
|
+
.filter(([_, count]) => count >= 3)
|
|
88
|
+
.map(([anchor, count]) => `"${anchor}" (${count}x)`);
|
|
89
|
+
if (overused.length > 0) {
|
|
90
|
+
return createResult({ id: 'linking-anchor-diversity', name: 'Anchor Text Diversity', category: 'links', severity: 'info' }, 'info', `Some anchor texts overused`, {
|
|
91
|
+
recommendation: 'Vary anchor text for better SEO signal distribution',
|
|
92
|
+
evidence: {
|
|
93
|
+
found: overused.slice(0, 3),
|
|
94
|
+
impact: 'Repetitive anchors may look spammy to search engines',
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
return createResult({ id: 'linking-anchor-diversity', name: 'Anchor Text Diversity', category: 'links', severity: 'info' }, 'pass', 'Good anchor text diversity');
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
id: 'linking-deep-links',
|
|
103
|
+
name: 'Deep Linking',
|
|
104
|
+
category: 'links',
|
|
105
|
+
severity: 'info',
|
|
106
|
+
description: 'Pages should link to deep content, not just homepage',
|
|
107
|
+
check: (ctx) => {
|
|
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
|
+
}
|
|
111
|
+
const internalLinks = ctx.allLinks.filter(l => l.type === 'internal');
|
|
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
|
+
}
|
|
115
|
+
let rootLinks = 0;
|
|
116
|
+
let deepLinks = 0;
|
|
117
|
+
for (const link of internalLinks) {
|
|
118
|
+
try {
|
|
119
|
+
const url = new URL(link.href, ctx.url);
|
|
120
|
+
if (url.pathname === '/' || url.pathname === '') {
|
|
121
|
+
rootLinks++;
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
deepLinks++;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
const deepRatio = deepLinks / internalLinks.length;
|
|
132
|
+
if (rootLinks > deepLinks && internalLinks.length > 5) {
|
|
133
|
+
return createResult({ id: 'linking-deep-links', name: 'Deep Linking', category: 'links', severity: 'info' }, 'info', `Most internal links go to homepage`, {
|
|
134
|
+
recommendation: 'Add more links to inner pages',
|
|
135
|
+
evidence: {
|
|
136
|
+
found: `${rootLinks} homepage links, ${deepLinks} deep links`,
|
|
137
|
+
expected: 'More deep links than homepage links',
|
|
138
|
+
impact: 'Deep linking improves crawlability of inner pages',
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
return createResult({ id: 'linking-deep-links', name: 'Deep Linking', category: 'links', severity: 'info' }, 'pass', `${Math.round(deepRatio * 100)}% deep links`);
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
id: 'linking-nav-links',
|
|
147
|
+
name: 'Navigation Links',
|
|
148
|
+
category: 'links',
|
|
149
|
+
severity: 'info',
|
|
150
|
+
description: 'Check for proper navigation link structure',
|
|
151
|
+
check: (ctx) => {
|
|
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
|
+
}
|
|
158
|
+
if (ctx.navLinkCount === 0) {
|
|
159
|
+
return createResult({ id: 'linking-nav-links', name: 'Navigation Links', category: 'links', severity: 'info' }, 'warn', 'Navigation element has no links', {
|
|
160
|
+
recommendation: 'Add links to navigation for user experience and SEO',
|
|
161
|
+
evidence: {
|
|
162
|
+
found: '<nav> element with no links',
|
|
163
|
+
expected: 'Navigation should contain meaningful links',
|
|
164
|
+
},
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
if (ctx.navLinkCount > 20) {
|
|
168
|
+
return createResult({ id: 'linking-nav-links', name: 'Navigation Links', category: 'links', severity: 'info' }, 'info', `Navigation has ${ctx.navLinkCount} links (high)`, {
|
|
169
|
+
recommendation: 'Consider simplifying navigation',
|
|
170
|
+
evidence: {
|
|
171
|
+
found: ctx.navLinkCount,
|
|
172
|
+
expected: 'Under 20 links for optimal UX',
|
|
173
|
+
impact: 'Too many nav links may dilute link equity',
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
return createResult({ id: 'linking-nav-links', name: 'Navigation Links', category: 'links', severity: 'info' }, 'pass', `${ctx.navLinkCount} navigation links`);
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
id: 'linking-footer-links',
|
|
182
|
+
name: 'Footer Links',
|
|
183
|
+
category: 'links',
|
|
184
|
+
severity: 'info',
|
|
185
|
+
description: 'Footer should contain important site-wide links',
|
|
186
|
+
check: (ctx) => {
|
|
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
|
+
}
|
|
193
|
+
if (ctx.footerLinkCount === 0) {
|
|
194
|
+
return createResult({ id: 'linking-footer-links', name: 'Footer Links', category: 'links', severity: 'info' }, 'info', 'Footer has no links', {
|
|
195
|
+
recommendation: 'Add important links to footer',
|
|
196
|
+
evidence: {
|
|
197
|
+
expected: 'Links to privacy policy, terms, contact, sitemap',
|
|
198
|
+
},
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
if (ctx.footerLinkCount > 50) {
|
|
202
|
+
return createResult({ id: 'linking-footer-links', name: 'Footer Links', category: 'links', severity: 'info' }, 'info', `Footer has ${ctx.footerLinkCount} links (excessive)`, {
|
|
203
|
+
recommendation: 'Reduce footer links to essential pages',
|
|
204
|
+
evidence: {
|
|
205
|
+
found: ctx.footerLinkCount,
|
|
206
|
+
impact: 'Excessive footer links may be seen as link spam',
|
|
207
|
+
},
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
return createResult({ id: 'linking-footer-links', name: 'Footer Links', category: 'links', severity: 'info' }, 'pass', `${ctx.footerLinkCount} footer links`);
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
id: 'linking-contextual',
|
|
215
|
+
name: 'Contextual Links',
|
|
216
|
+
category: 'links',
|
|
217
|
+
severity: 'info',
|
|
218
|
+
description: 'Check for in-content contextual links',
|
|
219
|
+
check: (ctx) => {
|
|
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
|
+
}
|
|
226
|
+
const count = ctx.contextualLinkCount;
|
|
227
|
+
if (count === 0) {
|
|
228
|
+
return createResult({ id: 'linking-contextual', name: 'Contextual Links', category: 'links', severity: 'info' }, 'info', 'No contextual links in content', {
|
|
229
|
+
recommendation: 'Add links within body content to related pages',
|
|
230
|
+
evidence: {
|
|
231
|
+
expected: 'At least 2-3 contextual links per 500 words',
|
|
232
|
+
impact: 'Contextual links pass more link equity than navigation links',
|
|
233
|
+
},
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
const linksPerWords = (count / ctx.wordCount) * 500;
|
|
237
|
+
if (linksPerWords < 1 && ctx.wordCount > 500) {
|
|
238
|
+
return createResult({ id: 'linking-contextual', name: 'Contextual Links', category: 'links', severity: 'info' }, 'info', `Only ${count} contextual link(s) in ${ctx.wordCount} words`, {
|
|
239
|
+
recommendation: 'Add more in-content links',
|
|
240
|
+
evidence: {
|
|
241
|
+
found: `${linksPerWords.toFixed(1)} links per 500 words`,
|
|
242
|
+
expected: '2-3 links per 500 words',
|
|
243
|
+
},
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
return createResult({ id: 'linking-contextual', name: 'Contextual Links', category: 'links', severity: 'info' }, 'pass', `${count} contextual links`);
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
id: 'linking-orphan-page',
|
|
251
|
+
name: 'Orphan Page Detection',
|
|
252
|
+
category: 'links',
|
|
253
|
+
severity: 'warning',
|
|
254
|
+
description: 'Pages should be linked from other pages on the site',
|
|
255
|
+
check: (ctx) => {
|
|
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
|
+
}
|
|
259
|
+
if (ctx.incomingInternalLinks === 0) {
|
|
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)', {
|
|
261
|
+
recommendation: 'Link to this page from other pages on your site',
|
|
262
|
+
evidence: {
|
|
263
|
+
found: '0 incoming internal links detected',
|
|
264
|
+
impact: 'Orphan pages are harder for search engines to discover',
|
|
265
|
+
learnMore: 'https://ahrefs.com/blog/orphan-pages/',
|
|
266
|
+
},
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
return createResult({ id: 'linking-orphan-page', name: 'Orphan Page Detection', category: 'links', severity: 'warning' }, 'pass', `${ctx.incomingInternalLinks} incoming internal link(s)`);
|
|
270
|
+
},
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
id: 'linking-self-referencing',
|
|
274
|
+
name: 'Self-Referencing Links',
|
|
275
|
+
category: 'links',
|
|
276
|
+
severity: 'info',
|
|
277
|
+
description: 'Avoid excessive self-referencing links',
|
|
278
|
+
check: (ctx) => {
|
|
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
|
+
}
|
|
282
|
+
if (ctx.selfReferencingLinks > 3) {
|
|
283
|
+
return createResult({ id: 'linking-self-referencing', name: 'Self-Referencing Links', category: 'links', severity: 'info' }, 'info', `${ctx.selfReferencingLinks} self-referencing links`, {
|
|
284
|
+
recommendation: 'Reduce links that point to the current page',
|
|
285
|
+
evidence: {
|
|
286
|
+
found: ctx.selfReferencingLinks,
|
|
287
|
+
expected: '0-1 self-referencing links (e.g., canonical only)',
|
|
288
|
+
impact: 'Self-links waste crawl budget and confuse users',
|
|
289
|
+
},
|
|
290
|
+
});
|
|
291
|
+
}
|
|
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)' });
|
|
293
|
+
},
|
|
294
|
+
},
|
|
295
|
+
{
|
|
296
|
+
id: 'linking-broken-internal',
|
|
297
|
+
name: 'Broken Internal Links',
|
|
298
|
+
category: 'links',
|
|
299
|
+
severity: 'error',
|
|
300
|
+
description: 'Internal links should not be broken',
|
|
301
|
+
check: (ctx) => {
|
|
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
|
+
}
|
|
305
|
+
if (ctx.brokenInternalLinks.length > 0) {
|
|
306
|
+
return createResult({ id: 'linking-broken-internal', name: 'Broken Internal Links', category: 'links', severity: 'error' }, 'fail', `${ctx.brokenInternalLinks.length} broken internal link(s)`, {
|
|
307
|
+
recommendation: 'Fix or remove broken internal links',
|
|
308
|
+
evidence: {
|
|
309
|
+
found: ctx.brokenInternalLinks.slice(0, 5),
|
|
310
|
+
expected: '0 broken links',
|
|
311
|
+
impact: 'Broken links waste crawl budget and harm user experience',
|
|
312
|
+
},
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
return createResult({ id: 'linking-broken-internal', name: 'Broken Internal Links', category: 'links', severity: 'error' }, 'pass', 'No broken internal links');
|
|
316
|
+
},
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
id: 'linking-redirect-chains',
|
|
320
|
+
name: 'Redirect Chains',
|
|
321
|
+
category: 'links',
|
|
322
|
+
severity: 'warning',
|
|
323
|
+
description: 'Internal links should not go through redirect chains',
|
|
324
|
+
check: (ctx) => {
|
|
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
|
+
}
|
|
328
|
+
if (ctx.redirectChainLinks.length > 0) {
|
|
329
|
+
return createResult({ id: 'linking-redirect-chains', name: 'Redirect Chains', category: 'links', severity: 'warning' }, 'warn', `${ctx.redirectChainLinks.length} link(s) go through redirects`, {
|
|
330
|
+
recommendation: 'Update links to point to final destination URLs',
|
|
331
|
+
evidence: {
|
|
332
|
+
found: ctx.redirectChainLinks.slice(0, 5).map(r => `${r.from} → ${r.to} (${r.hops} hops)`),
|
|
333
|
+
expected: '0 redirect chain links',
|
|
334
|
+
impact: 'Redirect chains slow down crawling and lose link equity',
|
|
335
|
+
},
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
return createResult({ id: 'linking-redirect-chains', name: 'Redirect Chains', category: 'links', severity: 'warning' }, 'pass', 'No redirect chain links');
|
|
339
|
+
},
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
id: 'linking-nofollow-internal',
|
|
343
|
+
name: 'Nofollow Internal Links',
|
|
344
|
+
category: 'links',
|
|
345
|
+
severity: 'warning',
|
|
346
|
+
description: 'Internal links should not use nofollow',
|
|
347
|
+
check: (ctx) => {
|
|
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
|
+
}
|
|
351
|
+
const nofollowInternal = ctx.allLinks.filter(l => l.type === 'internal' && l.rel?.includes('nofollow'));
|
|
352
|
+
if (nofollowInternal.length > 0) {
|
|
353
|
+
return createResult({ id: 'linking-nofollow-internal', name: 'Nofollow Internal Links', category: 'links', severity: 'warning' }, 'warn', `${nofollowInternal.length} internal link(s) have nofollow`, {
|
|
354
|
+
recommendation: 'Remove nofollow from internal links',
|
|
355
|
+
evidence: {
|
|
356
|
+
found: nofollowInternal.slice(0, 3).map(l => l.href),
|
|
357
|
+
impact: 'Nofollow on internal links wastes PageRank',
|
|
358
|
+
learnMore: 'https://developers.google.com/search/docs/crawling-indexing/qualify-outbound-links',
|
|
359
|
+
},
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
return createResult({ id: 'linking-nofollow-internal', name: 'Nofollow Internal Links', category: 'links', severity: 'warning' }, 'pass', 'No nofollow on internal links');
|
|
363
|
+
},
|
|
364
|
+
},
|
|
365
|
+
{
|
|
366
|
+
id: 'linking-click-depth',
|
|
367
|
+
name: 'Click Depth',
|
|
368
|
+
category: 'links',
|
|
369
|
+
severity: 'info',
|
|
370
|
+
description: 'Important pages should be reachable in few clicks',
|
|
371
|
+
check: (ctx) => {
|
|
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
|
+
}
|
|
375
|
+
const depth = ctx.pageClickDepth;
|
|
376
|
+
if (depth > 4) {
|
|
377
|
+
return createResult({ id: 'linking-click-depth', name: 'Click Depth', category: 'links', severity: 'info' }, 'warn', `Page is ${depth} clicks from homepage`, {
|
|
378
|
+
recommendation: 'Improve site architecture for better accessibility',
|
|
379
|
+
evidence: {
|
|
380
|
+
found: `${depth} clicks deep`,
|
|
381
|
+
expected: 'Under 4 clicks from homepage',
|
|
382
|
+
impact: 'Deep pages receive less crawl priority and link equity',
|
|
383
|
+
},
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
if (depth > 3) {
|
|
387
|
+
return createResult({ id: 'linking-click-depth', name: 'Click Depth', category: 'links', severity: 'info' }, 'info', `Page is ${depth} clicks from homepage`, {
|
|
388
|
+
recommendation: 'Consider adding shortcuts to this page',
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
return createResult({ id: 'linking-click-depth', name: 'Click Depth', category: 'links', severity: 'info' }, 'pass', `${depth} click(s) from homepage`);
|
|
392
|
+
},
|
|
393
|
+
},
|
|
394
|
+
];
|