recker 1.0.42 → 1.0.43-next.7a08602
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +93958 -0
- 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 +722 -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 +258 -0
- package/dist/browser/seo/rules/content.d.ts +2 -0
- package/dist/browser/seo/rules/content.js +424 -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 +283 -0
- package/dist/browser/seo/rules/i18n.d.ts +2 -0
- package/dist/browser/seo/rules/i18n.js +277 -0
- package/dist/browser/seo/rules/images.d.ts +2 -0
- package/dist/browser/seo/rules/images.js +235 -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 +490 -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 +646 -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 +625 -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 +179 -0
- package/dist/browser/seo/rules/technical-advanced.d.ts +10 -0
- package/dist/browser/seo/rules/technical-advanced.js +288 -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 -0
- 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 +85 -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 +31 -22
- package/dist/seo/rules/content.js +207 -19
- package/dist/seo/rules/crawl.js +55 -40
- package/dist/seo/rules/cwv.js +21 -15
- package/dist/seo/rules/ecommerce.js +51 -20
- package/dist/seo/rules/i18n.js +61 -33
- package/dist/seo/rules/images.js +87 -28
- package/dist/seo/rules/index.js +2 -0
- package/dist/seo/rules/internal-linking.js +58 -39
- package/dist/seo/rules/links.js +70 -51
- package/dist/seo/rules/local.js +49 -25
- package/dist/seo/rules/meta.js +161 -62
- package/dist/seo/rules/mobile.js +112 -2
- package/dist/seo/rules/performance.js +309 -54
- 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 +40 -16
- package/dist/seo/rules/technical-advanced.js +27 -22
- 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 +14 -5
- package/dist/browser/utils/optional-require.d.ts +0 -19
- package/dist/browser/utils/optional-require.js +0 -105
package/dist/seo/rules/meta.js
CHANGED
|
@@ -19,7 +19,7 @@ export const metaRules = [
|
|
|
19
19
|
},
|
|
20
20
|
});
|
|
21
21
|
}
|
|
22
|
-
return
|
|
22
|
+
return createResult({ id: 'title-exists', name: 'Title Tag Exists', category: 'title', severity: 'error' }, 'info', 'Not applicable (page has title tag)', { recommendation: 'This rule checks for the presence of a title tag' });
|
|
23
23
|
},
|
|
24
24
|
},
|
|
25
25
|
{
|
|
@@ -29,15 +29,32 @@ export const metaRules = [
|
|
|
29
29
|
severity: 'warning',
|
|
30
30
|
description: 'Title should be between 50-60 characters',
|
|
31
31
|
check: (ctx) => {
|
|
32
|
-
if (!ctx.title)
|
|
33
|
-
return
|
|
32
|
+
if (!ctx.title) {
|
|
33
|
+
return createResult({ id: 'title-length', name: 'Title Length', category: 'title', severity: 'warning' }, 'info', 'Not applicable (no title tag)', { recommendation: 'This rule checks title length when a title tag is present' });
|
|
34
|
+
}
|
|
34
35
|
const len = ctx.titleLength ?? ctx.title.length;
|
|
35
36
|
const { min, ideal, max } = SEO_THRESHOLDS.title;
|
|
36
37
|
if (len < min) {
|
|
37
|
-
return createResult({ id: 'title-length', name: 'Title Length', category: 'title', severity: 'warning' }, 'warn', `Title too short (${len} chars, min: ${min})`, {
|
|
38
|
+
return createResult({ id: 'title-length', name: 'Title Length', category: 'title', severity: 'warning' }, 'warn', `Title too short (${len} chars, min: ${min})`, {
|
|
39
|
+
value: len,
|
|
40
|
+
recommendation: `Expand title to ${ideal.min}-${ideal.max} characters. Ensure it includes target keywords and encourages clicks.`,
|
|
41
|
+
evidence: {
|
|
42
|
+
found: `${len} characters`,
|
|
43
|
+
expected: `${ideal.min}-${ideal.max} characters`,
|
|
44
|
+
impact: 'Short titles limit keyword potential and may be replaced by Google.'
|
|
45
|
+
}
|
|
46
|
+
});
|
|
38
47
|
}
|
|
39
48
|
if (len > max) {
|
|
40
|
-
return createResult({ id: 'title-length', name: 'Title Length', category: 'title', severity: 'warning' }, 'warn', `Title too long (${len} chars, will be truncated after ~60)`, {
|
|
49
|
+
return createResult({ id: 'title-length', name: 'Title Length', category: 'title', severity: 'warning' }, 'warn', `Title too long (${len} chars, will be truncated after ~60)`, {
|
|
50
|
+
value: len,
|
|
51
|
+
recommendation: `Shorten title to under ${ideal.max} characters to ensure visibility in SERPs.`,
|
|
52
|
+
evidence: {
|
|
53
|
+
found: `${len} characters`,
|
|
54
|
+
expected: `< ${max} characters`,
|
|
55
|
+
impact: 'Truncated titles may lose click-through rate if key information is hidden.'
|
|
56
|
+
}
|
|
57
|
+
});
|
|
41
58
|
}
|
|
42
59
|
if (len >= ideal.min && len <= ideal.max) {
|
|
43
60
|
return createResult({ id: 'title-length', name: 'Title Length', category: 'title', severity: 'warning' }, 'pass', `Title length ideal (${len} chars)`, { value: len });
|
|
@@ -52,14 +69,23 @@ export const metaRules = [
|
|
|
52
69
|
severity: 'warning',
|
|
53
70
|
description: 'Title should not be ALL CAPS',
|
|
54
71
|
check: (ctx) => {
|
|
55
|
-
if (!ctx.title)
|
|
56
|
-
return
|
|
72
|
+
if (!ctx.title) {
|
|
73
|
+
return createResult({ id: 'title-no-caps', name: 'Title Case', category: 'title', severity: 'warning' }, 'info', 'Not applicable (no title tag)', { recommendation: 'This rule checks title capitalization when a title tag is present' });
|
|
74
|
+
}
|
|
57
75
|
const words = ctx.title.split(/\s+/).filter((w) => w.length > 3);
|
|
58
76
|
const allCapsWords = words.filter((w) => w === w.toUpperCase() && /[A-Z]/.test(w));
|
|
59
77
|
if (allCapsWords.length > words.length / 2) {
|
|
60
|
-
return createResult({ id: 'title-no-caps', name: 'Title Case', category: 'title', severity: 'warning' }, 'warn', 'Title appears to be ALL CAPS', {
|
|
78
|
+
return createResult({ id: 'title-no-caps', name: 'Title Case', category: 'title', severity: 'warning' }, 'warn', 'Title appears to be ALL CAPS', {
|
|
79
|
+
recommendation: 'Use title case or sentence case for better readability and click-through rate.',
|
|
80
|
+
evidence: {
|
|
81
|
+
found: ctx.title,
|
|
82
|
+
expected: 'Normal capitalization (Title Case or Sentence case)',
|
|
83
|
+
impact: 'ALL CAPS titles look spammy and may be ignored by users. Google may also rewrite them.',
|
|
84
|
+
example: 'Instead of "BUY SHOES ONLINE NOW", use "Buy Shoes Online - Free Shipping"'
|
|
85
|
+
}
|
|
86
|
+
});
|
|
61
87
|
}
|
|
62
|
-
return
|
|
88
|
+
return createResult({ id: 'title-no-caps', name: 'Title Case', category: 'title', severity: 'warning' }, 'info', 'Not applicable (title uses proper capitalization)', { recommendation: 'This rule checks for excessive ALL CAPS usage in title' });
|
|
63
89
|
},
|
|
64
90
|
},
|
|
65
91
|
{
|
|
@@ -69,14 +95,15 @@ export const metaRules = [
|
|
|
69
95
|
severity: 'warning',
|
|
70
96
|
description: 'Title and H1 should be similar but not identical',
|
|
71
97
|
check: (ctx) => {
|
|
72
|
-
if (!ctx.title || !ctx.h1Text)
|
|
73
|
-
return
|
|
98
|
+
if (!ctx.title || !ctx.h1Text) {
|
|
99
|
+
return createResult({ id: 'title-h1-different', name: 'Title vs H1', category: 'title', severity: 'warning' }, 'info', 'Not applicable (missing title or H1)', { recommendation: 'This rule compares title and H1 when both are present' });
|
|
100
|
+
}
|
|
74
101
|
const titleNorm = ctx.title.toLowerCase().trim();
|
|
75
102
|
const h1Norm = ctx.h1Text.toLowerCase().trim();
|
|
76
103
|
if (titleNorm === h1Norm) {
|
|
77
104
|
return createResult({ id: 'title-h1-different', name: 'Title vs H1', category: 'title', severity: 'warning' }, 'warn', 'Title and H1 are identical', { recommendation: 'Consider making H1 slightly different from title for variety' });
|
|
78
105
|
}
|
|
79
|
-
return
|
|
106
|
+
return createResult({ id: 'title-h1-different', name: 'Title vs H1', category: 'title', severity: 'warning' }, 'info', 'Not applicable (title and H1 are different)', { recommendation: 'This rule checks if title and H1 are identical' });
|
|
80
107
|
},
|
|
81
108
|
},
|
|
82
109
|
{
|
|
@@ -97,7 +124,7 @@ export const metaRules = [
|
|
|
97
124
|
},
|
|
98
125
|
});
|
|
99
126
|
}
|
|
100
|
-
return
|
|
127
|
+
return createResult({ id: 'meta-description-exists', name: 'Meta Description Exists', category: 'meta', severity: 'error' }, 'info', 'Not applicable (page has meta description)', { recommendation: 'This rule checks for the presence of a meta description' });
|
|
101
128
|
},
|
|
102
129
|
},
|
|
103
130
|
{
|
|
@@ -107,15 +134,32 @@ export const metaRules = [
|
|
|
107
134
|
severity: 'warning',
|
|
108
135
|
description: 'Meta description should be 120-155 characters',
|
|
109
136
|
check: (ctx) => {
|
|
110
|
-
if (!ctx.metaDescription)
|
|
111
|
-
return
|
|
137
|
+
if (!ctx.metaDescription) {
|
|
138
|
+
return createResult({ id: 'meta-description-length', name: 'Meta Description Length', category: 'meta', severity: 'warning' }, 'info', 'Not applicable (no meta description)', { recommendation: 'This rule checks meta description length when present' });
|
|
139
|
+
}
|
|
112
140
|
const len = ctx.metaDescriptionLength ?? ctx.metaDescription.length;
|
|
113
141
|
const { min, ideal, max } = SEO_THRESHOLDS.metaDescription;
|
|
114
142
|
if (len < min) {
|
|
115
|
-
return createResult({ id: 'meta-description-length', name: 'Meta Description Length', category: 'meta', severity: 'warning' }, 'warn', `Description too short (${len} chars, min: ${min})`, {
|
|
143
|
+
return createResult({ id: 'meta-description-length', name: 'Meta Description Length', category: 'meta', severity: 'warning' }, 'warn', `Description too short (${len} chars, min: ${min})`, {
|
|
144
|
+
value: len,
|
|
145
|
+
recommendation: `Expand to ${ideal.min}-${ideal.max} characters. Summarize content and include keywords naturally.`,
|
|
146
|
+
evidence: {
|
|
147
|
+
found: `${len} characters`,
|
|
148
|
+
expected: `${ideal.min}-${ideal.max} characters`,
|
|
149
|
+
impact: 'Short descriptions may be ignored by search engines in favor of auto-generated snippets.'
|
|
150
|
+
}
|
|
151
|
+
});
|
|
116
152
|
}
|
|
117
153
|
if (len > max) {
|
|
118
|
-
return createResult({ id: 'meta-description-length', name: 'Meta Description Length', category: 'meta', severity: 'warning' }, 'warn', `Description may be truncated (${len} chars, max: ${max})`, {
|
|
154
|
+
return createResult({ id: 'meta-description-length', name: 'Meta Description Length', category: 'meta', severity: 'warning' }, 'warn', `Description may be truncated (${len} chars, max: ${max})`, {
|
|
155
|
+
value: len,
|
|
156
|
+
recommendation: `Shorten to under ${max} characters. Ensure the most important info is at the start.`,
|
|
157
|
+
evidence: {
|
|
158
|
+
found: `${len} characters`,
|
|
159
|
+
expected: `< ${max} characters`,
|
|
160
|
+
impact: 'Truncated descriptions look unprofessional and may lower CTR.'
|
|
161
|
+
}
|
|
162
|
+
});
|
|
119
163
|
}
|
|
120
164
|
if (len >= ideal.min && len <= ideal.max) {
|
|
121
165
|
return createResult({ id: 'meta-description-length', name: 'Meta Description Length', category: 'meta', severity: 'warning' }, 'pass', `Description length ideal (${len} chars)`, { value: len });
|
|
@@ -130,8 +174,9 @@ export const metaRules = [
|
|
|
130
174
|
severity: 'info',
|
|
131
175
|
description: 'Meta description should be unique and compelling',
|
|
132
176
|
check: (ctx) => {
|
|
133
|
-
if (!ctx.metaDescription)
|
|
134
|
-
return
|
|
177
|
+
if (!ctx.metaDescription) {
|
|
178
|
+
return createResult({ id: 'meta-description-unique', name: 'Description Quality', category: 'meta', severity: 'info' }, 'info', 'Not applicable (no meta description)', { recommendation: 'This rule checks meta description quality when present' });
|
|
179
|
+
}
|
|
135
180
|
const desc = ctx.metaDescription.toLowerCase();
|
|
136
181
|
const placeholders = ['lorem ipsum', 'description here', 'todo', 'placeholder', 'change this'];
|
|
137
182
|
for (const placeholder of placeholders) {
|
|
@@ -139,7 +184,7 @@ export const metaRules = [
|
|
|
139
184
|
return createResult({ id: 'meta-description-unique', name: 'Description Quality', category: 'meta', severity: 'info' }, 'warn', 'Meta description appears to be a placeholder', { recommendation: 'Replace with a unique, compelling description for better CTR' });
|
|
140
185
|
}
|
|
141
186
|
}
|
|
142
|
-
return
|
|
187
|
+
return createResult({ id: 'meta-description-unique', name: 'Description Quality', category: 'meta', severity: 'info' }, 'info', 'Not applicable (description has good quality)', { recommendation: 'This rule checks for placeholder patterns in meta description' });
|
|
143
188
|
},
|
|
144
189
|
},
|
|
145
190
|
{
|
|
@@ -160,7 +205,7 @@ export const metaRules = [
|
|
|
160
205
|
},
|
|
161
206
|
});
|
|
162
207
|
}
|
|
163
|
-
return
|
|
208
|
+
return createResult({ id: 'og-title-exists', name: 'OG Title Exists', category: 'og', severity: 'error' }, 'info', 'Not applicable (page has og:title)', { recommendation: 'This rule checks for the presence of og:title meta tag' });
|
|
164
209
|
},
|
|
165
210
|
},
|
|
166
211
|
{
|
|
@@ -170,8 +215,9 @@ export const metaRules = [
|
|
|
170
215
|
severity: 'warning',
|
|
171
216
|
description: 'og:title should be 60-70 characters (max 90)',
|
|
172
217
|
check: (ctx) => {
|
|
173
|
-
if (!ctx.ogTitle)
|
|
174
|
-
return
|
|
218
|
+
if (!ctx.ogTitle) {
|
|
219
|
+
return createResult({ id: 'og-title-length', name: 'OG Title Length', category: 'og', severity: 'warning' }, 'info', 'Not applicable (no og:title)', { recommendation: 'This rule checks og:title length when present' });
|
|
220
|
+
}
|
|
175
221
|
const len = ctx.ogTitle.length;
|
|
176
222
|
const { ideal, max } = SEO_THRESHOLDS.og.title;
|
|
177
223
|
if (len > max) {
|
|
@@ -190,13 +236,14 @@ export const metaRules = [
|
|
|
190
236
|
severity: 'warning',
|
|
191
237
|
description: 'og:title should not contain emojis (some networks remove them)',
|
|
192
238
|
check: (ctx) => {
|
|
193
|
-
if (!ctx.ogTitle)
|
|
194
|
-
return
|
|
239
|
+
if (!ctx.ogTitle) {
|
|
240
|
+
return createResult({ id: 'og-title-no-emoji', name: 'OG Title No Emoji', category: 'og', severity: 'warning' }, 'info', 'Not applicable (no og:title)', { recommendation: 'This rule checks for emojis in og:title when present' });
|
|
241
|
+
}
|
|
195
242
|
const emojiRegex = /[\u{1F300}-\u{1F9FF}]|[\u{2600}-\u{26FF}]|[\u{2700}-\u{27BF}]/u;
|
|
196
243
|
if (emojiRegex.test(ctx.ogTitle)) {
|
|
197
244
|
return createResult({ id: 'og-title-no-emoji', name: 'OG Title Emoji', category: 'og', severity: 'warning' }, 'warn', 'og:title contains emojis (some networks remove them)', { recommendation: 'Remove emojis from og:title for consistent display' });
|
|
198
245
|
}
|
|
199
|
-
return
|
|
246
|
+
return createResult({ id: 'og-title-no-emoji', name: 'OG Title No Emoji', category: 'og', severity: 'warning' }, 'info', 'Not applicable (og:title has no emojis)', { recommendation: 'This rule checks for emoji characters in og:title' });
|
|
200
247
|
},
|
|
201
248
|
},
|
|
202
249
|
{
|
|
@@ -217,7 +264,7 @@ export const metaRules = [
|
|
|
217
264
|
},
|
|
218
265
|
});
|
|
219
266
|
}
|
|
220
|
-
return
|
|
267
|
+
return createResult({ id: 'og-description-exists', name: 'OG Description Exists', category: 'og', severity: 'error' }, 'info', 'Not applicable (page has og:description)', { recommendation: 'This rule checks for the presence of og:description meta tag' });
|
|
221
268
|
},
|
|
222
269
|
},
|
|
223
270
|
{
|
|
@@ -227,8 +274,9 @@ export const metaRules = [
|
|
|
227
274
|
severity: 'warning',
|
|
228
275
|
description: 'og:description should be 110-155 characters (max 200)',
|
|
229
276
|
check: (ctx) => {
|
|
230
|
-
if (!ctx.ogDescription)
|
|
231
|
-
return
|
|
277
|
+
if (!ctx.ogDescription) {
|
|
278
|
+
return createResult({ id: 'og-description-length', name: 'OG Description Length', category: 'og', severity: 'warning' }, 'info', 'Not applicable (no og:description)', { recommendation: 'This rule checks og:description length when present' });
|
|
279
|
+
}
|
|
232
280
|
const len = ctx.ogDescription.length;
|
|
233
281
|
const { ideal, max } = SEO_THRESHOLDS.og.description;
|
|
234
282
|
if (len > max) {
|
|
@@ -259,7 +307,7 @@ export const metaRules = [
|
|
|
259
307
|
},
|
|
260
308
|
});
|
|
261
309
|
}
|
|
262
|
-
return
|
|
310
|
+
return createResult({ id: 'og-image-exists', name: 'OG Image Exists', category: 'og', severity: 'error' }, 'info', 'Not applicable (page has og:image)', { recommendation: 'This rule checks for the presence of og:image meta tag' });
|
|
263
311
|
},
|
|
264
312
|
},
|
|
265
313
|
{
|
|
@@ -269,8 +317,9 @@ export const metaRules = [
|
|
|
269
317
|
severity: 'error',
|
|
270
318
|
description: 'og:image URL must use HTTPS',
|
|
271
319
|
check: (ctx) => {
|
|
272
|
-
if (!ctx.ogImage)
|
|
273
|
-
return
|
|
320
|
+
if (!ctx.ogImage) {
|
|
321
|
+
return createResult({ id: 'og-image-https', name: 'OG Image HTTPS', category: 'og', severity: 'error' }, 'info', 'Not applicable (no og:image)', { recommendation: 'This rule checks og:image URL protocol when present' });
|
|
322
|
+
}
|
|
274
323
|
if (ctx.ogImage.startsWith('http://')) {
|
|
275
324
|
return createResult({ id: 'og-image-https', name: 'OG Image HTTPS', category: 'og', severity: 'error' }, 'fail', 'og:image uses HTTP instead of HTTPS', { value: ctx.ogImage, recommendation: 'Always use HTTPS for og:image URLs' });
|
|
276
325
|
}
|
|
@@ -310,13 +359,14 @@ export const metaRules = [
|
|
|
310
359
|
severity: 'warning',
|
|
311
360
|
description: 'og:image URL should be under 2000 characters',
|
|
312
361
|
check: (ctx) => {
|
|
313
|
-
if (!ctx.ogImage)
|
|
314
|
-
return
|
|
362
|
+
if (!ctx.ogImage) {
|
|
363
|
+
return createResult({ id: 'og-image-url-length', name: 'OG Image URL Length', category: 'og', severity: 'warning' }, 'info', 'Not applicable (no og:image)', { recommendation: 'This rule checks og:image URL length when present' });
|
|
364
|
+
}
|
|
315
365
|
const maxLen = SEO_THRESHOLDS.og.meta.maxUrlLength;
|
|
316
366
|
if (ctx.ogImage.length > maxLen) {
|
|
317
367
|
return createResult({ id: 'og-image-url-length', name: 'OG Image URL Length', category: 'og', severity: 'warning' }, 'warn', `og:image URL too long (${ctx.ogImage.length} chars, max: ${maxLen})`, { value: ctx.ogImage.length, recommendation: 'Shorten the image URL path' });
|
|
318
368
|
}
|
|
319
|
-
return
|
|
369
|
+
return createResult({ id: 'og-image-url-length', name: 'OG Image URL Length', category: 'og', severity: 'warning' }, 'info', 'Not applicable (og:image URL length is acceptable)', { recommendation: 'This rule checks if og:image URL exceeds length limits' });
|
|
320
370
|
},
|
|
321
371
|
},
|
|
322
372
|
{
|
|
@@ -326,8 +376,9 @@ export const metaRules = [
|
|
|
326
376
|
severity: 'warning',
|
|
327
377
|
description: 'og:image URL should not have expiring tokens or excessive query params',
|
|
328
378
|
check: (ctx) => {
|
|
329
|
-
if (!ctx.ogImage)
|
|
330
|
-
return
|
|
379
|
+
if (!ctx.ogImage) {
|
|
380
|
+
return createResult({ id: 'og-image-url-quality', name: 'OG Image URL Quality', category: 'og', severity: 'warning' }, 'info', 'Not applicable (no og:image)', { recommendation: 'This rule checks og:image URL for expiring tokens when present' });
|
|
381
|
+
}
|
|
331
382
|
try {
|
|
332
383
|
const url = new URL(ctx.ogImage);
|
|
333
384
|
const params = url.searchParams;
|
|
@@ -342,7 +393,7 @@ export const metaRules = [
|
|
|
342
393
|
}
|
|
343
394
|
catch {
|
|
344
395
|
}
|
|
345
|
-
return
|
|
396
|
+
return createResult({ id: 'og-image-url-quality', name: 'OG Image URL Quality', category: 'og', severity: 'warning' }, 'info', 'Not applicable (og:image URL has good quality)', { recommendation: 'This rule checks for expiring tokens and excessive query parameters' });
|
|
346
397
|
},
|
|
347
398
|
},
|
|
348
399
|
{
|
|
@@ -352,15 +403,16 @@ export const metaRules = [
|
|
|
352
403
|
severity: 'info',
|
|
353
404
|
description: 'og:description should not have excessive emojis',
|
|
354
405
|
check: (ctx) => {
|
|
355
|
-
if (!ctx.ogDescription)
|
|
356
|
-
return
|
|
406
|
+
if (!ctx.ogDescription) {
|
|
407
|
+
return createResult({ id: 'og-description-emojis', name: 'OG Description Emojis', category: 'og', severity: 'info' }, 'info', 'Not applicable (no og:description)', { recommendation: 'This rule checks emoji usage in og:description when present' });
|
|
408
|
+
}
|
|
357
409
|
const emojiRegex = /[\u{1F300}-\u{1F9FF}]|[\u{2600}-\u{26FF}]|[\u{2700}-\u{27BF}]|[\u{1F600}-\u{1F64F}]|[\u{1F680}-\u{1F6FF}]/gu;
|
|
358
410
|
const emojis = ctx.ogDescription.match(emojiRegex) || [];
|
|
359
411
|
const maxEmojis = SEO_THRESHOLDS.og.meta.maxDescriptionEmojis;
|
|
360
412
|
if (emojis.length > maxEmojis) {
|
|
361
413
|
return createResult({ id: 'og-description-emojis', name: 'OG Description Emojis', category: 'og', severity: 'info' }, 'info', `og:description has ${emojis.length} emojis (recommended max: ${maxEmojis})`, { value: emojis.length, recommendation: 'Reduce emojis in og:description for better compatibility' });
|
|
362
414
|
}
|
|
363
|
-
return
|
|
415
|
+
return createResult({ id: 'og-description-emojis', name: 'OG Description Emojis', category: 'og', severity: 'info' }, 'info', 'Not applicable (og:description has acceptable emoji count)', { recommendation: 'This rule checks for excessive emoji usage in og:description' });
|
|
364
416
|
},
|
|
365
417
|
},
|
|
366
418
|
{
|
|
@@ -370,18 +422,20 @@ export const metaRules = [
|
|
|
370
422
|
severity: 'warning',
|
|
371
423
|
description: 'og:title should not be mostly uppercase (Meta may flag as low quality)',
|
|
372
424
|
check: (ctx) => {
|
|
373
|
-
if (!ctx.ogTitle)
|
|
374
|
-
return
|
|
425
|
+
if (!ctx.ogTitle) {
|
|
426
|
+
return createResult({ id: 'og-title-caps', name: 'OG Title Caps', category: 'og', severity: 'warning' }, 'info', 'Not applicable (no og:title)', { recommendation: 'This rule checks capitalization in og:title when present' });
|
|
427
|
+
}
|
|
375
428
|
const letters = ctx.ogTitle.replace(/[^a-zA-Z]/g, '');
|
|
376
|
-
if (letters.length < 5)
|
|
377
|
-
return
|
|
429
|
+
if (letters.length < 5) {
|
|
430
|
+
return createResult({ id: 'og-title-caps', name: 'OG Title Caps', category: 'og', severity: 'warning' }, 'info', 'Not applicable (og:title has too few letters to evaluate)', { recommendation: 'This rule checks for excessive uppercase in og:title' });
|
|
431
|
+
}
|
|
378
432
|
const uppercase = letters.replace(/[^A-Z]/g, '').length;
|
|
379
433
|
const percentage = Math.round((uppercase / letters.length) * 100);
|
|
380
434
|
const maxCaps = SEO_THRESHOLDS.og.meta.maxCapsPercentage;
|
|
381
435
|
if (percentage > maxCaps) {
|
|
382
436
|
return createResult({ id: 'og-title-caps', name: 'OG Title Caps', category: 'og', severity: 'warning' }, 'warn', `og:title has ${percentage}% uppercase (Meta may flag as low quality)`, { value: percentage, recommendation: 'Use normal capitalization in og:title' });
|
|
383
437
|
}
|
|
384
|
-
return
|
|
438
|
+
return createResult({ id: 'og-title-caps', name: 'OG Title Caps', category: 'og', severity: 'warning' }, 'info', 'Not applicable (og:title uses proper capitalization)', { recommendation: 'This rule checks for excessive uppercase in og:title' });
|
|
385
439
|
},
|
|
386
440
|
},
|
|
387
441
|
{
|
|
@@ -417,7 +471,7 @@ export const metaRules = [
|
|
|
417
471
|
if (ctx.ogTitle && !ctx.title) {
|
|
418
472
|
return createResult({ id: 'og-fallback-meta-title', name: 'Fallback Meta Title', category: 'og', severity: 'info' }, 'info', 'No <title> tag found (og:title exists)', { recommendation: 'Add <title> tag as fallback for universal compatibility' });
|
|
419
473
|
}
|
|
420
|
-
return
|
|
474
|
+
return createResult({ id: 'og-fallback-meta-title', name: 'Fallback Meta Title', category: 'og', severity: 'info' }, 'info', 'Not applicable (page has title tag or no og:title)', { recommendation: 'This rule checks for title tag when og:title exists' });
|
|
421
475
|
},
|
|
422
476
|
},
|
|
423
477
|
{
|
|
@@ -427,8 +481,9 @@ export const metaRules = [
|
|
|
427
481
|
severity: 'warning',
|
|
428
482
|
description: 'og:image should not have redirect chains (Meta blocks >2 redirects)',
|
|
429
483
|
check: (ctx) => {
|
|
430
|
-
if (!ctx.ogImage)
|
|
431
|
-
return
|
|
484
|
+
if (!ctx.ogImage) {
|
|
485
|
+
return createResult({ id: 'og-image-redirects', name: 'OG Image Redirects', category: 'og', severity: 'warning' }, 'info', 'Not applicable (no og:image)', { recommendation: 'This rule checks og:image URL for redirect patterns when present' });
|
|
486
|
+
}
|
|
432
487
|
try {
|
|
433
488
|
const url = new URL(ctx.ogImage);
|
|
434
489
|
const redirectPatterns = ['redirect', 'proxy', 'forward', 'goto', 'redir', 'bounce'];
|
|
@@ -439,7 +494,7 @@ export const metaRules = [
|
|
|
439
494
|
}
|
|
440
495
|
catch {
|
|
441
496
|
}
|
|
442
|
-
return
|
|
497
|
+
return createResult({ id: 'og-image-redirects', name: 'OG Image Redirects', category: 'og', severity: 'warning' }, 'info', 'Not applicable (og:image URL has no redirect patterns)', { recommendation: 'This rule checks for redirect patterns in og:image URL' });
|
|
443
498
|
},
|
|
444
499
|
},
|
|
445
500
|
{
|
|
@@ -449,8 +504,9 @@ export const metaRules = [
|
|
|
449
504
|
severity: 'warning',
|
|
450
505
|
description: 'og:image must be publicly accessible (no auth, no private URLs)',
|
|
451
506
|
check: (ctx) => {
|
|
452
|
-
if (!ctx.ogImage)
|
|
453
|
-
return
|
|
507
|
+
if (!ctx.ogImage) {
|
|
508
|
+
return createResult({ id: 'og-image-public', name: 'OG Image Public', category: 'og', severity: 'warning' }, 'info', 'Not applicable (no og:image)', { recommendation: 'This rule checks og:image URL accessibility when present' });
|
|
509
|
+
}
|
|
454
510
|
try {
|
|
455
511
|
const url = new URL(ctx.ogImage);
|
|
456
512
|
if (url.username || url.password) {
|
|
@@ -464,7 +520,7 @@ export const metaRules = [
|
|
|
464
520
|
}
|
|
465
521
|
catch {
|
|
466
522
|
}
|
|
467
|
-
return
|
|
523
|
+
return createResult({ id: 'og-image-public', name: 'OG Image Public', category: 'og', severity: 'warning' }, 'info', 'Not applicable (og:image URL is publicly accessible)', { recommendation: 'This rule checks for authentication or private IPs in og:image URL' });
|
|
468
524
|
},
|
|
469
525
|
},
|
|
470
526
|
{
|
|
@@ -492,14 +548,15 @@ export const metaRules = [
|
|
|
492
548
|
description: 'twitter:title should be 55-70 characters',
|
|
493
549
|
check: (ctx) => {
|
|
494
550
|
const title = ctx.twitterTitle || ctx.ogTitle;
|
|
495
|
-
if (!title)
|
|
496
|
-
return
|
|
551
|
+
if (!title) {
|
|
552
|
+
return createResult({ id: 'twitter-title-length', name: 'Twitter Title Length', category: 'twitter', severity: 'warning' }, 'info', 'Not applicable (no twitter:title or og:title)', { recommendation: 'This rule checks Twitter title length when present' });
|
|
553
|
+
}
|
|
497
554
|
const len = title.length;
|
|
498
555
|
const { ideal, max } = SEO_THRESHOLDS.twitter.title;
|
|
499
556
|
if (len > max) {
|
|
500
557
|
return createResult({ id: 'twitter-title-length', name: 'Twitter Title Length', category: 'twitter', severity: 'warning' }, 'warn', `twitter:title too long (${len} chars, max: ${max})`, { value: len, recommendation: `Shorten to ${ideal.max} characters` });
|
|
501
558
|
}
|
|
502
|
-
return
|
|
559
|
+
return createResult({ id: 'twitter-title-length', name: 'Twitter Title Length', category: 'twitter', severity: 'warning' }, 'info', 'Not applicable (Twitter title length is acceptable)', { recommendation: 'This rule checks if Twitter title exceeds length limits' });
|
|
503
560
|
},
|
|
504
561
|
},
|
|
505
562
|
{
|
|
@@ -510,14 +567,15 @@ export const metaRules = [
|
|
|
510
567
|
description: 'twitter:description should be 125-200 characters',
|
|
511
568
|
check: (ctx) => {
|
|
512
569
|
const description = ctx.twitterDescription || ctx.ogDescription;
|
|
513
|
-
if (!description)
|
|
514
|
-
return
|
|
570
|
+
if (!description) {
|
|
571
|
+
return createResult({ id: 'twitter-description-length', name: 'Twitter Description Length', category: 'twitter', severity: 'warning' }, 'info', 'Not applicable (no twitter:description or og:description)', { recommendation: 'This rule checks Twitter description length when present' });
|
|
572
|
+
}
|
|
515
573
|
const len = description.length;
|
|
516
574
|
const { max } = SEO_THRESHOLDS.twitter.description;
|
|
517
575
|
if (len > max) {
|
|
518
576
|
return createResult({ id: 'twitter-description-length', name: 'Twitter Description Length', category: 'twitter', severity: 'warning' }, 'warn', `twitter:description too long (${len} chars, max: ${max})`, { value: len, recommendation: `Shorten to ${max} characters` });
|
|
519
577
|
}
|
|
520
|
-
return
|
|
578
|
+
return createResult({ id: 'twitter-description-length', name: 'Twitter Description Length', category: 'twitter', severity: 'warning' }, 'info', 'Not applicable (Twitter description length is acceptable)', { recommendation: 'This rule checks if Twitter description exceeds length limits' });
|
|
521
579
|
},
|
|
522
580
|
},
|
|
523
581
|
{
|
|
@@ -527,8 +585,9 @@ export const metaRules = [
|
|
|
527
585
|
severity: 'warning',
|
|
528
586
|
description: 'Title should have at least 10 characters for SEO value',
|
|
529
587
|
check: (ctx) => {
|
|
530
|
-
if (!ctx.title)
|
|
531
|
-
return
|
|
588
|
+
if (!ctx.title) {
|
|
589
|
+
return createResult({ id: 'title-too-short', name: 'Title Too Short', category: 'title', severity: 'warning' }, 'info', 'Not applicable (no title tag)', { recommendation: 'This rule checks if title is too short when present' });
|
|
590
|
+
}
|
|
532
591
|
const len = ctx.titleLength ?? ctx.title.length;
|
|
533
592
|
if (len <= 10) {
|
|
534
593
|
return createResult({ id: 'title-too-short', name: 'Title Too Short', category: 'title', severity: 'warning' }, 'warn', `Title is very short (${len} chars)`, {
|
|
@@ -541,7 +600,47 @@ export const metaRules = [
|
|
|
541
600
|
}
|
|
542
601
|
});
|
|
543
602
|
}
|
|
544
|
-
return
|
|
603
|
+
return createResult({ id: 'title-too-short', name: 'Title Too Short', category: 'title', severity: 'warning' }, 'info', 'Not applicable (title has sufficient length)', { recommendation: 'This rule checks if title is shorter than 10 characters' });
|
|
604
|
+
},
|
|
605
|
+
},
|
|
606
|
+
{
|
|
607
|
+
id: 'keywords-in-title',
|
|
608
|
+
name: 'Keywords in Title',
|
|
609
|
+
category: 'title',
|
|
610
|
+
severity: 'warning',
|
|
611
|
+
description: 'Title should contain main keywords found in content',
|
|
612
|
+
check: (ctx) => {
|
|
613
|
+
if (ctx.keywordsInTitle === false && ctx.topKeywords && ctx.topKeywords.length > 0) {
|
|
614
|
+
return createResult({ id: 'keywords-in-title', name: 'Keywords in Title', category: 'title', severity: 'warning' }, 'warn', 'Title does not appear to contain top keywords', {
|
|
615
|
+
recommendation: 'Include your main target keywords in the page title.',
|
|
616
|
+
evidence: {
|
|
617
|
+
found: `Title: "${ctx.title}"`,
|
|
618
|
+
expected: `Should contain one of: ${ctx.topKeywords.join(', ')}`,
|
|
619
|
+
impact: 'Keywords in title are a strong ranking signal.'
|
|
620
|
+
}
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
return createResult({ id: 'keywords-in-title', name: 'Keywords in Title', category: 'title', severity: 'warning' }, 'info', 'Not applicable (title contains keywords or no keyword data)', { recommendation: 'This rule checks if title contains main keywords from content' });
|
|
624
|
+
},
|
|
625
|
+
},
|
|
626
|
+
{
|
|
627
|
+
id: 'keywords-in-description',
|
|
628
|
+
name: 'Keywords in Description',
|
|
629
|
+
category: 'meta',
|
|
630
|
+
severity: 'info',
|
|
631
|
+
description: 'Meta description should contain main keywords',
|
|
632
|
+
check: (ctx) => {
|
|
633
|
+
if (ctx.keywordsInDescription === false && ctx.topKeywords && ctx.topKeywords.length > 0) {
|
|
634
|
+
return createResult({ id: 'keywords-in-description', name: 'Keywords in Description', category: 'meta', severity: 'info' }, 'info', 'Meta description does not appear to contain top keywords', {
|
|
635
|
+
recommendation: 'Include main keywords in the description to embolden them in search results.',
|
|
636
|
+
evidence: {
|
|
637
|
+
found: 'Description does not match top keywords',
|
|
638
|
+
expected: `Should contain one of: ${ctx.topKeywords.join(', ')}`,
|
|
639
|
+
impact: 'Keywords in description are bolded in SERPs, improving CTR.'
|
|
640
|
+
}
|
|
641
|
+
});
|
|
642
|
+
}
|
|
643
|
+
return createResult({ id: 'keywords-in-description', name: 'Keywords in Description', category: 'meta', severity: 'info' }, 'info', 'Not applicable (description contains keywords or no keyword data)', { recommendation: 'This rule checks if meta description contains main keywords' });
|
|
545
644
|
},
|
|
546
645
|
},
|
|
547
646
|
];
|
package/dist/seo/rules/mobile.js
CHANGED
|
@@ -26,8 +26,9 @@ export const mobileRules = [
|
|
|
26
26
|
severity: 'warning',
|
|
27
27
|
description: 'Viewport should allow user scaling for accessibility',
|
|
28
28
|
check: (ctx) => {
|
|
29
|
-
if (!ctx.viewportContent)
|
|
30
|
-
return
|
|
29
|
+
if (!ctx.viewportContent) {
|
|
30
|
+
return createResult({ id: 'viewport-scalable', name: 'Viewport Scalable', category: 'mobile', severity: 'warning' }, 'info', 'Not applicable (no viewport meta tag)', { recommendation: 'First add a viewport meta tag, then ensure it allows user scaling' });
|
|
31
|
+
}
|
|
31
32
|
const content = ctx.viewportContent.toLowerCase();
|
|
32
33
|
const hasUserScalableNo = /user-scalable\s*=\s*no/.test(content);
|
|
33
34
|
const hasMaximumScale1 = /maximum-scale\s*=\s*1(\.0)?/.test(content);
|
|
@@ -48,4 +49,113 @@ export const mobileRules = [
|
|
|
48
49
|
return createResult({ id: 'viewport-scalable', name: 'Viewport Scalable', category: 'mobile', severity: 'warning' }, 'pass', 'Viewport allows user scaling');
|
|
49
50
|
},
|
|
50
51
|
},
|
|
52
|
+
{
|
|
53
|
+
id: 'viewport-device-width',
|
|
54
|
+
name: 'Viewport Device Width',
|
|
55
|
+
category: 'mobile',
|
|
56
|
+
severity: 'warning',
|
|
57
|
+
description: 'Viewport should use width=device-width for responsive design',
|
|
58
|
+
check: (ctx) => {
|
|
59
|
+
if (!ctx.viewportContent) {
|
|
60
|
+
return createResult({ id: 'viewport-device-width', name: 'Viewport Device Width', category: 'mobile', severity: 'warning' }, 'info', 'Not applicable (no viewport meta tag)', { recommendation: 'First add a viewport meta tag with width=device-width' });
|
|
61
|
+
}
|
|
62
|
+
const content = ctx.viewportContent.toLowerCase();
|
|
63
|
+
const hasDeviceWidth = /width\s*=\s*device-width/.test(content);
|
|
64
|
+
const hasFixedWidth = /width\s*=\s*(\d+)/.test(content);
|
|
65
|
+
if (hasFixedWidth && !hasDeviceWidth) {
|
|
66
|
+
const match = content.match(/width\s*=\s*(\d+)/);
|
|
67
|
+
const fixedWidth = match ? match[1] : 'unknown';
|
|
68
|
+
return createResult({ id: 'viewport-device-width', name: 'Viewport Device Width', category: 'mobile', severity: 'warning' }, 'warn', `Viewport uses fixed width (${fixedWidth}px) instead of device-width`, {
|
|
69
|
+
recommendation: 'Use width=device-width to make your site responsive across all devices',
|
|
70
|
+
evidence: {
|
|
71
|
+
found: ctx.viewportContent,
|
|
72
|
+
expected: 'width=device-width',
|
|
73
|
+
impact: 'Fixed width viewports force users to scroll horizontally on different screen sizes',
|
|
74
|
+
example: '<meta name="viewport" content="width=device-width, initial-scale=1">',
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
if (!hasDeviceWidth) {
|
|
79
|
+
return createResult({ id: 'viewport-device-width', name: 'Viewport Device Width', category: 'mobile', severity: 'warning' }, 'info', 'Viewport missing width=device-width', {
|
|
80
|
+
recommendation: 'Add width=device-width to ensure proper responsive behavior',
|
|
81
|
+
evidence: {
|
|
82
|
+
found: ctx.viewportContent,
|
|
83
|
+
expected: 'width=device-width',
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
return createResult({ id: 'viewport-device-width', name: 'Viewport Device Width', category: 'mobile', severity: 'warning' }, 'pass', 'Viewport uses device-width correctly');
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
id: 'viewport-initial-scale',
|
|
92
|
+
name: 'Viewport Initial Scale',
|
|
93
|
+
category: 'mobile',
|
|
94
|
+
severity: 'info',
|
|
95
|
+
description: 'Viewport should include initial-scale=1 for consistent initial zoom',
|
|
96
|
+
check: (ctx) => {
|
|
97
|
+
if (!ctx.viewportContent) {
|
|
98
|
+
return createResult({ id: 'viewport-initial-scale', name: 'Viewport Initial Scale', category: 'mobile', severity: 'info' }, 'info', 'Not applicable (no viewport meta tag)', { recommendation: 'First add a viewport meta tag with initial-scale=1' });
|
|
99
|
+
}
|
|
100
|
+
const content = ctx.viewportContent.toLowerCase();
|
|
101
|
+
const hasInitialScale = /initial-scale\s*=\s*1(\.0)?/.test(content);
|
|
102
|
+
if (!hasInitialScale) {
|
|
103
|
+
return createResult({ id: 'viewport-initial-scale', name: 'Viewport Initial Scale', category: 'mobile', severity: 'info' }, 'info', 'Viewport missing initial-scale=1', {
|
|
104
|
+
recommendation: 'Add initial-scale=1 to set consistent initial zoom level',
|
|
105
|
+
evidence: {
|
|
106
|
+
found: ctx.viewportContent,
|
|
107
|
+
expected: 'initial-scale=1',
|
|
108
|
+
example: '<meta name="viewport" content="width=device-width, initial-scale=1">',
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
return createResult({ id: 'viewport-initial-scale', name: 'Viewport Initial Scale', category: 'mobile', severity: 'info' }, 'pass', 'Viewport has initial-scale=1');
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
id: 'viewport-complete',
|
|
117
|
+
name: 'Viewport Complete Configuration',
|
|
118
|
+
category: 'mobile',
|
|
119
|
+
severity: 'info',
|
|
120
|
+
description: 'Viewport should have the complete recommended configuration',
|
|
121
|
+
check: (ctx) => {
|
|
122
|
+
if (!ctx.viewportContent) {
|
|
123
|
+
return createResult({ id: 'viewport-complete', name: 'Viewport Complete', category: 'mobile', severity: 'info' }, 'info', 'Not applicable (no viewport meta tag)', { recommendation: 'Add a complete viewport meta tag: <meta name="viewport" content="width=device-width, initial-scale=1">' });
|
|
124
|
+
}
|
|
125
|
+
const content = ctx.viewportContent.toLowerCase();
|
|
126
|
+
const hasDeviceWidth = /width\s*=\s*device-width/.test(content);
|
|
127
|
+
const hasInitialScale = /initial-scale\s*=\s*1(\.0)?/.test(content);
|
|
128
|
+
const hasUserScalableNo = /user-scalable\s*=\s*no/.test(content);
|
|
129
|
+
const hasFixedWidth = /width\s*=\s*\d+/.test(content) && !hasDeviceWidth;
|
|
130
|
+
const issues = [];
|
|
131
|
+
if (!hasDeviceWidth)
|
|
132
|
+
issues.push('missing width=device-width');
|
|
133
|
+
if (!hasInitialScale)
|
|
134
|
+
issues.push('missing initial-scale=1');
|
|
135
|
+
if (hasUserScalableNo)
|
|
136
|
+
issues.push('has user-scalable=no');
|
|
137
|
+
if (hasFixedWidth)
|
|
138
|
+
issues.push('uses fixed width');
|
|
139
|
+
if (issues.length === 0) {
|
|
140
|
+
return createResult({ id: 'viewport-complete', name: 'Viewport Complete', category: 'mobile', severity: 'info' }, 'pass', 'Viewport has optimal configuration for mobile', { evidence: { found: ctx.viewportContent } });
|
|
141
|
+
}
|
|
142
|
+
if (issues.length >= 2 || hasFixedWidth) {
|
|
143
|
+
return createResult({ id: 'viewport-complete', name: 'Viewport Complete', category: 'mobile', severity: 'info' }, 'warn', `Viewport configuration has issues: ${issues.join(', ')}`, {
|
|
144
|
+
recommendation: 'Use the recommended viewport configuration for best mobile experience',
|
|
145
|
+
evidence: {
|
|
146
|
+
found: ctx.viewportContent,
|
|
147
|
+
expected: '<meta name="viewport" content="width=device-width, initial-scale=1">',
|
|
148
|
+
issue: issues.join('; '),
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
return createResult({ id: 'viewport-complete', name: 'Viewport Complete', category: 'mobile', severity: 'info' }, 'info', `Viewport configuration could be improved: ${issues.join(', ')}`, {
|
|
153
|
+
recommendation: 'Consider using the complete recommended viewport configuration',
|
|
154
|
+
evidence: {
|
|
155
|
+
found: ctx.viewportContent,
|
|
156
|
+
expected: '<meta name="viewport" content="width=device-width, initial-scale=1">',
|
|
157
|
+
},
|
|
158
|
+
});
|
|
159
|
+
},
|
|
160
|
+
},
|
|
51
161
|
];
|