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
package/dist/seo/rules/i18n.js
CHANGED
|
@@ -19,7 +19,9 @@ export const i18nRules = [
|
|
|
19
19
|
},
|
|
20
20
|
});
|
|
21
21
|
}
|
|
22
|
-
return
|
|
22
|
+
return createResult({ id: 'i18n-hreflang-exists', name: 'Hreflang Tags', category: 'technical', severity: 'warning' }, 'info', 'No hreflang tags found', {
|
|
23
|
+
recommendation: 'Consider adding hreflang tags if you have multi-language versions of this page',
|
|
24
|
+
});
|
|
23
25
|
}
|
|
24
26
|
return createResult({ id: 'i18n-hreflang-exists', name: 'Hreflang Tags', category: 'technical', severity: 'warning' }, 'pass', `${ctx.hreflangTags.length} hreflang tag(s) found`, { value: ctx.hreflangTags.length });
|
|
25
27
|
},
|
|
@@ -31,10 +33,14 @@ export const i18nRules = [
|
|
|
31
33
|
severity: 'warning',
|
|
32
34
|
description: 'Hreflang tags should include a self-referencing tag for the current page',
|
|
33
35
|
check: (ctx) => {
|
|
34
|
-
if (!ctx.hreflangTags || ctx.hreflangTags.length === 0)
|
|
35
|
-
return
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
if (!ctx.hreflangTags || ctx.hreflangTags.length === 0) {
|
|
37
|
+
return createResult({ id: 'i18n-hreflang-self', name: 'Hreflang Self-Reference', category: 'technical', severity: 'warning' }, 'info', 'Not applicable (no hreflang tags)', {
|
|
38
|
+
recommendation: 'Add hreflang tags first, then ensure self-reference is included',
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
if (!ctx.url) {
|
|
42
|
+
return createResult({ id: 'i18n-hreflang-self', name: 'Hreflang Self-Reference', category: 'technical', severity: 'warning' }, 'info', 'Cannot verify (URL not available)');
|
|
43
|
+
}
|
|
38
44
|
const currentUrl = ctx.url.toLowerCase().replace(/\/$/, '');
|
|
39
45
|
const hasSelfRef = ctx.hreflangTags.some((tag) => {
|
|
40
46
|
const href = tag.href?.toLowerCase().replace(/\/$/, '');
|
|
@@ -47,10 +53,11 @@ export const i18nRules = [
|
|
|
47
53
|
found: `Current URL: ${ctx.url}`,
|
|
48
54
|
expected: `<link rel="alternate" hreflang="${ctx.langValue || 'en'}" href="${ctx.url}">`,
|
|
49
55
|
impact: 'Google recommends including a self-referencing hreflang tag for clarity',
|
|
56
|
+
example: `<link rel="alternate" hreflang="en" href="${ctx.url}">\n<link rel="alternate" hreflang="es" href="${ctx.url.replace(/\/en\//, '/es/')}">`,
|
|
50
57
|
},
|
|
51
58
|
});
|
|
52
59
|
}
|
|
53
|
-
return
|
|
60
|
+
return createResult({ id: 'i18n-hreflang-self', name: 'Hreflang Self-Reference', category: 'technical', severity: 'warning' }, 'pass', 'Self-referencing hreflang tag present');
|
|
54
61
|
},
|
|
55
62
|
},
|
|
56
63
|
{
|
|
@@ -60,8 +67,11 @@ export const i18nRules = [
|
|
|
60
67
|
severity: 'info',
|
|
61
68
|
description: 'Include x-default hreflang for users outside defined regions',
|
|
62
69
|
check: (ctx) => {
|
|
63
|
-
if (!ctx.hreflangTags || ctx.hreflangTags.length < 2)
|
|
64
|
-
return
|
|
70
|
+
if (!ctx.hreflangTags || ctx.hreflangTags.length < 2) {
|
|
71
|
+
return createResult({ id: 'i18n-hreflang-x-default', name: 'Hreflang X-Default', category: 'technical', severity: 'info' }, 'info', 'Not applicable (need 2+ hreflang tags for x-default)', {
|
|
72
|
+
recommendation: 'Add multiple hreflang tags to support international visitors',
|
|
73
|
+
});
|
|
74
|
+
}
|
|
65
75
|
const hasXDefault = ctx.hreflangTags.some((tag) => tag.lang === 'x-default');
|
|
66
76
|
if (!hasXDefault) {
|
|
67
77
|
return createResult({ id: 'i18n-hreflang-x-default', name: 'Hreflang X-Default', category: 'technical', severity: 'info' }, 'info', 'No x-default hreflang tag found', {
|
|
@@ -82,8 +92,9 @@ export const i18nRules = [
|
|
|
82
92
|
severity: 'warning',
|
|
83
93
|
description: 'Hreflang language codes must be valid ISO 639-1 codes',
|
|
84
94
|
check: (ctx) => {
|
|
85
|
-
if (!ctx.hreflangTags || ctx.hreflangTags.length === 0)
|
|
86
|
-
return
|
|
95
|
+
if (!ctx.hreflangTags || ctx.hreflangTags.length === 0) {
|
|
96
|
+
return createResult({ id: 'i18n-hreflang-valid-codes', name: 'Hreflang Valid Codes', category: 'technical', severity: 'warning' }, 'info', 'Not applicable (no hreflang tags)');
|
|
97
|
+
}
|
|
87
98
|
const validLanguageCodes = new Set([
|
|
88
99
|
'aa', 'ab', 'af', 'ak', 'sq', 'am', 'ar', 'an', 'hy', 'as', 'av', 'ae', 'ay', 'az',
|
|
89
100
|
'ba', 'bm', 'eu', 'be', 'bn', 'bh', 'bi', 'bo', 'bs', 'br', 'bg', 'my', 'ca', 'cs',
|
|
@@ -113,11 +124,13 @@ export const i18nRules = [
|
|
|
113
124
|
evidence: {
|
|
114
125
|
found: invalidTags,
|
|
115
126
|
expected: 'Valid ISO 639-1 codes like: en, es, fr, de, pt-BR, zh-CN',
|
|
127
|
+
impact: 'Invalid language codes will be ignored by search engines, breaking international targeting',
|
|
128
|
+
example: '<link rel="alternate" hreflang="en-US" href="https://example.com/en/">\n<link rel="alternate" hreflang="es-ES" href="https://example.com/es/">',
|
|
116
129
|
learnMore: 'https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes',
|
|
117
130
|
},
|
|
118
131
|
});
|
|
119
132
|
}
|
|
120
|
-
return
|
|
133
|
+
return createResult({ id: 'i18n-hreflang-valid-codes', name: 'Hreflang Valid Codes', category: 'technical', severity: 'warning' }, 'pass', 'All hreflang codes are valid ISO 639-1');
|
|
121
134
|
},
|
|
122
135
|
},
|
|
123
136
|
{
|
|
@@ -127,8 +140,11 @@ export const i18nRules = [
|
|
|
127
140
|
severity: 'warning',
|
|
128
141
|
description: 'All hreflang URLs should return links back to this page (bidirectional)',
|
|
129
142
|
check: (ctx) => {
|
|
130
|
-
if (!ctx.hreflangTags || ctx.hreflangTags.length < 2)
|
|
131
|
-
return
|
|
143
|
+
if (!ctx.hreflangTags || ctx.hreflangTags.length < 2) {
|
|
144
|
+
return createResult({ id: 'i18n-hreflang-return-links', name: 'Hreflang Return Links', category: 'technical', severity: 'warning' }, 'info', 'Not applicable (need 2+ hreflang tags)', {
|
|
145
|
+
recommendation: 'Add hreflang tags to enable return link verification',
|
|
146
|
+
});
|
|
147
|
+
}
|
|
132
148
|
return createResult({ id: 'i18n-hreflang-return-links', name: 'Hreflang Return Links', category: 'technical', severity: 'warning' }, 'info', 'Hreflang return links cannot be verified from HTML alone', {
|
|
133
149
|
recommendation: 'Ensure all alternate pages link back to this page with matching hreflang tags',
|
|
134
150
|
evidence: {
|
|
@@ -145,19 +161,17 @@ export const i18nRules = [
|
|
|
145
161
|
severity: 'info',
|
|
146
162
|
description: 'Content-Language header can indicate the language of the document',
|
|
147
163
|
check: (ctx) => {
|
|
148
|
-
if (!ctx.responseHeaders)
|
|
149
|
-
return
|
|
164
|
+
if (!ctx.responseHeaders) {
|
|
165
|
+
return createResult({ id: 'i18n-content-language', name: 'Content-Language Header', category: 'technical', severity: 'info' }, 'info', 'Cannot verify (response headers not available)');
|
|
166
|
+
}
|
|
150
167
|
const contentLang = ctx.responseHeaders['content-language'] || ctx.responseHeaders['Content-Language'];
|
|
151
168
|
if (!contentLang) {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
});
|
|
159
|
-
}
|
|
160
|
-
return null;
|
|
169
|
+
return createResult({ id: 'i18n-content-language', name: 'Content-Language Header', category: 'technical', severity: 'info' }, 'info', 'Content-Language header not set', {
|
|
170
|
+
recommendation: `Consider adding Content-Language: ${ctx.langValue || 'en'} header`,
|
|
171
|
+
evidence: {
|
|
172
|
+
impact: 'While not critical for SEO, it helps with content negotiation',
|
|
173
|
+
},
|
|
174
|
+
});
|
|
161
175
|
}
|
|
162
176
|
if (ctx.langValue) {
|
|
163
177
|
const headerLang = Array.isArray(contentLang) ? contentLang[0] : contentLang;
|
|
@@ -166,6 +180,11 @@ export const i18nRules = [
|
|
|
166
180
|
if (headerLangPrimary !== htmlLangPrimary) {
|
|
167
181
|
return createResult({ id: 'i18n-content-language', name: 'Content-Language Header', category: 'technical', severity: 'info' }, 'warn', `Content-Language (${headerLang}) doesn't match html lang (${ctx.langValue})`, {
|
|
168
182
|
recommendation: 'Ensure Content-Language header matches the html lang attribute',
|
|
183
|
+
evidence: {
|
|
184
|
+
found: `Content-Language: ${headerLang}, html lang="${ctx.langValue}"`,
|
|
185
|
+
expected: 'Both should declare the same language',
|
|
186
|
+
impact: 'Mismatched language declarations can confuse browsers and search engines',
|
|
187
|
+
},
|
|
169
188
|
});
|
|
170
189
|
}
|
|
171
190
|
}
|
|
@@ -179,8 +198,18 @@ export const i18nRules = [
|
|
|
179
198
|
severity: 'warning',
|
|
180
199
|
description: 'HTML lang attribute should match the og:locale if present',
|
|
181
200
|
check: (ctx) => {
|
|
182
|
-
if (!ctx.hasLang
|
|
183
|
-
return
|
|
201
|
+
if (!ctx.hasLang && !ctx.ogLocale) {
|
|
202
|
+
return createResult({ id: 'i18n-lang-consistency', name: 'Language Consistency', category: 'technical', severity: 'warning' }, 'info', 'No lang attribute or og:locale to compare', {
|
|
203
|
+
recommendation: 'Add html lang attribute and og:locale for language consistency',
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
if (!ctx.hasLang || !ctx.ogLocale) {
|
|
207
|
+
return createResult({ id: 'i18n-lang-consistency', name: 'Language Consistency', category: 'technical', severity: 'warning' }, 'info', ctx.hasLang ? 'No og:locale to compare with html lang' : 'No html lang to compare with og:locale', {
|
|
208
|
+
recommendation: ctx.hasLang
|
|
209
|
+
? 'Add og:locale meta tag for social platforms'
|
|
210
|
+
: 'Add html lang attribute for language declaration',
|
|
211
|
+
});
|
|
212
|
+
}
|
|
184
213
|
const htmlLang = ctx.langValue?.toLowerCase().split('-')[0];
|
|
185
214
|
const ogLocaleLang = ctx.ogLocale.toLowerCase().split('_')[0];
|
|
186
215
|
if (htmlLang !== ogLocaleLang) {
|
|
@@ -188,11 +217,13 @@ export const i18nRules = [
|
|
|
188
217
|
recommendation: 'Ensure html lang and og:locale represent the same language',
|
|
189
218
|
evidence: {
|
|
190
219
|
found: [`html lang="${ctx.langValue}"`, `og:locale="${ctx.ogLocale}"`],
|
|
220
|
+
expected: 'Both attributes should use the same language code',
|
|
191
221
|
impact: 'Inconsistent language signals can confuse search engines and social platforms',
|
|
222
|
+
example: '<html lang="en">\n<meta property="og:locale" content="en_US">',
|
|
192
223
|
},
|
|
193
224
|
});
|
|
194
225
|
}
|
|
195
|
-
return
|
|
226
|
+
return createResult({ id: 'i18n-lang-consistency', name: 'Language Consistency', category: 'technical', severity: 'warning' }, 'pass', `Language consistent: html lang="${ctx.langValue}", og:locale="${ctx.ogLocale}"`);
|
|
196
227
|
},
|
|
197
228
|
},
|
|
198
229
|
{
|
|
@@ -202,8 +233,11 @@ export const i18nRules = [
|
|
|
202
233
|
severity: 'info',
|
|
203
234
|
description: 'Consider using region-specific language codes for better targeting',
|
|
204
235
|
check: (ctx) => {
|
|
205
|
-
if (!ctx.hasLang || !ctx.langValue)
|
|
206
|
-
return
|
|
236
|
+
if (!ctx.hasLang || !ctx.langValue) {
|
|
237
|
+
return createResult({ id: 'i18n-lang-region', name: 'Language Region Specificity', category: 'technical', severity: 'info' }, 'info', 'No lang attribute to analyze', {
|
|
238
|
+
recommendation: 'Add html lang attribute first',
|
|
239
|
+
});
|
|
240
|
+
}
|
|
207
241
|
const multiRegionalLangs = ['en', 'es', 'pt', 'zh', 'fr', 'de', 'ar'];
|
|
208
242
|
const langPrimary = ctx.langValue.toLowerCase().split('-')[0];
|
|
209
243
|
if (multiRegionalLangs.includes(langPrimary) && !ctx.langValue.includes('-')) {
|
|
@@ -216,7 +250,7 @@ export const i18nRules = [
|
|
|
216
250
|
},
|
|
217
251
|
});
|
|
218
252
|
}
|
|
219
|
-
return
|
|
253
|
+
return createResult({ id: 'i18n-lang-region', name: 'Language Region Specificity', category: 'technical', severity: 'info' }, 'pass', `Lang attribute: ${ctx.langValue}`);
|
|
220
254
|
},
|
|
221
255
|
},
|
|
222
256
|
{
|
|
@@ -226,8 +260,12 @@ export const i18nRules = [
|
|
|
226
260
|
severity: 'warning',
|
|
227
261
|
description: 'Hreflang language should match page content language',
|
|
228
262
|
check: (ctx) => {
|
|
229
|
-
if (!ctx.hreflangTags ||
|
|
230
|
-
return
|
|
263
|
+
if (!ctx.hreflangTags || ctx.hreflangTags.length === 0) {
|
|
264
|
+
return createResult({ id: 'hreflang-language-mismatch', name: 'Hreflang Language Mismatch', category: 'technical', severity: 'warning' }, 'info', 'Not applicable (no hreflang tags)');
|
|
265
|
+
}
|
|
266
|
+
if (!ctx.detectedLanguage) {
|
|
267
|
+
return createResult({ id: 'hreflang-language-mismatch', name: 'Hreflang Language Mismatch', category: 'technical', severity: 'warning' }, 'info', 'Cannot verify (language detection not available)');
|
|
268
|
+
}
|
|
231
269
|
const selfHreflang = ctx.hreflangTags.find(tag => tag.href === ctx.url || tag.href === ctx.canonicalUrl);
|
|
232
270
|
if (selfHreflang && ctx.detectedLanguage) {
|
|
233
271
|
const hreflangLang = selfHreflang.lang.split('-')[0].toLowerCase();
|
|
@@ -236,14 +274,15 @@ export const i18nRules = [
|
|
|
236
274
|
return createResult({ id: 'hreflang-language-mismatch', name: 'Hreflang Language Mismatch', category: 'technical', severity: 'warning' }, 'warn', `Hreflang declares "${selfHreflang.lang}" but content appears to be "${ctx.detectedLanguage}"`, {
|
|
237
275
|
recommendation: 'Verify the hreflang attribute matches the actual page language',
|
|
238
276
|
evidence: {
|
|
239
|
-
found: `hreflang="${selfHreflang.lang}"`,
|
|
240
|
-
expected: `
|
|
241
|
-
impact: 'Language mismatch may confuse search engines and affect international SEO'
|
|
277
|
+
found: `hreflang="${selfHreflang.lang}", detected content language: "${ctx.detectedLanguage}"`,
|
|
278
|
+
expected: `hreflang="${ctx.detectedLanguage}"`,
|
|
279
|
+
impact: 'Language mismatch may confuse search engines and affect international SEO',
|
|
280
|
+
example: `<link rel="alternate" hreflang="${ctx.detectedLanguage}" href="${ctx.url}">`,
|
|
242
281
|
}
|
|
243
282
|
});
|
|
244
283
|
}
|
|
245
284
|
}
|
|
246
|
-
return
|
|
285
|
+
return createResult({ id: 'hreflang-language-mismatch', name: 'Hreflang Language Mismatch', category: 'technical', severity: 'warning' }, 'pass', 'Hreflang language matches detected content language');
|
|
247
286
|
},
|
|
248
287
|
},
|
|
249
288
|
];
|
package/dist/seo/rules/images.js
CHANGED
|
@@ -8,13 +8,24 @@ export const imageRules = [
|
|
|
8
8
|
severity: 'error',
|
|
9
9
|
description: 'All images must have alt text',
|
|
10
10
|
check: (ctx) => {
|
|
11
|
-
if (ctx.totalImages === undefined || ctx.totalImages === 0)
|
|
12
|
-
return
|
|
11
|
+
if (ctx.totalImages === undefined || ctx.totalImages === 0) {
|
|
12
|
+
return createResult({ id: 'images-alt-text', name: 'Image Alt Text', category: 'images', severity: 'error' }, 'info', 'Not applicable (no images detected)', { recommendation: 'This rule ensures all images have descriptive alt text for accessibility and SEO' });
|
|
13
|
+
}
|
|
13
14
|
const withoutAlt = ctx.imagesWithoutAlt ?? 0;
|
|
14
15
|
if (withoutAlt > 0) {
|
|
15
16
|
const percentage = Math.round((withoutAlt / ctx.totalImages) * 100);
|
|
16
17
|
const severity = withoutAlt > ctx.totalImages / 2 ? 'fail' : 'warn';
|
|
17
|
-
return createResult({ id: 'images-alt-text', name: 'Image Alt Text', category: 'images', severity: 'error' }, severity, `${withoutAlt} of ${ctx.totalImages} images missing alt text (${percentage}%)`, {
|
|
18
|
+
return createResult({ id: 'images-alt-text', name: 'Image Alt Text', category: 'images', severity: 'error' }, severity, `${withoutAlt} of ${ctx.totalImages} images missing alt text (${percentage}%)`, {
|
|
19
|
+
value: withoutAlt,
|
|
20
|
+
recommendation: 'Add descriptive alt text to all images. Describe what the image shows and its relevance to the content.',
|
|
21
|
+
evidence: {
|
|
22
|
+
found: `${withoutAlt} images without alt attribute`,
|
|
23
|
+
expected: 'All images should have meaningful alt text',
|
|
24
|
+
impact: 'Missing alt text hurts accessibility (screen readers) and prevents images from appearing in Google Image Search. Alt text is also used as anchor text when images are linked.',
|
|
25
|
+
example: '<img src="product.jpg" alt="Red leather wallet with zipper closure - front view">',
|
|
26
|
+
learnMore: 'https://developers.google.com/search/docs/appearance/google-images#use-descriptive-alt-text'
|
|
27
|
+
}
|
|
28
|
+
});
|
|
18
29
|
}
|
|
19
30
|
return createResult({ id: 'images-alt-text', name: 'Image Alt Text', category: 'images', severity: 'error' }, 'pass', 'All images have alt text');
|
|
20
31
|
},
|
|
@@ -26,11 +37,23 @@ export const imageRules = [
|
|
|
26
37
|
severity: 'warning',
|
|
27
38
|
description: 'Images should have width and height attributes to prevent CLS',
|
|
28
39
|
check: (ctx) => {
|
|
29
|
-
if (ctx.totalImages === undefined || ctx.totalImages === 0)
|
|
30
|
-
return
|
|
40
|
+
if (ctx.totalImages === undefined || ctx.totalImages === 0) {
|
|
41
|
+
return createResult({ id: 'images-dimensions', name: 'Image Dimensions', category: 'images', severity: 'warning' }, 'info', 'Not applicable (no images detected)', { recommendation: 'This rule checks that images have width and height attributes to prevent layout shifts' });
|
|
42
|
+
}
|
|
31
43
|
const missing = ctx.imagesMissingDimensions ?? 0;
|
|
32
44
|
if (missing > 0) {
|
|
33
|
-
|
|
45
|
+
const percentage = Math.round((missing / ctx.totalImages) * 100);
|
|
46
|
+
return createResult({ id: 'images-dimensions', name: 'Image Dimensions', category: 'images', severity: 'warning' }, 'warn', `${missing} of ${ctx.totalImages} images missing width/height (${percentage}%)`, {
|
|
47
|
+
value: missing,
|
|
48
|
+
recommendation: 'Add explicit width and height attributes to all images to reserve space and prevent layout shifts.',
|
|
49
|
+
evidence: {
|
|
50
|
+
found: `${missing} images without dimensions`,
|
|
51
|
+
expected: 'All images should have width and height attributes',
|
|
52
|
+
impact: 'Images without dimensions cause Cumulative Layout Shift (CLS), which negatively affects Core Web Vitals and user experience.',
|
|
53
|
+
example: '<img src="photo.jpg" width="800" height="600" alt="Description">',
|
|
54
|
+
learnMore: 'https://web.dev/optimize-cls/#images-without-dimensions'
|
|
55
|
+
}
|
|
56
|
+
});
|
|
34
57
|
}
|
|
35
58
|
return createResult({ id: 'images-dimensions', name: 'Image Dimensions', category: 'images', severity: 'warning' }, 'pass', 'All images have dimensions defined');
|
|
36
59
|
},
|
|
@@ -42,11 +65,21 @@ export const imageRules = [
|
|
|
42
65
|
severity: 'info',
|
|
43
66
|
description: 'Below-the-fold images should use lazy loading',
|
|
44
67
|
check: (ctx) => {
|
|
45
|
-
if (ctx.totalImages === undefined || ctx.totalImages <= 3)
|
|
46
|
-
return
|
|
68
|
+
if (ctx.totalImages === undefined || ctx.totalImages <= 3) {
|
|
69
|
+
return createResult({ id: 'images-lazy-loading', name: 'Lazy Loading', category: 'images', severity: 'info' }, 'info', 'Not applicable (too few images to require lazy loading)', { recommendation: 'This rule checks for lazy loading on pages with multiple images' });
|
|
70
|
+
}
|
|
47
71
|
const lazy = ctx.imagesWithLazyLoad ?? 0;
|
|
48
72
|
if (lazy === 0) {
|
|
49
|
-
return createResult({ id: 'images-lazy-loading', name: 'Lazy Loading', category: 'images', severity: 'info' }, 'info', 'No images use lazy loading', {
|
|
73
|
+
return createResult({ id: 'images-lazy-loading', name: 'Lazy Loading', category: 'images', severity: 'info' }, 'info', 'No images use lazy loading', {
|
|
74
|
+
recommendation: 'Add loading="lazy" to below-the-fold images to defer loading until they are near the viewport.',
|
|
75
|
+
evidence: {
|
|
76
|
+
found: 'No images with loading="lazy"',
|
|
77
|
+
expected: 'Below-the-fold images should use lazy loading',
|
|
78
|
+
impact: 'Lazy loading reduces initial page load time, saves bandwidth, and improves Core Web Vitals (LCP).',
|
|
79
|
+
example: '<img src="photo.jpg" loading="lazy" alt="Description">',
|
|
80
|
+
learnMore: 'https://web.dev/browser-level-image-lazy-loading/'
|
|
81
|
+
}
|
|
82
|
+
});
|
|
50
83
|
}
|
|
51
84
|
return createResult({ id: 'images-lazy-loading', name: 'Lazy Loading', category: 'images', severity: 'info' }, 'pass', `${lazy} images use lazy loading`);
|
|
52
85
|
},
|
|
@@ -58,11 +91,22 @@ export const imageRules = [
|
|
|
58
91
|
severity: 'info',
|
|
59
92
|
description: 'Images should use modern formats like WebP or AVIF',
|
|
60
93
|
check: (ctx) => {
|
|
61
|
-
if (ctx.totalImages === undefined || ctx.totalImages === 0)
|
|
62
|
-
return
|
|
94
|
+
if (ctx.totalImages === undefined || ctx.totalImages === 0) {
|
|
95
|
+
return createResult({ id: 'images-format-modern', name: 'Modern Image Formats', category: 'images', severity: 'info' }, 'info', 'Not applicable (no images detected)', { recommendation: 'This rule checks for modern image formats like WebP and AVIF for better performance' });
|
|
96
|
+
}
|
|
63
97
|
const modern = ctx.imagesUsingModernFormats ?? 0;
|
|
64
98
|
if (ctx.totalImages > 0 && modern === 0) {
|
|
65
|
-
return createResult({ id: 'images-format-modern', name: 'Modern Image Formats', category: 'images', severity: 'info' }, 'info', 'No images using modern formats (WebP/AVIF)', {
|
|
99
|
+
return createResult({ id: 'images-format-modern', name: 'Modern Image Formats', category: 'images', severity: 'info' }, 'info', 'No images using modern formats (WebP/AVIF)', {
|
|
100
|
+
value: modern,
|
|
101
|
+
recommendation: 'Convert images to WebP or AVIF format for 25-50% smaller file sizes with same quality.',
|
|
102
|
+
evidence: {
|
|
103
|
+
found: 'Only traditional formats (JPEG, PNG, GIF)',
|
|
104
|
+
expected: 'At least some images in WebP or AVIF format',
|
|
105
|
+
impact: 'Modern formats significantly reduce page weight and improve load times. WebP has 94% browser support.',
|
|
106
|
+
example: '<picture>\n <source srcset="image.avif" type="image/avif">\n <source srcset="image.webp" type="image/webp">\n <img src="image.jpg" alt="Description">\n</picture>',
|
|
107
|
+
learnMore: 'https://web.dev/uses-webp-images/'
|
|
108
|
+
}
|
|
109
|
+
});
|
|
66
110
|
}
|
|
67
111
|
return createResult({ id: 'images-format-modern', name: 'Modern Image Formats', category: 'images', severity: 'info' }, 'pass', `${modern} images using modern formats`, { value: modern });
|
|
68
112
|
},
|
|
@@ -78,7 +122,7 @@ export const imageRules = [
|
|
|
78
122
|
if (emptyAlt > 0) {
|
|
79
123
|
return createResult({ id: 'images-empty-alt', name: 'Empty Alt Text', category: 'images', severity: 'info' }, 'info', `${emptyAlt} image(s) with empty alt="" (decorative)`, { value: emptyAlt, recommendation: 'Ensure these images are truly decorative' });
|
|
80
124
|
}
|
|
81
|
-
return
|
|
125
|
+
return createResult({ id: 'images-empty-alt', name: 'Empty Alt Text', category: 'images', severity: 'info' }, 'pass', 'No images with empty alt attributes');
|
|
82
126
|
},
|
|
83
127
|
},
|
|
84
128
|
{
|
|
@@ -88,8 +132,9 @@ export const imageRules = [
|
|
|
88
132
|
severity: 'warning',
|
|
89
133
|
description: 'Alt text should be descriptive (ideal 80-120, max 150 chars)',
|
|
90
134
|
check: (ctx) => {
|
|
91
|
-
if (!ctx.altTextLengths || ctx.altTextLengths.length === 0)
|
|
92
|
-
return
|
|
135
|
+
if (!ctx.altTextLengths || ctx.altTextLengths.length === 0) {
|
|
136
|
+
return createResult({ id: 'images-alt-length', name: 'Alt Text Length', category: 'images', severity: 'warning' }, 'info', 'Not applicable (no alt text data available)', { recommendation: 'This rule checks alt text length to ensure descriptions are meaningful' });
|
|
137
|
+
}
|
|
93
138
|
const { minLength, idealLength, maxLength } = SEO_THRESHOLDS.images.alt;
|
|
94
139
|
let shortAlts = 0;
|
|
95
140
|
let longAlts = 0;
|
|
@@ -103,15 +148,35 @@ export const imageRules = [
|
|
|
103
148
|
nonIdealAlts++;
|
|
104
149
|
});
|
|
105
150
|
if (shortAlts > 0) {
|
|
106
|
-
return createResult({ id: 'images-alt-length', name: 'Alt Text Length', category: 'images', severity: 'warning' }, 'warn', `${shortAlts} alt text(s) are too short (min: ${minLength} chars)`, {
|
|
151
|
+
return createResult({ id: 'images-alt-length', name: 'Alt Text Length', category: 'images', severity: 'warning' }, 'warn', `${shortAlts} alt text(s) are too short (min: ${minLength} chars)`, {
|
|
152
|
+
value: shortAlts,
|
|
153
|
+
recommendation: `Make alt texts more descriptive, at least ${minLength} characters.`,
|
|
154
|
+
evidence: {
|
|
155
|
+
found: `${shortAlts} alt text(s) with less than ${minLength} characters`,
|
|
156
|
+
expected: `Alt text should be at least ${minLength} characters to be meaningful`,
|
|
157
|
+
impact: 'Very short alt text (like "logo" or "pic") doesn\'t provide enough context for screen readers or search engines, reducing accessibility and SEO value.',
|
|
158
|
+
example: 'Instead of alt="logo", use alt="Acme Corp logo - handshake symbol representing trust"',
|
|
159
|
+
learnMore: 'https://www.w3.org/WAI/tutorials/images/'
|
|
160
|
+
}
|
|
161
|
+
});
|
|
107
162
|
}
|
|
108
163
|
if (longAlts > 0) {
|
|
109
|
-
return createResult({ id: 'images-alt-length', name: 'Alt Text Length', category: 'images', severity: 'warning' }, 'warn', `${longAlts} alt text(s) are too long (max: ${maxLength} chars)`, {
|
|
164
|
+
return createResult({ id: 'images-alt-length', name: 'Alt Text Length', category: 'images', severity: 'warning' }, 'warn', `${longAlts} alt text(s) are too long (max: ${maxLength} chars)`, {
|
|
165
|
+
value: longAlts,
|
|
166
|
+
recommendation: `Shorten alt texts to be concise, under ${maxLength} characters.`,
|
|
167
|
+
evidence: {
|
|
168
|
+
found: `${longAlts} alt text(s) exceeding ${maxLength} characters`,
|
|
169
|
+
expected: `Alt text should be under ${maxLength} characters for optimal accessibility`,
|
|
170
|
+
impact: 'Excessively long alt text can be tedious for screen reader users and may get truncated in search results. Keep it concise while still being descriptive.',
|
|
171
|
+
example: 'Instead of a long paragraph, use concise descriptions like: alt="Professional headshot of Jane Smith, CEO, wearing navy blue suit"',
|
|
172
|
+
learnMore: 'https://www.w3.org/WAI/tutorials/images/'
|
|
173
|
+
}
|
|
174
|
+
});
|
|
110
175
|
}
|
|
111
176
|
if (nonIdealAlts > 0) {
|
|
112
177
|
return createResult({ id: 'images-alt-length', name: 'Alt Text Length', category: 'images', severity: 'info' }, 'info', `${nonIdealAlts} alt text(s) are not in the ideal length range (${idealLength.min}-${idealLength.max} chars)`, { value: nonIdealAlts, recommendation: `Aim for alt texts between ${idealLength.min} and ${idealLength.max} characters for best results.` });
|
|
113
178
|
}
|
|
114
|
-
return
|
|
179
|
+
return createResult({ id: 'images-alt-length', name: 'Alt Text Length', category: 'images', severity: 'warning' }, 'pass', 'All alt text lengths are within ideal range');
|
|
115
180
|
},
|
|
116
181
|
},
|
|
117
182
|
{
|
|
@@ -121,15 +186,26 @@ export const imageRules = [
|
|
|
121
186
|
severity: 'info',
|
|
122
187
|
description: 'Image filenames should be descriptive and use keywords, not generic names.',
|
|
123
188
|
check: (ctx) => {
|
|
124
|
-
if (!ctx.imageFilenames || ctx.imageFilenames.length === 0)
|
|
125
|
-
return
|
|
189
|
+
if (!ctx.imageFilenames || ctx.imageFilenames.length === 0) {
|
|
190
|
+
return createResult({ id: 'images-clean-filenames', name: 'Image Filenames', category: 'images', severity: 'info' }, 'info', 'Not applicable (no image filename data available)', { recommendation: 'This rule checks for descriptive image filenames with keywords' });
|
|
191
|
+
}
|
|
126
192
|
const genericFilenames = ctx.imageFilenames.filter(name => /^(img|image|photo|pic)\d*\.(jpg|jpeg|png|webp|avif|gif)$/i.test(name) ||
|
|
127
193
|
/^screenshot_\d*\.(jpg|jpeg|png)$/i.test(name) ||
|
|
128
194
|
/^untitled-\d*\.(jpg|jpeg|png)$/i.test(name));
|
|
129
195
|
if (genericFilenames.length > 0) {
|
|
130
|
-
return createResult({ id: 'images-clean-filenames', name: 'Image Filenames', category: 'images', severity: 'warning' }, 'warn', `${genericFilenames.length} image(s) have generic filenames
|
|
196
|
+
return createResult({ id: 'images-clean-filenames', name: 'Image Filenames', category: 'images', severity: 'warning' }, 'warn', `${genericFilenames.length} image(s) have generic filenames`, {
|
|
197
|
+
value: genericFilenames.length,
|
|
198
|
+
recommendation: 'Rename image files to be descriptive and include relevant keywords.',
|
|
199
|
+
evidence: {
|
|
200
|
+
found: genericFilenames.slice(0, 5),
|
|
201
|
+
expected: 'Descriptive filenames with keywords (e.g., red-leather-wallet-front.jpg)',
|
|
202
|
+
impact: 'Image filenames are used by search engines to understand image content and can appear in Google Image Search.',
|
|
203
|
+
example: 'Instead of "IMG_1234.jpg", use "blue-running-shoes-nike-air.jpg"',
|
|
204
|
+
learnMore: 'https://developers.google.com/search/docs/appearance/google-images#descriptive-titles-captions-filenames'
|
|
205
|
+
}
|
|
206
|
+
});
|
|
131
207
|
}
|
|
132
|
-
return
|
|
208
|
+
return createResult({ id: 'images-clean-filenames', name: 'Image Filenames', category: 'images', severity: 'info' }, 'pass', 'All image filenames are descriptive');
|
|
133
209
|
},
|
|
134
210
|
},
|
|
135
211
|
{
|
|
@@ -139,15 +215,17 @@ export const imageRules = [
|
|
|
139
215
|
severity: 'info',
|
|
140
216
|
description: 'Use decoding="async" for non-critical images to improve rendering performance.',
|
|
141
217
|
check: (ctx) => {
|
|
142
|
-
if (ctx.totalImages === undefined || ctx.totalImages === 0)
|
|
143
|
-
return
|
|
144
|
-
|
|
145
|
-
|
|
218
|
+
if (ctx.totalImages === undefined || ctx.totalImages === 0) {
|
|
219
|
+
return createResult({ id: 'images-decoding-async', name: 'Image Decoding Async', category: 'images', severity: 'info' }, 'info', 'Not applicable (no images detected)', { recommendation: 'This rule checks for async decoding on images for better rendering performance' });
|
|
220
|
+
}
|
|
221
|
+
if (ctx.imagesWithAsyncDecoding === undefined) {
|
|
222
|
+
return createResult({ id: 'images-decoding-async', name: 'Image Decoding Async', category: 'images', severity: 'info' }, 'info', 'Not applicable (async decoding data unavailable)', { recommendation: 'This rule checks for async decoding on images for better rendering performance' });
|
|
223
|
+
}
|
|
146
224
|
const nonAsync = ctx.totalImages - ctx.imagesWithAsyncDecoding;
|
|
147
225
|
if (nonAsync > 0 && ctx.totalImages > 3) {
|
|
148
226
|
return createResult({ id: 'images-decoding-async', name: 'Image Decoding Async', category: 'images', severity: 'info' }, 'info', `${nonAsync} image(s) do not use decoding="async"`, { value: nonAsync, recommendation: 'Consider adding decoding="async" to non-critical images for performance benefits.' });
|
|
149
227
|
}
|
|
150
|
-
return
|
|
228
|
+
return createResult({ id: 'images-decoding-async', name: 'Image Decoding Async', category: 'images', severity: 'info' }, 'pass', 'All images use async decoding or too few images to require it');
|
|
151
229
|
},
|
|
152
230
|
},
|
|
153
231
|
{
|
|
@@ -157,8 +235,9 @@ export const imageRules = [
|
|
|
157
235
|
severity: 'warning',
|
|
158
236
|
description: 'External images should be accessible',
|
|
159
237
|
check: (ctx) => {
|
|
160
|
-
if (ctx.brokenExternalImages === undefined)
|
|
161
|
-
return
|
|
238
|
+
if (ctx.brokenExternalImages === undefined) {
|
|
239
|
+
return createResult({ id: 'broken-external-images', name: 'Broken External Images', category: 'images', severity: 'warning' }, 'info', 'Not applicable (broken external images data unavailable)', { recommendation: 'This rule checks for broken external image references' });
|
|
240
|
+
}
|
|
162
241
|
if (ctx.brokenExternalImages > 0) {
|
|
163
242
|
return createResult({ id: 'broken-external-images', name: 'Broken External Images', category: 'images', severity: 'warning' }, 'warn', `${ctx.brokenExternalImages} broken external images`, {
|
|
164
243
|
value: ctx.brokenExternalImages,
|
|
@@ -170,7 +249,7 @@ export const imageRules = [
|
|
|
170
249
|
}
|
|
171
250
|
});
|
|
172
251
|
}
|
|
173
|
-
return
|
|
252
|
+
return createResult({ id: 'broken-external-images', name: 'Broken External Images', category: 'images', severity: 'warning' }, 'pass', 'No broken external images detected');
|
|
174
253
|
},
|
|
175
254
|
},
|
|
176
255
|
];
|
package/dist/seo/rules/index.js
CHANGED
|
@@ -24,6 +24,7 @@ import { resourceRules } from './resources.js';
|
|
|
24
24
|
import { technicalAdvancedRules } from './technical-advanced.js';
|
|
25
25
|
import { redirectRules } from './redirects.js';
|
|
26
26
|
import { canonicalRules } from './canonical.js';
|
|
27
|
+
import { analyticsRules } from './analytics.js';
|
|
27
28
|
export * from './types.js';
|
|
28
29
|
export * from './thresholds.js';
|
|
29
30
|
export const ALL_SEO_RULES = [
|
|
@@ -53,6 +54,7 @@ export const ALL_SEO_RULES = [
|
|
|
53
54
|
...technicalAdvancedRules,
|
|
54
55
|
...redirectRules,
|
|
55
56
|
...canonicalRules,
|
|
57
|
+
...analyticsRules,
|
|
56
58
|
];
|
|
57
59
|
export const SCORING_WEIGHTS = {
|
|
58
60
|
severity: {
|