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