recker 1.0.43 → 1.0.44
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +47 -0
- package/dist/bin/recker-linux-x64 +0 -0
- package/dist/bin/recker-macos-x64 +0 -0
- package/dist/bin/recker-win-x64.exe +0 -0
- package/dist/bin/rek.cjs +85152 -100207
- package/dist/browser/ai/adaptive-timeout.d.ts +50 -0
- package/dist/browser/ai/adaptive-timeout.js +208 -0
- package/dist/browser/ai/client.d.ts +22 -0
- package/dist/browser/ai/client.js +294 -0
- package/dist/browser/ai/index.d.ts +14 -0
- package/dist/browser/ai/index.js +11 -0
- package/dist/browser/ai/providers/anthropic.d.ts +63 -0
- package/dist/browser/ai/providers/anthropic.js +370 -0
- package/dist/browser/ai/providers/base.d.ts +48 -0
- package/dist/browser/ai/providers/base.js +150 -0
- package/dist/browser/ai/providers/google.d.ts +59 -0
- package/dist/browser/ai/providers/google.js +305 -0
- package/dist/browser/ai/providers/ollama.d.ts +44 -0
- package/dist/browser/ai/providers/ollama.js +240 -0
- package/dist/browser/ai/providers/openai.d.ts +64 -0
- package/dist/browser/ai/providers/openai.js +298 -0
- package/dist/browser/ai/rate-limiter.d.ts +43 -0
- package/dist/browser/ai/rate-limiter.js +215 -0
- package/dist/browser/ai/vector/index.d.ts +2 -0
- package/dist/browser/ai/vector/index.js +2 -0
- package/dist/browser/ai/vector/similarity.d.ts +2 -0
- package/dist/browser/ai/vector/similarity.js +27 -0
- package/dist/browser/ai/vector/store.d.ts +27 -0
- package/dist/browser/ai/vector/store.js +82 -0
- package/dist/browser/browser/cache.d.ts +2 -40
- package/dist/browser/browser/cache.js +2 -199
- package/dist/browser/browser/index.d.ts +8 -0
- package/dist/browser/browser/index.js +8 -0
- package/dist/browser/browser/recker.d.ts +8 -1
- package/dist/browser/browser/recker.js +8 -2
- package/dist/browser/cache/indexed-db.d.ts +10 -0
- package/dist/browser/cache/indexed-db.js +88 -0
- package/dist/browser/cache/service-worker-cache.d.ts +18 -0
- package/dist/browser/cache/service-worker-cache.js +103 -0
- package/dist/browser/cache.d.ts +2 -40
- package/dist/browser/cache.js +2 -199
- package/dist/browser/constants/user-agents.d.ts +7 -0
- package/dist/browser/constants/user-agents.js +7 -0
- package/dist/browser/core/client.d.ts +2 -0
- package/dist/browser/core/client.js +19 -1
- package/dist/browser/index.d.ts +8 -0
- package/dist/browser/index.js +8 -0
- package/dist/browser/plugins/har-recorder.d.ts +40 -0
- package/dist/browser/plugins/har-recorder.js +120 -0
- package/dist/browser/plugins/network-simulation.d.ts +7 -0
- package/dist/browser/plugins/network-simulation.js +13 -0
- package/dist/browser/presets/android.d.ts +2 -0
- package/dist/browser/presets/android.js +16 -0
- package/dist/browser/presets/anthropic.d.ts +8 -0
- package/dist/browser/presets/anthropic.js +27 -0
- package/dist/browser/presets/aws.d.ts +19 -0
- package/dist/browser/presets/aws.js +68 -0
- package/dist/browser/presets/azure-openai.d.ts +10 -0
- package/dist/browser/presets/azure-openai.js +35 -0
- package/dist/browser/presets/azure.d.ts +41 -0
- package/dist/browser/presets/azure.js +104 -0
- package/dist/browser/presets/chaturbate.d.ts +2 -0
- package/dist/browser/presets/chaturbate.js +17 -0
- package/dist/browser/presets/cloudflare.d.ts +12 -0
- package/dist/browser/presets/cloudflare.js +39 -0
- package/dist/browser/presets/cohere.d.ts +7 -0
- package/dist/browser/presets/cohere.js +22 -0
- package/dist/browser/presets/deepseek.d.ts +7 -0
- package/dist/browser/presets/deepseek.js +22 -0
- package/dist/browser/presets/digitalocean.d.ts +5 -0
- package/dist/browser/presets/digitalocean.js +16 -0
- package/dist/browser/presets/discord.d.ts +6 -0
- package/dist/browser/presets/discord.js +17 -0
- package/dist/browser/presets/elevenlabs.d.ts +6 -0
- package/dist/browser/presets/elevenlabs.js +20 -0
- package/dist/browser/presets/enhancers.d.ts +20 -0
- package/dist/browser/presets/enhancers.js +85 -0
- package/dist/browser/presets/fireworks.d.ts +7 -0
- package/dist/browser/presets/fireworks.js +22 -0
- package/dist/browser/presets/gcp.d.ts +34 -0
- package/dist/browser/presets/gcp.js +91 -0
- package/dist/browser/presets/gemini.d.ts +7 -0
- package/dist/browser/presets/gemini.js +23 -0
- package/dist/browser/presets/github.d.ts +6 -0
- package/dist/browser/presets/github.js +17 -0
- package/dist/browser/presets/gitlab.d.ts +6 -0
- package/dist/browser/presets/gitlab.js +16 -0
- package/dist/browser/presets/groq.d.ts +7 -0
- package/dist/browser/presets/groq.js +22 -0
- package/dist/browser/presets/hubspot.d.ts +9 -0
- package/dist/browser/presets/hubspot.js +28 -0
- package/dist/browser/presets/huggingface.d.ts +7 -0
- package/dist/browser/presets/huggingface.js +23 -0
- package/dist/browser/presets/index.d.ts +47 -0
- package/dist/browser/presets/index.js +47 -0
- package/dist/browser/presets/ios.d.ts +2 -0
- package/dist/browser/presets/ios.js +13 -0
- package/dist/browser/presets/linear.d.ts +5 -0
- package/dist/browser/presets/linear.js +16 -0
- package/dist/browser/presets/mailgun.d.ts +7 -0
- package/dist/browser/presets/mailgun.js +20 -0
- package/dist/browser/presets/meta.d.ts +10 -0
- package/dist/browser/presets/meta.js +33 -0
- package/dist/browser/presets/mistral.d.ts +7 -0
- package/dist/browser/presets/mistral.js +22 -0
- package/dist/browser/presets/notion.d.ts +6 -0
- package/dist/browser/presets/notion.js +17 -0
- package/dist/browser/presets/openai.d.ts +9 -0
- package/dist/browser/presets/openai.js +30 -0
- package/dist/browser/presets/oracle.d.ts +19 -0
- package/dist/browser/presets/oracle.js +117 -0
- package/dist/browser/presets/perplexity.d.ts +7 -0
- package/dist/browser/presets/perplexity.js +22 -0
- package/dist/browser/presets/pinecone.d.ts +8 -0
- package/dist/browser/presets/pinecone.js +42 -0
- package/dist/browser/presets/registry.d.ts +23 -0
- package/dist/browser/presets/registry.js +519 -0
- package/dist/browser/presets/replicate.d.ts +7 -0
- package/dist/browser/presets/replicate.js +23 -0
- package/dist/browser/presets/sendgrid.d.ts +6 -0
- package/dist/browser/presets/sendgrid.js +20 -0
- package/dist/browser/presets/sentry.d.ts +11 -0
- package/dist/browser/presets/sentry.js +48 -0
- package/dist/browser/presets/sinch.d.ts +9 -0
- package/dist/browser/presets/sinch.js +39 -0
- package/dist/browser/presets/slack.d.ts +5 -0
- package/dist/browser/presets/slack.js +16 -0
- package/dist/browser/presets/square.d.ts +10 -0
- package/dist/browser/presets/square.js +33 -0
- package/dist/browser/presets/stripe.d.ts +7 -0
- package/dist/browser/presets/stripe.js +23 -0
- package/dist/browser/presets/supabase.d.ts +6 -0
- package/dist/browser/presets/supabase.js +18 -0
- package/dist/browser/presets/tiktok.d.ts +10 -0
- package/dist/browser/presets/tiktok.js +38 -0
- package/dist/browser/presets/together.d.ts +7 -0
- package/dist/browser/presets/together.js +22 -0
- package/dist/browser/presets/twilio.d.ts +6 -0
- package/dist/browser/presets/twilio.js +17 -0
- package/dist/browser/presets/vercel.d.ts +6 -0
- package/dist/browser/presets/vercel.js +23 -0
- package/dist/browser/presets/vultr.d.ts +5 -0
- package/dist/browser/presets/vultr.js +16 -0
- package/dist/browser/presets/xai.d.ts +8 -0
- package/dist/browser/presets/xai.js +23 -0
- package/dist/browser/presets/youtube.d.ts +5 -0
- package/dist/browser/presets/youtube.js +20 -0
- package/dist/browser/recker.d.ts +8 -1
- package/dist/browser/recker.js +8 -2
- package/dist/browser/scrape/document.d.ts +5 -4
- package/dist/browser/scrape/document.js +89 -76
- package/dist/browser/scrape/element.d.ts +10 -8
- package/dist/browser/scrape/element.js +295 -81
- package/dist/browser/scrape/extractors.d.ts +11 -11
- package/dist/browser/scrape/extractors.js +145 -113
- package/dist/browser/scrape/parser/back.d.ts +1 -0
- package/dist/browser/scrape/parser/back.js +3 -0
- package/dist/browser/scrape/parser/index.d.ts +20 -0
- package/dist/browser/scrape/parser/index.js +19 -0
- package/dist/browser/scrape/parser/matcher.d.ts +30 -0
- package/dist/browser/scrape/parser/matcher.js +99 -0
- package/dist/browser/scrape/parser/nodes/comment.d.ts +12 -0
- package/dist/browser/scrape/parser/nodes/comment.js +21 -0
- package/dist/browser/scrape/parser/nodes/html.d.ts +110 -0
- package/dist/browser/scrape/parser/nodes/html.js +978 -0
- package/dist/browser/scrape/parser/nodes/node.d.ts +18 -0
- package/dist/browser/scrape/parser/nodes/node.js +31 -0
- package/dist/browser/scrape/parser/nodes/text.d.ts +14 -0
- package/dist/browser/scrape/parser/nodes/text.js +30 -0
- package/dist/browser/scrape/parser/nodes/type.d.ts +6 -0
- package/dist/browser/scrape/parser/nodes/type.js +7 -0
- package/dist/browser/scrape/parser/parse.d.ts +1 -0
- package/dist/browser/scrape/parser/parse.js +1 -0
- package/dist/browser/scrape/parser/valid.d.ts +2 -0
- package/dist/browser/scrape/parser/valid.js +5 -0
- package/dist/browser/scrape/parser/void-tag.d.ts +7 -0
- package/dist/browser/scrape/parser/void-tag.js +43 -0
- package/dist/browser/scrape/types.d.ts +7 -0
- package/dist/browser/seo/analyzer.d.ts +59 -0
- package/dist/browser/seo/analyzer.js +1399 -0
- package/dist/browser/seo/keywords.d.ts +16 -0
- package/dist/browser/seo/keywords.js +55 -0
- package/dist/browser/seo/rules/accessibility.d.ts +2 -0
- package/dist/browser/seo/rules/accessibility.js +733 -0
- package/dist/browser/seo/rules/ai-search.d.ts +2 -0
- package/dist/browser/seo/rules/ai-search.js +436 -0
- package/dist/browser/seo/rules/analytics.d.ts +2 -0
- package/dist/browser/seo/rules/analytics.js +306 -0
- package/dist/browser/seo/rules/best-practices.d.ts +2 -0
- package/dist/browser/seo/rules/best-practices.js +195 -0
- package/dist/browser/seo/rules/canonical.d.ts +12 -0
- package/dist/browser/seo/rules/canonical.js +270 -0
- package/dist/browser/seo/rules/content.d.ts +2 -0
- package/dist/browser/seo/rules/content.js +522 -0
- package/dist/browser/seo/rules/crawl.d.ts +2 -0
- package/dist/browser/seo/rules/crawl.js +435 -0
- package/dist/browser/seo/rules/cwv.d.ts +2 -0
- package/dist/browser/seo/rules/cwv.js +248 -0
- package/dist/browser/seo/rules/ecommerce.d.ts +2 -0
- package/dist/browser/seo/rules/ecommerce.js +312 -0
- package/dist/browser/seo/rules/i18n.d.ts +2 -0
- package/dist/browser/seo/rules/i18n.js +288 -0
- package/dist/browser/seo/rules/images.d.ts +2 -0
- package/dist/browser/seo/rules/images.js +255 -0
- package/dist/browser/seo/rules/index.d.ts +52 -0
- package/dist/browser/seo/rules/index.js +159 -0
- package/dist/browser/seo/rules/internal-linking.d.ts +2 -0
- package/dist/browser/seo/rules/internal-linking.js +394 -0
- package/dist/browser/seo/rules/links.d.ts +2 -0
- package/dist/browser/seo/rules/links.js +498 -0
- package/dist/browser/seo/rules/local.d.ts +2 -0
- package/dist/browser/seo/rules/local.js +289 -0
- package/dist/browser/seo/rules/meta.d.ts +2 -0
- package/dist/browser/seo/rules/meta.js +805 -0
- package/dist/browser/seo/rules/mobile.d.ts +2 -0
- package/dist/browser/seo/rules/mobile.js +161 -0
- package/dist/browser/seo/rules/performance.d.ts +2 -0
- package/dist/browser/seo/rules/performance.js +738 -0
- package/dist/browser/seo/rules/pwa.d.ts +2 -0
- package/dist/browser/seo/rules/pwa.js +299 -0
- package/dist/browser/seo/rules/readability.d.ts +2 -0
- package/dist/browser/seo/rules/readability.js +264 -0
- package/dist/browser/seo/rules/redirects.d.ts +16 -0
- package/dist/browser/seo/rules/redirects.js +199 -0
- package/dist/browser/seo/rules/resources.d.ts +2 -0
- package/dist/browser/seo/rules/resources.js +390 -0
- package/dist/browser/seo/rules/schema.d.ts +2 -0
- package/dist/browser/seo/rules/schema.js +379 -0
- package/dist/browser/seo/rules/security.d.ts +2 -0
- package/dist/browser/seo/rules/security.js +877 -0
- package/dist/browser/seo/rules/social.d.ts +2 -0
- package/dist/browser/seo/rules/social.js +603 -0
- package/dist/browser/seo/rules/structural.d.ts +2 -0
- package/dist/browser/seo/rules/structural.js +223 -0
- package/dist/browser/seo/rules/technical-advanced.d.ts +10 -0
- package/dist/browser/seo/rules/technical-advanced.js +289 -0
- package/dist/browser/seo/rules/technical.d.ts +2 -0
- package/dist/browser/seo/rules/technical.js +480 -0
- package/dist/browser/seo/rules/thresholds.d.ts +196 -0
- package/dist/browser/seo/rules/thresholds.js +118 -0
- package/dist/browser/seo/rules/types.d.ts +498 -0
- package/dist/browser/seo/rules/types.js +11 -0
- package/dist/browser/seo/types.d.ts +211 -0
- package/dist/browser/seo/types.js +1 -0
- package/dist/browser/transport/curl.d.ts +4 -0
- package/dist/browser/transport/curl.js +101 -0
- package/dist/browser/transport/undici.js +1 -2
- package/dist/browser/transport/worker.d.ts +18 -0
- package/dist/browser/transport/worker.js +278 -0
- package/dist/browser/types/index.d.ts +4 -1
- package/dist/browser/utils/binary-manager.d.ts +4 -0
- package/dist/browser/utils/binary-manager.js +72 -0
- package/dist/browser/utils/user-agent.js +2 -13
- package/dist/cache/indexed-db.d.ts +10 -0
- package/dist/cache/indexed-db.js +88 -0
- package/dist/cache/service-worker-cache.d.ts +18 -0
- package/dist/cache/service-worker-cache.js +103 -0
- package/dist/cli/commands/ai.d.ts +2 -0
- package/dist/cli/commands/ai.js +162 -0
- package/dist/cli/commands/bench.d.ts +2 -0
- package/dist/cli/commands/bench.js +51 -0
- package/dist/cli/commands/dns.d.ts +2 -0
- package/dist/cli/commands/dns.js +295 -0
- package/dist/cli/commands/har.d.ts +2 -0
- package/dist/cli/commands/har.js +171 -0
- package/dist/cli/commands/hls.d.ts +2 -0
- package/dist/cli/commands/hls.js +192 -0
- package/dist/cli/commands/network.d.ts +2 -0
- package/dist/cli/commands/network.js +288 -0
- package/dist/cli/commands/protocols.d.ts +2 -0
- package/dist/cli/commands/protocols.js +344 -0
- package/dist/cli/commands/scrape.d.ts +2 -0
- package/dist/cli/commands/scrape.js +176 -0
- package/dist/cli/commands/security.d.ts +2 -0
- package/dist/cli/commands/security.js +57 -0
- package/dist/cli/commands/seo.d.ts +2 -0
- package/dist/cli/commands/seo.js +125 -0
- package/dist/cli/commands/serve.d.ts +2 -0
- package/dist/cli/commands/serve.js +531 -0
- package/dist/cli/commands/spider.d.ts +3 -0
- package/dist/cli/commands/spider.js +456 -0
- package/dist/cli/commands/utils.d.ts +2 -0
- package/dist/cli/commands/utils.js +176 -0
- package/dist/cli/commands/vector.d.ts +2 -0
- package/dist/cli/commands/vector.js +158 -0
- package/dist/cli/handler.d.ts +2 -2
- package/dist/cli/handler.js +6 -6
- package/dist/cli/helpers.d.ts +7 -0
- package/dist/cli/helpers.js +128 -0
- package/dist/cli/index.js +96 -5228
- package/dist/cli/parser/help.d.ts +2 -0
- package/dist/cli/parser/help.js +52 -0
- package/dist/cli/parser/index.d.ts +3 -0
- package/dist/cli/parser/index.js +3 -0
- package/dist/cli/parser/parser.d.ts +4 -0
- package/dist/cli/parser/parser.js +146 -0
- package/dist/cli/parser/types.d.ts +41 -0
- package/dist/cli/parser/types.js +1 -0
- package/dist/cli/presets.d.ts +1 -1
- package/dist/cli/presets.js +1 -1
- package/dist/cli/router.d.ts +36 -0
- package/dist/cli/router.js +195 -0
- package/dist/cli/tui/ai-chat.js +1 -1
- package/dist/cli/tui/commands/context.d.ts +9 -0
- package/dist/cli/tui/commands/context.js +1 -0
- package/dist/cli/tui/commands/dns.d.ts +10 -0
- package/dist/cli/tui/commands/dns.js +461 -0
- package/dist/cli/tui/commands/hls.d.ts +2 -0
- package/dist/cli/tui/commands/hls.js +162 -0
- package/dist/cli/tui/commands/ip.d.ts +2 -0
- package/dist/cli/tui/commands/ip.js +45 -0
- package/dist/cli/tui/commands/network.d.ts +3 -0
- package/dist/cli/tui/commands/network.js +81 -0
- package/dist/cli/tui/commands/protocols.d.ts +6 -0
- package/dist/cli/tui/commands/protocols.js +531 -0
- package/dist/cli/tui/commands/security.d.ts +2 -0
- package/dist/cli/tui/commands/security.js +48 -0
- package/dist/cli/tui/commands/seo.d.ts +2 -0
- package/dist/cli/tui/commands/seo.js +74 -0
- package/dist/cli/tui/context.d.ts +12 -0
- package/dist/cli/tui/context.js +1 -0
- package/dist/cli/tui/shell.d.ts +11 -20
- package/dist/cli/tui/shell.js +216 -1873
- package/dist/constants/user-agents.d.ts +7 -0
- package/dist/constants/user-agents.js +7 -0
- package/dist/core/client.d.ts +2 -0
- package/dist/core/client.js +19 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/mcp/cli.js +2 -3
- package/dist/mcp/data/embeddings.json +1 -1
- package/dist/mcp/tools/network.js +298 -158
- package/dist/plugins/har-player.d.ts +23 -0
- package/dist/plugins/har-player.js +49 -0
- package/dist/plugins/har-recorder.d.ts +37 -3
- package/dist/plugins/har-recorder.js +116 -63
- package/dist/plugins/network-simulation.d.ts +7 -0
- package/dist/plugins/network-simulation.js +13 -0
- package/dist/presets/android.d.ts +2 -0
- package/dist/presets/android.js +16 -0
- package/dist/presets/chaturbate.d.ts +2 -0
- package/dist/presets/chaturbate.js +17 -0
- package/dist/presets/elevenlabs.d.ts +6 -0
- package/dist/presets/elevenlabs.js +20 -0
- package/dist/presets/enhancers.d.ts +20 -0
- package/dist/presets/enhancers.js +85 -0
- package/dist/presets/hubspot.d.ts +9 -0
- package/dist/presets/hubspot.js +28 -0
- package/dist/presets/index.d.ts +10 -0
- package/dist/presets/index.js +10 -0
- package/dist/presets/ios.d.ts +2 -0
- package/dist/presets/ios.js +13 -0
- package/dist/presets/pinecone.d.ts +8 -0
- package/dist/presets/pinecone.js +42 -0
- package/dist/presets/registry.js +60 -0
- package/dist/presets/sendgrid.d.ts +6 -0
- package/dist/presets/sendgrid.js +20 -0
- package/dist/presets/sentry.d.ts +11 -0
- package/dist/presets/sentry.js +48 -0
- package/dist/presets/square.d.ts +10 -0
- package/dist/presets/square.js +33 -0
- package/dist/recker.d.ts +3 -0
- package/dist/recker.js +4 -0
- package/dist/scrape/document.d.ts +5 -4
- package/dist/scrape/document.js +89 -76
- package/dist/scrape/element.d.ts +10 -8
- package/dist/scrape/element.js +295 -81
- package/dist/scrape/extractors.d.ts +11 -11
- package/dist/scrape/extractors.js +145 -113
- package/dist/scrape/index.d.ts +2 -0
- package/dist/scrape/index.js +1 -0
- package/dist/scrape/parser/back.d.ts +1 -0
- package/dist/scrape/parser/back.js +3 -0
- package/dist/scrape/parser/index.d.ts +20 -0
- package/dist/scrape/parser/index.js +19 -0
- package/dist/scrape/parser/matcher.d.ts +30 -0
- package/dist/scrape/parser/matcher.js +99 -0
- package/dist/scrape/parser/nodes/comment.d.ts +12 -0
- package/dist/scrape/parser/nodes/comment.js +21 -0
- package/dist/scrape/parser/nodes/html.d.ts +110 -0
- package/dist/scrape/parser/nodes/html.js +978 -0
- package/dist/scrape/parser/nodes/node.d.ts +18 -0
- package/dist/scrape/parser/nodes/node.js +31 -0
- package/dist/scrape/parser/nodes/text.d.ts +14 -0
- package/dist/scrape/parser/nodes/text.js +30 -0
- package/dist/scrape/parser/nodes/type.d.ts +6 -0
- package/dist/scrape/parser/nodes/type.js +7 -0
- package/dist/scrape/parser/parse.d.ts +1 -0
- package/dist/scrape/parser/parse.js +1 -0
- package/dist/scrape/parser/valid.d.ts +2 -0
- package/dist/scrape/parser/valid.js +5 -0
- package/dist/scrape/parser/void-tag.d.ts +7 -0
- package/dist/scrape/parser/void-tag.js +43 -0
- package/dist/scrape/spider.d.ts +19 -0
- package/dist/scrape/spider.js +28 -3
- package/dist/scrape/types.d.ts +7 -0
- package/dist/seo/analyzer.d.ts +15 -5
- package/dist/seo/analyzer.js +636 -175
- package/dist/seo/formatter.d.ts +16 -0
- package/dist/seo/formatter.js +228 -0
- package/dist/seo/index.d.ts +2 -0
- package/dist/seo/index.js +1 -0
- package/dist/seo/keywords.d.ts +16 -0
- package/dist/seo/keywords.js +55 -0
- package/dist/seo/rules/accessibility.js +96 -57
- package/dist/seo/rules/ai-search.js +44 -31
- package/dist/seo/rules/analytics.d.ts +2 -0
- package/dist/seo/rules/analytics.js +306 -0
- package/dist/seo/rules/best-practices.js +21 -14
- package/dist/seo/rules/canonical.js +53 -32
- package/dist/seo/rules/content.js +317 -31
- package/dist/seo/rules/crawl.js +55 -40
- package/dist/seo/rules/cwv.js +21 -15
- package/dist/seo/rules/ecommerce.js +82 -22
- package/dist/seo/rules/i18n.js +75 -36
- package/dist/seo/rules/images.js +109 -30
- package/dist/seo/rules/index.js +2 -0
- package/dist/seo/rules/internal-linking.js +58 -39
- package/dist/seo/rules/links.js +79 -52
- package/dist/seo/rules/local.js +49 -25
- package/dist/seo/rules/meta.js +339 -81
- package/dist/seo/rules/mobile.js +112 -2
- package/dist/seo/rules/performance.js +434 -66
- package/dist/seo/rules/pwa.js +36 -39
- package/dist/seo/rules/readability.js +31 -22
- package/dist/seo/rules/redirects.js +21 -15
- package/dist/seo/rules/resources.js +59 -42
- package/dist/seo/rules/schema.js +333 -8
- package/dist/seo/rules/security.js +142 -80
- package/dist/seo/rules/social.js +277 -47
- package/dist/seo/rules/structural.js +87 -19
- package/dist/seo/rules/technical-advanced.js +30 -24
- package/dist/seo/rules/technical.js +243 -42
- package/dist/seo/rules/types.d.ts +53 -1
- package/dist/seo/seo-spider.d.ts +22 -0
- package/dist/seo/seo-spider.js +77 -13
- package/dist/seo/types.d.ts +8 -1
- package/dist/seo/validators/llms-txt.js +19 -0
- package/dist/seo/validators/rss.d.ts +11 -0
- package/dist/seo/validators/rss.js +93 -0
- package/dist/seo/validators/sitemap.js +36 -26
- package/dist/transport/curl.d.ts +4 -0
- package/dist/transport/curl.js +101 -0
- package/dist/transport/udp.js +0 -1
- package/dist/transport/undici.js +1 -2
- package/dist/transport/worker.d.ts +18 -0
- package/dist/transport/worker.js +278 -0
- package/dist/types/index.d.ts +4 -1
- package/dist/utils/binary-manager.d.ts +4 -0
- package/dist/utils/binary-manager.js +72 -0
- package/dist/utils/optional-require.d.ts +7 -8
- package/dist/utils/optional-require.js +2 -21
- package/dist/utils/upload.d.ts +6 -0
- package/dist/utils/upload.js +11 -0
- package/dist/utils/user-agent.js +2 -13
- package/dist/version.js +1 -1
- package/package.json +12 -6
- package/dist/browser/utils/optional-require.d.ts +0 -19
- package/dist/browser/utils/optional-require.js +0 -105
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import { createResult } from './types.js';
|
|
2
|
+
export const cwvRules = [
|
|
3
|
+
{
|
|
4
|
+
id: 'cwv-lcp-hero-image',
|
|
5
|
+
name: 'LCP Hero Image',
|
|
6
|
+
category: 'performance',
|
|
7
|
+
severity: 'warning',
|
|
8
|
+
description: 'Hero images should be optimized for fast LCP',
|
|
9
|
+
check: (ctx) => {
|
|
10
|
+
if (!ctx.lcpCandidate) {
|
|
11
|
+
return createResult({ id: 'cwv-lcp-hero-image', name: 'LCP Hero Image', category: 'performance', severity: 'warning' }, 'info', 'No LCP candidate detected', { recommendation: 'Ensure the page has a hero image or prominent content element' });
|
|
12
|
+
}
|
|
13
|
+
const issues = [];
|
|
14
|
+
if (ctx.lcpCandidate.loading === 'lazy') {
|
|
15
|
+
issues.push('LCP image has loading="lazy" (should be eager or omitted)');
|
|
16
|
+
}
|
|
17
|
+
if (!ctx.lcpCandidate.fetchpriority) {
|
|
18
|
+
issues.push('Missing fetchpriority="high" on LCP image');
|
|
19
|
+
}
|
|
20
|
+
if (!ctx.hasLcpPreload) {
|
|
21
|
+
issues.push('LCP image not preloaded');
|
|
22
|
+
}
|
|
23
|
+
if (issues.length > 0) {
|
|
24
|
+
return createResult({ id: 'cwv-lcp-hero-image', name: 'LCP Hero Image', category: 'performance', severity: 'warning' }, 'warn', `LCP optimization issues: ${issues.length} found`, {
|
|
25
|
+
recommendation: 'Optimize LCP image loading',
|
|
26
|
+
evidence: {
|
|
27
|
+
found: issues,
|
|
28
|
+
expected: 'LCP image with fetchpriority="high", no lazy loading, and preload link',
|
|
29
|
+
example: `<link rel="preload" as="image" href="hero.webp" fetchpriority="high">
|
|
30
|
+
<img src="hero.webp" fetchpriority="high" alt="Hero">`,
|
|
31
|
+
impact: 'LCP is a Core Web Vital - aim for < 2.5s',
|
|
32
|
+
learnMore: 'https://web.dev/lcp/',
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
return createResult({ id: 'cwv-lcp-hero-image', name: 'LCP Hero Image', category: 'performance', severity: 'warning' }, 'pass', 'LCP image appears optimized');
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
id: 'cwv-lcp-text-visible',
|
|
41
|
+
name: 'LCP Text Visibility',
|
|
42
|
+
category: 'performance',
|
|
43
|
+
severity: 'warning',
|
|
44
|
+
description: 'Text LCP elements should render immediately without font blocking',
|
|
45
|
+
check: (ctx) => {
|
|
46
|
+
if (!ctx.webFonts || ctx.webFonts.length === 0) {
|
|
47
|
+
return createResult({ id: 'cwv-lcp-text-visible', name: 'LCP Text Visibility', category: 'performance', severity: 'warning' }, 'info', 'No web fonts detected', { recommendation: 'No custom web fonts to analyze' });
|
|
48
|
+
}
|
|
49
|
+
const blockingFonts = ctx.webFonts.filter(f => !f.hasSwap && !f.hasOptional);
|
|
50
|
+
if (blockingFonts.length > 0) {
|
|
51
|
+
return createResult({ id: 'cwv-lcp-text-visible', name: 'LCP Text Visibility', category: 'performance', severity: 'warning' }, 'warn', `${blockingFonts.length} web font(s) may block text rendering`, {
|
|
52
|
+
recommendation: 'Use font-display: swap or optional for web fonts',
|
|
53
|
+
evidence: {
|
|
54
|
+
found: blockingFonts.map(f => f.family).filter(Boolean).slice(0, 3),
|
|
55
|
+
expected: 'font-display: swap or font-display: optional',
|
|
56
|
+
example: '@font-face { font-display: swap; }',
|
|
57
|
+
impact: 'Blocking fonts delay LCP for text elements',
|
|
58
|
+
learnMore: 'https://web.dev/font-display/',
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
return createResult({ id: 'cwv-lcp-text-visible', name: 'LCP Text Visibility', category: 'performance', severity: 'warning' }, 'pass', 'Web fonts use non-blocking display');
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
id: 'cwv-render-blocking-css',
|
|
67
|
+
name: 'Render-Blocking CSS',
|
|
68
|
+
category: 'performance',
|
|
69
|
+
severity: 'warning',
|
|
70
|
+
description: 'Minimize render-blocking CSS for faster LCP',
|
|
71
|
+
check: (ctx) => {
|
|
72
|
+
if (ctx.renderBlockingStylesheets === undefined) {
|
|
73
|
+
return createResult({ id: 'cwv-render-blocking-css', name: 'Render-Blocking CSS', category: 'performance', severity: 'warning' }, 'info', 'Unable to check render-blocking CSS (data unavailable)', { recommendation: 'Ensure CSS resource analysis completed' });
|
|
74
|
+
}
|
|
75
|
+
if (ctx.renderBlockingStylesheets > 3) {
|
|
76
|
+
return createResult({ id: 'cwv-render-blocking-css', name: 'Render-Blocking CSS', category: 'performance', severity: 'warning' }, 'warn', `${ctx.renderBlockingStylesheets} render-blocking stylesheets`, {
|
|
77
|
+
recommendation: 'Reduce render-blocking CSS or use critical CSS inline',
|
|
78
|
+
evidence: {
|
|
79
|
+
found: ctx.renderBlockingStylesheets,
|
|
80
|
+
expected: '1-3 stylesheets or inline critical CSS',
|
|
81
|
+
example: '<style>/* critical CSS */</style>\n<link rel="stylesheet" href="main.css" media="print" onload="this.media=\'all\'">',
|
|
82
|
+
impact: 'Each blocking stylesheet delays first paint',
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
return createResult({ id: 'cwv-render-blocking-css', name: 'Render-Blocking CSS', category: 'performance', severity: 'warning' }, 'pass', `${ctx.renderBlockingStylesheets} render-blocking stylesheet(s)`);
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
id: 'cwv-render-blocking-js',
|
|
91
|
+
name: 'Render-Blocking JavaScript',
|
|
92
|
+
category: 'performance',
|
|
93
|
+
severity: 'warning',
|
|
94
|
+
description: 'Scripts in head should use async or defer',
|
|
95
|
+
check: (ctx) => {
|
|
96
|
+
if (ctx.renderBlockingScripts === undefined) {
|
|
97
|
+
return createResult({ id: 'cwv-render-blocking-js', name: 'Render-Blocking JavaScript', category: 'performance', severity: 'warning' }, 'info', 'Unable to check render-blocking scripts (data unavailable)', { recommendation: 'Ensure script analysis completed' });
|
|
98
|
+
}
|
|
99
|
+
if (ctx.renderBlockingScripts > 0) {
|
|
100
|
+
return createResult({ id: 'cwv-render-blocking-js', name: 'Render-Blocking JavaScript', category: 'performance', severity: 'warning' }, 'warn', `${ctx.renderBlockingScripts} render-blocking script(s) in <head>`, {
|
|
101
|
+
recommendation: 'Add async or defer to scripts, or move to end of body',
|
|
102
|
+
evidence: {
|
|
103
|
+
found: ctx.renderBlockingScripts,
|
|
104
|
+
expected: 'Scripts with async, defer, or type="module"',
|
|
105
|
+
example: '<script src="app.js" defer></script>',
|
|
106
|
+
impact: 'Blocking scripts delay HTML parsing and LCP',
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
return createResult({ id: 'cwv-render-blocking-js', name: 'Render-Blocking JavaScript', category: 'performance', severity: 'warning' }, 'pass', 'No render-blocking scripts in head');
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
id: 'cwv-cls-image-dimensions',
|
|
115
|
+
name: 'Image Dimensions',
|
|
116
|
+
category: 'performance',
|
|
117
|
+
severity: 'warning',
|
|
118
|
+
description: 'Images should have explicit width and height to prevent layout shift',
|
|
119
|
+
check: (ctx) => {
|
|
120
|
+
if (ctx.imagesMissingDimensions === undefined) {
|
|
121
|
+
return createResult({ id: 'cwv-cls-image-dimensions', name: 'Image Dimensions', category: 'performance', severity: 'warning' }, 'info', 'Unable to check image dimensions (data unavailable)', { recommendation: 'Ensure image analysis completed' });
|
|
122
|
+
}
|
|
123
|
+
if (ctx.imagesMissingDimensions > 0) {
|
|
124
|
+
const total = ctx.totalImages || 0;
|
|
125
|
+
const percent = total > 0 ? Math.round((ctx.imagesMissingDimensions / total) * 100) : 0;
|
|
126
|
+
return createResult({ id: 'cwv-cls-image-dimensions', name: 'Image Dimensions', category: 'performance', severity: 'warning' }, 'warn', `${ctx.imagesMissingDimensions} image(s) missing width/height (${percent}%)`, {
|
|
127
|
+
recommendation: 'Add explicit width and height attributes to all images',
|
|
128
|
+
evidence: {
|
|
129
|
+
found: `${ctx.imagesMissingDimensions} of ${total} images`,
|
|
130
|
+
expected: 'width and height on all <img> elements',
|
|
131
|
+
example: '<img src="photo.jpg" width="800" height="600" alt="...">',
|
|
132
|
+
impact: 'Missing dimensions cause layout shift when images load',
|
|
133
|
+
learnMore: 'https://web.dev/cls/',
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
return createResult({ id: 'cwv-cls-image-dimensions', name: 'Image Dimensions', category: 'performance', severity: 'warning' }, 'pass', 'All images have dimensions');
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
id: 'cwv-cls-aspect-ratio',
|
|
142
|
+
name: 'Aspect Ratio CSS',
|
|
143
|
+
category: 'performance',
|
|
144
|
+
severity: 'info',
|
|
145
|
+
description: 'Use CSS aspect-ratio for responsive media containers',
|
|
146
|
+
check: (ctx) => {
|
|
147
|
+
if (!ctx.hasAspectRatioCss && ctx.hasResponsiveImages) {
|
|
148
|
+
return createResult({ id: 'cwv-cls-aspect-ratio', name: 'Aspect Ratio CSS', category: 'performance', severity: 'info' }, 'info', 'Consider using CSS aspect-ratio for media containers', {
|
|
149
|
+
recommendation: 'Use aspect-ratio CSS property for responsive containers',
|
|
150
|
+
evidence: {
|
|
151
|
+
example: '.video-container { aspect-ratio: 16 / 9; }',
|
|
152
|
+
impact: 'Reserves space before media loads, preventing CLS',
|
|
153
|
+
learnMore: 'https://web.dev/aspect-ratio/',
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
return createResult({ id: 'cwv-cls-aspect-ratio', name: 'Aspect Ratio CSS', category: 'performance', severity: 'info' }, 'pass', 'Aspect ratio handling is appropriate');
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
id: 'cwv-cls-font-fallback',
|
|
162
|
+
name: 'Font Fallback Metrics',
|
|
163
|
+
category: 'performance',
|
|
164
|
+
severity: 'info',
|
|
165
|
+
description: 'Web fonts should have size-matched fallbacks',
|
|
166
|
+
check: (ctx) => {
|
|
167
|
+
if (!ctx.webFonts || ctx.webFonts.length === 0) {
|
|
168
|
+
return createResult({ id: 'cwv-cls-font-fallback', name: 'Font Fallback Metrics', category: 'performance', severity: 'info' }, 'info', 'No web fonts to check', { recommendation: 'No custom web fonts detected' });
|
|
169
|
+
}
|
|
170
|
+
const fontsWithoutMetrics = ctx.webFonts.filter(f => !f.hasSizeAdjust && !f.hasAscentOverride);
|
|
171
|
+
if (fontsWithoutMetrics.length > 0 && ctx.webFonts.length > 0) {
|
|
172
|
+
return createResult({ id: 'cwv-cls-font-fallback', name: 'Font Fallback Metrics', category: 'performance', severity: 'info' }, 'info', 'Web fonts may cause layout shift during swap', {
|
|
173
|
+
recommendation: 'Use size-adjust or metric overrides for fallback fonts',
|
|
174
|
+
evidence: {
|
|
175
|
+
example: `@font-face {
|
|
176
|
+
font-family: 'Custom Font';
|
|
177
|
+
src: url('custom.woff2') format('woff2');
|
|
178
|
+
font-display: swap;
|
|
179
|
+
size-adjust: 92%; /* Match fallback metrics */
|
|
180
|
+
}`,
|
|
181
|
+
impact: 'Font swapping can cause visible layout shift',
|
|
182
|
+
learnMore: 'https://web.dev/css-size-adjust/',
|
|
183
|
+
},
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
return createResult({ id: 'cwv-cls-font-fallback', name: 'Font Fallback Metrics', category: 'performance', severity: 'info' }, 'pass', 'Font fallback metrics configured');
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
id: 'cwv-resource-hints',
|
|
191
|
+
name: 'Resource Hints',
|
|
192
|
+
category: 'performance',
|
|
193
|
+
severity: 'info',
|
|
194
|
+
description: 'Use resource hints to speed up critical resources',
|
|
195
|
+
check: (ctx) => {
|
|
196
|
+
const hints = [];
|
|
197
|
+
if (!ctx.hasPreconnect && ctx.externalOrigins && ctx.externalOrigins > 2) {
|
|
198
|
+
hints.push('preconnect for critical third-party origins');
|
|
199
|
+
}
|
|
200
|
+
if (!ctx.hasDnsPrefetch && ctx.externalOrigins && ctx.externalOrigins > 3) {
|
|
201
|
+
hints.push('dns-prefetch for secondary origins');
|
|
202
|
+
}
|
|
203
|
+
if (!ctx.hasPreload && ctx.hasCriticalResources) {
|
|
204
|
+
hints.push('preload for critical CSS/fonts/images');
|
|
205
|
+
}
|
|
206
|
+
if (hints.length > 0) {
|
|
207
|
+
return createResult({ id: 'cwv-resource-hints', name: 'Resource Hints', category: 'performance', severity: 'info' }, 'info', `Consider adding: ${hints.join(', ')}`, {
|
|
208
|
+
recommendation: 'Add resource hints in <head> for faster loading',
|
|
209
|
+
evidence: {
|
|
210
|
+
expected: hints,
|
|
211
|
+
example: `<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
212
|
+
<link rel="dns-prefetch" href="https://analytics.example.com">
|
|
213
|
+
<link rel="preload" as="font" href="/fonts/main.woff2" crossorigin>`,
|
|
214
|
+
learnMore: 'https://web.dev/preconnect-and-dns-prefetch/',
|
|
215
|
+
},
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
return createResult({ id: 'cwv-resource-hints', name: 'Resource Hints', category: 'performance', severity: 'info' }, 'pass', 'Resource hints configured');
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
id: 'cwv-critical-css',
|
|
223
|
+
name: 'Critical CSS',
|
|
224
|
+
category: 'performance',
|
|
225
|
+
severity: 'info',
|
|
226
|
+
description: 'Inline critical CSS for above-the-fold content',
|
|
227
|
+
check: (ctx) => {
|
|
228
|
+
if (ctx.hasInlineCriticalCss) {
|
|
229
|
+
return createResult({ id: 'cwv-critical-css', name: 'Critical CSS', category: 'performance', severity: 'info' }, 'pass', 'Inline critical CSS detected');
|
|
230
|
+
}
|
|
231
|
+
if (ctx.renderBlockingStylesheets && ctx.renderBlockingStylesheets > 0) {
|
|
232
|
+
return createResult({ id: 'cwv-critical-css', name: 'Critical CSS', category: 'performance', severity: 'info' }, 'info', 'Consider inlining critical CSS', {
|
|
233
|
+
recommendation: 'Inline critical CSS in <head> and defer non-critical styles',
|
|
234
|
+
evidence: {
|
|
235
|
+
example: `<style>
|
|
236
|
+
/* Critical above-the-fold styles */
|
|
237
|
+
body { margin: 0; font-family: system-ui; }
|
|
238
|
+
</style>
|
|
239
|
+
<link rel="stylesheet" href="main.css" media="print" onload="this.media='all'">`,
|
|
240
|
+
impact: 'Critical CSS eliminates render-blocking stylesheets',
|
|
241
|
+
learnMore: 'https://web.dev/extract-critical-css/',
|
|
242
|
+
},
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
return createResult({ id: 'cwv-critical-css', name: 'Critical CSS', category: 'performance', severity: 'info' }, 'info', 'No render-blocking stylesheets to optimize', { recommendation: 'Page has no render-blocking CSS' });
|
|
246
|
+
},
|
|
247
|
+
},
|
|
248
|
+
];
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
import { createResult } from './types.js';
|
|
2
|
+
export const ecommerceRules = [
|
|
3
|
+
{
|
|
4
|
+
id: 'ecommerce-product-schema',
|
|
5
|
+
name: 'Product Schema',
|
|
6
|
+
category: 'structured-data',
|
|
7
|
+
severity: 'warning',
|
|
8
|
+
description: 'Product pages should have Product schema for rich snippets',
|
|
9
|
+
check: (ctx) => {
|
|
10
|
+
if (!ctx.isProductPage) {
|
|
11
|
+
return createResult({ id: 'ecommerce-product-schema', name: 'Product Schema', category: 'structured-data', severity: 'warning' }, 'info', 'Not a product page', {
|
|
12
|
+
recommendation: 'Add Product schema if this is a product page',
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
if (!ctx.jsonLdTypes) {
|
|
16
|
+
return createResult({ id: 'ecommerce-product-schema', name: 'Product Schema', category: 'structured-data', severity: 'warning' }, 'warn', 'Product page has no JSON-LD structured data', {
|
|
17
|
+
recommendation: 'Add Product structured data for rich snippets',
|
|
18
|
+
evidence: {
|
|
19
|
+
found: 'No JSON-LD structured data',
|
|
20
|
+
expected: 'Product schema with name, image, price, availability',
|
|
21
|
+
impact: 'Missing product schema prevents rich snippets in search results',
|
|
22
|
+
example: `<script type="application/ld+json">
|
|
23
|
+
{
|
|
24
|
+
"@context": "https://schema.org",
|
|
25
|
+
"@type": "Product",
|
|
26
|
+
"name": "Product Name",
|
|
27
|
+
"image": "https://example.com/image.jpg",
|
|
28
|
+
"offers": {
|
|
29
|
+
"@type": "Offer",
|
|
30
|
+
"price": "99.99",
|
|
31
|
+
"priceCurrency": "USD"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
</script>`,
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
const hasProduct = ctx.jsonLdTypes.includes('Product');
|
|
39
|
+
if (!hasProduct) {
|
|
40
|
+
return createResult({ id: 'ecommerce-product-schema', name: 'Product Schema', category: 'structured-data', severity: 'warning' }, 'warn', 'Product page missing Product schema', {
|
|
41
|
+
recommendation: 'Add Product structured data for rich snippets in search results',
|
|
42
|
+
evidence: {
|
|
43
|
+
expected: 'Product schema with name, image, price, availability',
|
|
44
|
+
example: `{
|
|
45
|
+
"@type": "Product",
|
|
46
|
+
"name": "Product Name",
|
|
47
|
+
"image": "https://example.com/image.jpg",
|
|
48
|
+
"offers": {
|
|
49
|
+
"@type": "Offer",
|
|
50
|
+
"price": "99.99",
|
|
51
|
+
"priceCurrency": "USD",
|
|
52
|
+
"availability": "https://schema.org/InStock"
|
|
53
|
+
}
|
|
54
|
+
}`,
|
|
55
|
+
learnMore: 'https://developers.google.com/search/docs/appearance/structured-data/product',
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
return createResult({ id: 'ecommerce-product-schema', name: 'Product Schema', category: 'structured-data', severity: 'warning' }, 'pass', 'Product schema found');
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
id: 'ecommerce-product-price',
|
|
64
|
+
name: 'Product Price',
|
|
65
|
+
category: 'structured-data',
|
|
66
|
+
severity: 'warning',
|
|
67
|
+
description: 'Product schema should include price information',
|
|
68
|
+
check: (ctx) => {
|
|
69
|
+
if (!ctx.productSchema) {
|
|
70
|
+
return createResult({ id: 'ecommerce-product-price', name: 'Product Price', category: 'structured-data', severity: 'warning' }, 'info', 'Not applicable (no Product schema)', {
|
|
71
|
+
recommendation: 'Add Product schema first, then include price information',
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
const hasPrice = ctx.productSchema.offers?.price !== undefined ||
|
|
75
|
+
ctx.productSchema.offers?.lowPrice !== undefined;
|
|
76
|
+
const hasCurrency = ctx.productSchema.offers?.priceCurrency !== undefined;
|
|
77
|
+
if (!hasPrice) {
|
|
78
|
+
return createResult({ id: 'ecommerce-product-price', name: 'Product Price', category: 'structured-data', severity: 'warning' }, 'warn', 'Product schema missing price', {
|
|
79
|
+
recommendation: 'Add price to Product offers for price display in search results',
|
|
80
|
+
evidence: {
|
|
81
|
+
found: 'Product offers without price',
|
|
82
|
+
expected: 'offers.price or offers.lowPrice with priceCurrency',
|
|
83
|
+
impact: 'Products without price may not show in Google Shopping results',
|
|
84
|
+
example: '"offers": {\n "@type": "Offer",\n "price": "99.99",\n "priceCurrency": "USD"\n}',
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
if (!hasCurrency) {
|
|
89
|
+
return createResult({ id: 'ecommerce-product-price', name: 'Product Price', category: 'structured-data', severity: 'warning' }, 'warn', 'Product schema missing currency', {
|
|
90
|
+
recommendation: 'Add priceCurrency (e.g., USD, EUR, BRL) to offers',
|
|
91
|
+
evidence: {
|
|
92
|
+
found: `price: ${ctx.productSchema.offers?.price}`,
|
|
93
|
+
expected: 'priceCurrency: "USD" or similar ISO 4217 code',
|
|
94
|
+
impact: 'Currency is required for Google Shopping and price display in search results',
|
|
95
|
+
example: '"priceCurrency": "USD"',
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
return createResult({ id: 'ecommerce-product-price', name: 'Product Price', category: 'structured-data', severity: 'warning' }, 'pass', `Price: ${ctx.productSchema.offers?.priceCurrency} ${ctx.productSchema.offers?.price || ctx.productSchema.offers?.lowPrice}`);
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
id: 'ecommerce-product-availability',
|
|
104
|
+
name: 'Product Availability',
|
|
105
|
+
category: 'structured-data',
|
|
106
|
+
severity: 'info',
|
|
107
|
+
description: 'Product schema should include availability status',
|
|
108
|
+
check: (ctx) => {
|
|
109
|
+
if (!ctx.productSchema?.offers) {
|
|
110
|
+
return createResult({ id: 'ecommerce-product-availability', name: 'Product Availability', category: 'structured-data', severity: 'info' }, 'info', 'Not applicable (no Product offers schema)', {
|
|
111
|
+
recommendation: 'Add Product schema with offers to include availability',
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
const availability = ctx.productSchema.offers.availability;
|
|
115
|
+
if (!availability) {
|
|
116
|
+
return createResult({ id: 'ecommerce-product-availability', name: 'Product Availability', category: 'structured-data', severity: 'info' }, 'info', 'Product schema missing availability', {
|
|
117
|
+
recommendation: 'Add availability to help users know if product is in stock',
|
|
118
|
+
evidence: {
|
|
119
|
+
expected: 'availability: "https://schema.org/InStock" or similar',
|
|
120
|
+
example: 'InStock, OutOfStock, PreOrder, BackOrder, Discontinued',
|
|
121
|
+
learnMore: 'https://schema.org/ItemAvailability',
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
const availType = availability.replace('https://schema.org/', '').replace('http://schema.org/', '');
|
|
126
|
+
return createResult({ id: 'ecommerce-product-availability', name: 'Product Availability', category: 'structured-data', severity: 'info' }, 'pass', `Availability: ${availType}`);
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
id: 'ecommerce-product-image',
|
|
131
|
+
name: 'Product Image',
|
|
132
|
+
category: 'structured-data',
|
|
133
|
+
severity: 'warning',
|
|
134
|
+
description: 'Product schema should include high-quality images',
|
|
135
|
+
check: (ctx) => {
|
|
136
|
+
if (!ctx.productSchema) {
|
|
137
|
+
return createResult({ id: 'ecommerce-product-image', name: 'Product Image', category: 'structured-data', severity: 'warning' }, 'info', 'Not applicable (no Product schema)', {
|
|
138
|
+
recommendation: 'Add Product schema first, then include images',
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
const hasImage = ctx.productSchema.image !== undefined;
|
|
142
|
+
if (!hasImage) {
|
|
143
|
+
return createResult({ id: 'ecommerce-product-image', name: 'Product Image', category: 'structured-data', severity: 'warning' }, 'warn', 'Product schema missing image', {
|
|
144
|
+
recommendation: 'Add product images for visual search results',
|
|
145
|
+
evidence: {
|
|
146
|
+
found: 'Product schema without image',
|
|
147
|
+
expected: 'At least one high-quality product image',
|
|
148
|
+
impact: 'Products without images are less likely to appear in image search and shopping results',
|
|
149
|
+
example: '"image": "https://example.com/product-image.jpg"',
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
const imageCount = Array.isArray(ctx.productSchema.image)
|
|
154
|
+
? ctx.productSchema.image.length
|
|
155
|
+
: 1;
|
|
156
|
+
return createResult({ id: 'ecommerce-product-image', name: 'Product Image', category: 'structured-data', severity: 'warning' }, 'pass', `${imageCount} product image(s) in schema`);
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
id: 'ecommerce-product-reviews',
|
|
161
|
+
name: 'Product Reviews',
|
|
162
|
+
category: 'structured-data',
|
|
163
|
+
severity: 'info',
|
|
164
|
+
description: 'Product schema can include aggregate ratings for star snippets',
|
|
165
|
+
check: (ctx) => {
|
|
166
|
+
if (!ctx.productSchema) {
|
|
167
|
+
return createResult({ id: 'ecommerce-product-reviews', name: 'Product Reviews', category: 'structured-data', severity: 'info' }, 'info', 'Not applicable (no Product schema)', {
|
|
168
|
+
recommendation: 'Add Product schema first, then include reviews/ratings',
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
const hasRating = ctx.productSchema.aggregateRating !== undefined;
|
|
172
|
+
const hasReviews = ctx.productSchema.review !== undefined;
|
|
173
|
+
if (!hasRating && !hasReviews) {
|
|
174
|
+
return createResult({ id: 'ecommerce-product-reviews', name: 'Product Reviews', category: 'structured-data', severity: 'info' }, 'info', 'Product schema has no reviews or ratings', {
|
|
175
|
+
recommendation: 'Add aggregateRating for star ratings in search results',
|
|
176
|
+
evidence: {
|
|
177
|
+
example: `"aggregateRating": {
|
|
178
|
+
"@type": "AggregateRating",
|
|
179
|
+
"ratingValue": "4.5",
|
|
180
|
+
"reviewCount": "42"
|
|
181
|
+
}`,
|
|
182
|
+
impact: 'Star ratings in search results can improve click-through rate by 20-30%',
|
|
183
|
+
},
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
if (hasRating && ctx.productSchema.aggregateRating) {
|
|
187
|
+
const rating = ctx.productSchema.aggregateRating;
|
|
188
|
+
return createResult({ id: 'ecommerce-product-reviews', name: 'Product Reviews', category: 'structured-data', severity: 'info' }, 'pass', `Rating: ${rating.ratingValue ?? '?'}/5 (${rating.reviewCount ?? rating.ratingCount ?? '?'} reviews)`);
|
|
189
|
+
}
|
|
190
|
+
return createResult({ id: 'ecommerce-product-reviews', name: 'Product Reviews', category: 'structured-data', severity: 'info' }, 'pass', 'Product has review data');
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
id: 'ecommerce-product-brand',
|
|
195
|
+
name: 'Product Brand',
|
|
196
|
+
category: 'structured-data',
|
|
197
|
+
severity: 'info',
|
|
198
|
+
description: 'Product schema should include brand information',
|
|
199
|
+
check: (ctx) => {
|
|
200
|
+
if (!ctx.productSchema) {
|
|
201
|
+
return createResult({ id: 'ecommerce-product-brand', name: 'Product Brand', category: 'structured-data', severity: 'info' }, 'info', 'Not applicable (no Product schema)', {
|
|
202
|
+
recommendation: 'Add Product schema first, then include brand',
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
const hasBrand = ctx.productSchema.brand !== undefined;
|
|
206
|
+
if (!hasBrand) {
|
|
207
|
+
return createResult({ id: 'ecommerce-product-brand', name: 'Product Brand', category: 'structured-data', severity: 'info' }, 'info', 'Product schema missing brand', {
|
|
208
|
+
recommendation: 'Add brand for better product identification',
|
|
209
|
+
evidence: {
|
|
210
|
+
example: `"brand": { "@type": "Brand", "name": "Brand Name" }`,
|
|
211
|
+
impact: 'Brand helps Google understand product context for shopping queries',
|
|
212
|
+
},
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
const brandName = typeof ctx.productSchema.brand === 'string'
|
|
216
|
+
? ctx.productSchema.brand
|
|
217
|
+
: ctx.productSchema.brand?.name;
|
|
218
|
+
return createResult({ id: 'ecommerce-product-brand', name: 'Product Brand', category: 'structured-data', severity: 'info' }, 'pass', `Brand: ${brandName || 'specified'}`);
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
id: 'ecommerce-product-sku',
|
|
223
|
+
name: 'Product Identifiers',
|
|
224
|
+
category: 'structured-data',
|
|
225
|
+
severity: 'info',
|
|
226
|
+
description: 'Product schema should include unique identifiers (SKU, GTIN, MPN)',
|
|
227
|
+
check: (ctx) => {
|
|
228
|
+
if (!ctx.productSchema) {
|
|
229
|
+
return createResult({ id: 'ecommerce-product-sku', name: 'Product Identifiers', category: 'structured-data', severity: 'info' }, 'info', 'Not applicable (no Product schema)', {
|
|
230
|
+
recommendation: 'Add Product schema first, then include identifiers',
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
const hasSku = ctx.productSchema.sku !== undefined;
|
|
234
|
+
const hasGtin = ctx.productSchema.gtin !== undefined ||
|
|
235
|
+
ctx.productSchema.gtin13 !== undefined ||
|
|
236
|
+
ctx.productSchema.gtin14 !== undefined ||
|
|
237
|
+
ctx.productSchema.gtin8 !== undefined;
|
|
238
|
+
const hasMpn = ctx.productSchema.mpn !== undefined;
|
|
239
|
+
const identifiers = [];
|
|
240
|
+
if (hasSku)
|
|
241
|
+
identifiers.push('SKU');
|
|
242
|
+
if (hasGtin)
|
|
243
|
+
identifiers.push('GTIN');
|
|
244
|
+
if (hasMpn)
|
|
245
|
+
identifiers.push('MPN');
|
|
246
|
+
if (identifiers.length === 0) {
|
|
247
|
+
return createResult({ id: 'ecommerce-product-sku', name: 'Product Identifiers', category: 'structured-data', severity: 'info' }, 'info', 'Product schema missing identifiers', {
|
|
248
|
+
recommendation: 'Add SKU, GTIN, or MPN for better product matching',
|
|
249
|
+
evidence: {
|
|
250
|
+
expected: 'At least one of: sku, gtin, gtin13, gtin14, gtin8, mpn',
|
|
251
|
+
impact: 'Product identifiers help Google match products across retailers',
|
|
252
|
+
learnMore: 'https://support.google.com/merchants/answer/6324461',
|
|
253
|
+
},
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
return createResult({ id: 'ecommerce-product-sku', name: 'Product Identifiers', category: 'structured-data', severity: 'info' }, 'pass', `Identifiers: ${identifiers.join(', ')}`);
|
|
257
|
+
},
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
id: 'ecommerce-offer-valid-dates',
|
|
261
|
+
name: 'Offer Valid Dates',
|
|
262
|
+
category: 'structured-data',
|
|
263
|
+
severity: 'info',
|
|
264
|
+
description: 'Time-sensitive offers should have valid date ranges',
|
|
265
|
+
check: (ctx) => {
|
|
266
|
+
if (!ctx.productSchema?.offers) {
|
|
267
|
+
return createResult({ id: 'ecommerce-offer-valid-dates', name: 'Offer Valid Dates', category: 'structured-data', severity: 'info' }, 'info', 'Not applicable (no Product offers schema)', {
|
|
268
|
+
recommendation: 'Add Product schema with offers to enable date validation',
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
const offers = ctx.productSchema.offers;
|
|
272
|
+
const priceValidUntil = offers.priceValidUntil;
|
|
273
|
+
const validThrough = offers.validThrough;
|
|
274
|
+
if (priceValidUntil) {
|
|
275
|
+
const endDate = new Date(priceValidUntil);
|
|
276
|
+
const now = new Date();
|
|
277
|
+
if (endDate < now) {
|
|
278
|
+
return createResult({ id: 'ecommerce-offer-valid-dates', name: 'Offer Valid Dates', category: 'structured-data', severity: 'info' }, 'warn', 'Offer priceValidUntil date has passed', {
|
|
279
|
+
recommendation: 'Update or remove expired priceValidUntil date',
|
|
280
|
+
evidence: {
|
|
281
|
+
found: priceValidUntil,
|
|
282
|
+
expected: 'Future date or no priceValidUntil',
|
|
283
|
+
impact: 'Expired dates may cause Google to distrust your pricing data',
|
|
284
|
+
example: `"priceValidUntil": "${new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString().split('T')[0]}"`,
|
|
285
|
+
},
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
if (validThrough) {
|
|
290
|
+
const endDate = new Date(validThrough);
|
|
291
|
+
const now = new Date();
|
|
292
|
+
if (endDate < now) {
|
|
293
|
+
return createResult({ id: 'ecommerce-offer-valid-dates', name: 'Offer Valid Dates', category: 'structured-data', severity: 'info' }, 'warn', 'Offer validThrough date has passed', {
|
|
294
|
+
recommendation: 'Update or remove expired validThrough date',
|
|
295
|
+
evidence: {
|
|
296
|
+
found: validThrough,
|
|
297
|
+
expected: 'Future date or no validThrough',
|
|
298
|
+
impact: 'Expired validity dates may prevent offer from showing in search results',
|
|
299
|
+
example: `"validThrough": "${new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString().split('T')[0]}"`,
|
|
300
|
+
},
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
if (priceValidUntil || validThrough) {
|
|
305
|
+
return createResult({ id: 'ecommerce-offer-valid-dates', name: 'Offer Valid Dates', category: 'structured-data', severity: 'info' }, 'pass', 'Offer dates are valid');
|
|
306
|
+
}
|
|
307
|
+
return createResult({ id: 'ecommerce-offer-valid-dates', name: 'Offer Valid Dates', category: 'structured-data', severity: 'info' }, 'info', 'No offer date constraints specified', {
|
|
308
|
+
recommendation: 'Consider adding priceValidUntil for time-limited offers',
|
|
309
|
+
});
|
|
310
|
+
},
|
|
311
|
+
},
|
|
312
|
+
];
|