recker 1.0.43 → 1.0.44
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +47 -0
- package/dist/bin/recker-linux-x64 +0 -0
- package/dist/bin/recker-macos-x64 +0 -0
- package/dist/bin/recker-win-x64.exe +0 -0
- package/dist/bin/rek.cjs +85152 -100207
- package/dist/browser/ai/adaptive-timeout.d.ts +50 -0
- package/dist/browser/ai/adaptive-timeout.js +208 -0
- package/dist/browser/ai/client.d.ts +22 -0
- package/dist/browser/ai/client.js +294 -0
- package/dist/browser/ai/index.d.ts +14 -0
- package/dist/browser/ai/index.js +11 -0
- package/dist/browser/ai/providers/anthropic.d.ts +63 -0
- package/dist/browser/ai/providers/anthropic.js +370 -0
- package/dist/browser/ai/providers/base.d.ts +48 -0
- package/dist/browser/ai/providers/base.js +150 -0
- package/dist/browser/ai/providers/google.d.ts +59 -0
- package/dist/browser/ai/providers/google.js +305 -0
- package/dist/browser/ai/providers/ollama.d.ts +44 -0
- package/dist/browser/ai/providers/ollama.js +240 -0
- package/dist/browser/ai/providers/openai.d.ts +64 -0
- package/dist/browser/ai/providers/openai.js +298 -0
- package/dist/browser/ai/rate-limiter.d.ts +43 -0
- package/dist/browser/ai/rate-limiter.js +215 -0
- package/dist/browser/ai/vector/index.d.ts +2 -0
- package/dist/browser/ai/vector/index.js +2 -0
- package/dist/browser/ai/vector/similarity.d.ts +2 -0
- package/dist/browser/ai/vector/similarity.js +27 -0
- package/dist/browser/ai/vector/store.d.ts +27 -0
- package/dist/browser/ai/vector/store.js +82 -0
- package/dist/browser/browser/cache.d.ts +2 -40
- package/dist/browser/browser/cache.js +2 -199
- package/dist/browser/browser/index.d.ts +8 -0
- package/dist/browser/browser/index.js +8 -0
- package/dist/browser/browser/recker.d.ts +8 -1
- package/dist/browser/browser/recker.js +8 -2
- package/dist/browser/cache/indexed-db.d.ts +10 -0
- package/dist/browser/cache/indexed-db.js +88 -0
- package/dist/browser/cache/service-worker-cache.d.ts +18 -0
- package/dist/browser/cache/service-worker-cache.js +103 -0
- package/dist/browser/cache.d.ts +2 -40
- package/dist/browser/cache.js +2 -199
- package/dist/browser/constants/user-agents.d.ts +7 -0
- package/dist/browser/constants/user-agents.js +7 -0
- package/dist/browser/core/client.d.ts +2 -0
- package/dist/browser/core/client.js +19 -1
- package/dist/browser/index.d.ts +8 -0
- package/dist/browser/index.js +8 -0
- package/dist/browser/plugins/har-recorder.d.ts +40 -0
- package/dist/browser/plugins/har-recorder.js +120 -0
- package/dist/browser/plugins/network-simulation.d.ts +7 -0
- package/dist/browser/plugins/network-simulation.js +13 -0
- package/dist/browser/presets/android.d.ts +2 -0
- package/dist/browser/presets/android.js +16 -0
- package/dist/browser/presets/anthropic.d.ts +8 -0
- package/dist/browser/presets/anthropic.js +27 -0
- package/dist/browser/presets/aws.d.ts +19 -0
- package/dist/browser/presets/aws.js +68 -0
- package/dist/browser/presets/azure-openai.d.ts +10 -0
- package/dist/browser/presets/azure-openai.js +35 -0
- package/dist/browser/presets/azure.d.ts +41 -0
- package/dist/browser/presets/azure.js +104 -0
- package/dist/browser/presets/chaturbate.d.ts +2 -0
- package/dist/browser/presets/chaturbate.js +17 -0
- package/dist/browser/presets/cloudflare.d.ts +12 -0
- package/dist/browser/presets/cloudflare.js +39 -0
- package/dist/browser/presets/cohere.d.ts +7 -0
- package/dist/browser/presets/cohere.js +22 -0
- package/dist/browser/presets/deepseek.d.ts +7 -0
- package/dist/browser/presets/deepseek.js +22 -0
- package/dist/browser/presets/digitalocean.d.ts +5 -0
- package/dist/browser/presets/digitalocean.js +16 -0
- package/dist/browser/presets/discord.d.ts +6 -0
- package/dist/browser/presets/discord.js +17 -0
- package/dist/browser/presets/elevenlabs.d.ts +6 -0
- package/dist/browser/presets/elevenlabs.js +20 -0
- package/dist/browser/presets/enhancers.d.ts +20 -0
- package/dist/browser/presets/enhancers.js +85 -0
- package/dist/browser/presets/fireworks.d.ts +7 -0
- package/dist/browser/presets/fireworks.js +22 -0
- package/dist/browser/presets/gcp.d.ts +34 -0
- package/dist/browser/presets/gcp.js +91 -0
- package/dist/browser/presets/gemini.d.ts +7 -0
- package/dist/browser/presets/gemini.js +23 -0
- package/dist/browser/presets/github.d.ts +6 -0
- package/dist/browser/presets/github.js +17 -0
- package/dist/browser/presets/gitlab.d.ts +6 -0
- package/dist/browser/presets/gitlab.js +16 -0
- package/dist/browser/presets/groq.d.ts +7 -0
- package/dist/browser/presets/groq.js +22 -0
- package/dist/browser/presets/hubspot.d.ts +9 -0
- package/dist/browser/presets/hubspot.js +28 -0
- package/dist/browser/presets/huggingface.d.ts +7 -0
- package/dist/browser/presets/huggingface.js +23 -0
- package/dist/browser/presets/index.d.ts +47 -0
- package/dist/browser/presets/index.js +47 -0
- package/dist/browser/presets/ios.d.ts +2 -0
- package/dist/browser/presets/ios.js +13 -0
- package/dist/browser/presets/linear.d.ts +5 -0
- package/dist/browser/presets/linear.js +16 -0
- package/dist/browser/presets/mailgun.d.ts +7 -0
- package/dist/browser/presets/mailgun.js +20 -0
- package/dist/browser/presets/meta.d.ts +10 -0
- package/dist/browser/presets/meta.js +33 -0
- package/dist/browser/presets/mistral.d.ts +7 -0
- package/dist/browser/presets/mistral.js +22 -0
- package/dist/browser/presets/notion.d.ts +6 -0
- package/dist/browser/presets/notion.js +17 -0
- package/dist/browser/presets/openai.d.ts +9 -0
- package/dist/browser/presets/openai.js +30 -0
- package/dist/browser/presets/oracle.d.ts +19 -0
- package/dist/browser/presets/oracle.js +117 -0
- package/dist/browser/presets/perplexity.d.ts +7 -0
- package/dist/browser/presets/perplexity.js +22 -0
- package/dist/browser/presets/pinecone.d.ts +8 -0
- package/dist/browser/presets/pinecone.js +42 -0
- package/dist/browser/presets/registry.d.ts +23 -0
- package/dist/browser/presets/registry.js +519 -0
- package/dist/browser/presets/replicate.d.ts +7 -0
- package/dist/browser/presets/replicate.js +23 -0
- package/dist/browser/presets/sendgrid.d.ts +6 -0
- package/dist/browser/presets/sendgrid.js +20 -0
- package/dist/browser/presets/sentry.d.ts +11 -0
- package/dist/browser/presets/sentry.js +48 -0
- package/dist/browser/presets/sinch.d.ts +9 -0
- package/dist/browser/presets/sinch.js +39 -0
- package/dist/browser/presets/slack.d.ts +5 -0
- package/dist/browser/presets/slack.js +16 -0
- package/dist/browser/presets/square.d.ts +10 -0
- package/dist/browser/presets/square.js +33 -0
- package/dist/browser/presets/stripe.d.ts +7 -0
- package/dist/browser/presets/stripe.js +23 -0
- package/dist/browser/presets/supabase.d.ts +6 -0
- package/dist/browser/presets/supabase.js +18 -0
- package/dist/browser/presets/tiktok.d.ts +10 -0
- package/dist/browser/presets/tiktok.js +38 -0
- package/dist/browser/presets/together.d.ts +7 -0
- package/dist/browser/presets/together.js +22 -0
- package/dist/browser/presets/twilio.d.ts +6 -0
- package/dist/browser/presets/twilio.js +17 -0
- package/dist/browser/presets/vercel.d.ts +6 -0
- package/dist/browser/presets/vercel.js +23 -0
- package/dist/browser/presets/vultr.d.ts +5 -0
- package/dist/browser/presets/vultr.js +16 -0
- package/dist/browser/presets/xai.d.ts +8 -0
- package/dist/browser/presets/xai.js +23 -0
- package/dist/browser/presets/youtube.d.ts +5 -0
- package/dist/browser/presets/youtube.js +20 -0
- package/dist/browser/recker.d.ts +8 -1
- package/dist/browser/recker.js +8 -2
- package/dist/browser/scrape/document.d.ts +5 -4
- package/dist/browser/scrape/document.js +89 -76
- package/dist/browser/scrape/element.d.ts +10 -8
- package/dist/browser/scrape/element.js +295 -81
- package/dist/browser/scrape/extractors.d.ts +11 -11
- package/dist/browser/scrape/extractors.js +145 -113
- package/dist/browser/scrape/parser/back.d.ts +1 -0
- package/dist/browser/scrape/parser/back.js +3 -0
- package/dist/browser/scrape/parser/index.d.ts +20 -0
- package/dist/browser/scrape/parser/index.js +19 -0
- package/dist/browser/scrape/parser/matcher.d.ts +30 -0
- package/dist/browser/scrape/parser/matcher.js +99 -0
- package/dist/browser/scrape/parser/nodes/comment.d.ts +12 -0
- package/dist/browser/scrape/parser/nodes/comment.js +21 -0
- package/dist/browser/scrape/parser/nodes/html.d.ts +110 -0
- package/dist/browser/scrape/parser/nodes/html.js +978 -0
- package/dist/browser/scrape/parser/nodes/node.d.ts +18 -0
- package/dist/browser/scrape/parser/nodes/node.js +31 -0
- package/dist/browser/scrape/parser/nodes/text.d.ts +14 -0
- package/dist/browser/scrape/parser/nodes/text.js +30 -0
- package/dist/browser/scrape/parser/nodes/type.d.ts +6 -0
- package/dist/browser/scrape/parser/nodes/type.js +7 -0
- package/dist/browser/scrape/parser/parse.d.ts +1 -0
- package/dist/browser/scrape/parser/parse.js +1 -0
- package/dist/browser/scrape/parser/valid.d.ts +2 -0
- package/dist/browser/scrape/parser/valid.js +5 -0
- package/dist/browser/scrape/parser/void-tag.d.ts +7 -0
- package/dist/browser/scrape/parser/void-tag.js +43 -0
- package/dist/browser/scrape/types.d.ts +7 -0
- package/dist/browser/seo/analyzer.d.ts +59 -0
- package/dist/browser/seo/analyzer.js +1399 -0
- package/dist/browser/seo/keywords.d.ts +16 -0
- package/dist/browser/seo/keywords.js +55 -0
- package/dist/browser/seo/rules/accessibility.d.ts +2 -0
- package/dist/browser/seo/rules/accessibility.js +733 -0
- package/dist/browser/seo/rules/ai-search.d.ts +2 -0
- package/dist/browser/seo/rules/ai-search.js +436 -0
- package/dist/browser/seo/rules/analytics.d.ts +2 -0
- package/dist/browser/seo/rules/analytics.js +306 -0
- package/dist/browser/seo/rules/best-practices.d.ts +2 -0
- package/dist/browser/seo/rules/best-practices.js +195 -0
- package/dist/browser/seo/rules/canonical.d.ts +12 -0
- package/dist/browser/seo/rules/canonical.js +270 -0
- package/dist/browser/seo/rules/content.d.ts +2 -0
- package/dist/browser/seo/rules/content.js +522 -0
- package/dist/browser/seo/rules/crawl.d.ts +2 -0
- package/dist/browser/seo/rules/crawl.js +435 -0
- package/dist/browser/seo/rules/cwv.d.ts +2 -0
- package/dist/browser/seo/rules/cwv.js +248 -0
- package/dist/browser/seo/rules/ecommerce.d.ts +2 -0
- package/dist/browser/seo/rules/ecommerce.js +312 -0
- package/dist/browser/seo/rules/i18n.d.ts +2 -0
- package/dist/browser/seo/rules/i18n.js +288 -0
- package/dist/browser/seo/rules/images.d.ts +2 -0
- package/dist/browser/seo/rules/images.js +255 -0
- package/dist/browser/seo/rules/index.d.ts +52 -0
- package/dist/browser/seo/rules/index.js +159 -0
- package/dist/browser/seo/rules/internal-linking.d.ts +2 -0
- package/dist/browser/seo/rules/internal-linking.js +394 -0
- package/dist/browser/seo/rules/links.d.ts +2 -0
- package/dist/browser/seo/rules/links.js +498 -0
- package/dist/browser/seo/rules/local.d.ts +2 -0
- package/dist/browser/seo/rules/local.js +289 -0
- package/dist/browser/seo/rules/meta.d.ts +2 -0
- package/dist/browser/seo/rules/meta.js +805 -0
- package/dist/browser/seo/rules/mobile.d.ts +2 -0
- package/dist/browser/seo/rules/mobile.js +161 -0
- package/dist/browser/seo/rules/performance.d.ts +2 -0
- package/dist/browser/seo/rules/performance.js +738 -0
- package/dist/browser/seo/rules/pwa.d.ts +2 -0
- package/dist/browser/seo/rules/pwa.js +299 -0
- package/dist/browser/seo/rules/readability.d.ts +2 -0
- package/dist/browser/seo/rules/readability.js +264 -0
- package/dist/browser/seo/rules/redirects.d.ts +16 -0
- package/dist/browser/seo/rules/redirects.js +199 -0
- package/dist/browser/seo/rules/resources.d.ts +2 -0
- package/dist/browser/seo/rules/resources.js +390 -0
- package/dist/browser/seo/rules/schema.d.ts +2 -0
- package/dist/browser/seo/rules/schema.js +379 -0
- package/dist/browser/seo/rules/security.d.ts +2 -0
- package/dist/browser/seo/rules/security.js +877 -0
- package/dist/browser/seo/rules/social.d.ts +2 -0
- package/dist/browser/seo/rules/social.js +603 -0
- package/dist/browser/seo/rules/structural.d.ts +2 -0
- package/dist/browser/seo/rules/structural.js +223 -0
- package/dist/browser/seo/rules/technical-advanced.d.ts +10 -0
- package/dist/browser/seo/rules/technical-advanced.js +289 -0
- package/dist/browser/seo/rules/technical.d.ts +2 -0
- package/dist/browser/seo/rules/technical.js +480 -0
- package/dist/browser/seo/rules/thresholds.d.ts +196 -0
- package/dist/browser/seo/rules/thresholds.js +118 -0
- package/dist/browser/seo/rules/types.d.ts +498 -0
- package/dist/browser/seo/rules/types.js +11 -0
- package/dist/browser/seo/types.d.ts +211 -0
- package/dist/browser/seo/types.js +1 -0
- package/dist/browser/transport/curl.d.ts +4 -0
- package/dist/browser/transport/curl.js +101 -0
- package/dist/browser/transport/undici.js +1 -2
- package/dist/browser/transport/worker.d.ts +18 -0
- package/dist/browser/transport/worker.js +278 -0
- package/dist/browser/types/index.d.ts +4 -1
- package/dist/browser/utils/binary-manager.d.ts +4 -0
- package/dist/browser/utils/binary-manager.js +72 -0
- package/dist/browser/utils/user-agent.js +2 -13
- package/dist/cache/indexed-db.d.ts +10 -0
- package/dist/cache/indexed-db.js +88 -0
- package/dist/cache/service-worker-cache.d.ts +18 -0
- package/dist/cache/service-worker-cache.js +103 -0
- package/dist/cli/commands/ai.d.ts +2 -0
- package/dist/cli/commands/ai.js +162 -0
- package/dist/cli/commands/bench.d.ts +2 -0
- package/dist/cli/commands/bench.js +51 -0
- package/dist/cli/commands/dns.d.ts +2 -0
- package/dist/cli/commands/dns.js +295 -0
- package/dist/cli/commands/har.d.ts +2 -0
- package/dist/cli/commands/har.js +171 -0
- package/dist/cli/commands/hls.d.ts +2 -0
- package/dist/cli/commands/hls.js +192 -0
- package/dist/cli/commands/network.d.ts +2 -0
- package/dist/cli/commands/network.js +288 -0
- package/dist/cli/commands/protocols.d.ts +2 -0
- package/dist/cli/commands/protocols.js +344 -0
- package/dist/cli/commands/scrape.d.ts +2 -0
- package/dist/cli/commands/scrape.js +176 -0
- package/dist/cli/commands/security.d.ts +2 -0
- package/dist/cli/commands/security.js +57 -0
- package/dist/cli/commands/seo.d.ts +2 -0
- package/dist/cli/commands/seo.js +125 -0
- package/dist/cli/commands/serve.d.ts +2 -0
- package/dist/cli/commands/serve.js +531 -0
- package/dist/cli/commands/spider.d.ts +3 -0
- package/dist/cli/commands/spider.js +456 -0
- package/dist/cli/commands/utils.d.ts +2 -0
- package/dist/cli/commands/utils.js +176 -0
- package/dist/cli/commands/vector.d.ts +2 -0
- package/dist/cli/commands/vector.js +158 -0
- package/dist/cli/handler.d.ts +2 -2
- package/dist/cli/handler.js +6 -6
- package/dist/cli/helpers.d.ts +7 -0
- package/dist/cli/helpers.js +128 -0
- package/dist/cli/index.js +96 -5228
- package/dist/cli/parser/help.d.ts +2 -0
- package/dist/cli/parser/help.js +52 -0
- package/dist/cli/parser/index.d.ts +3 -0
- package/dist/cli/parser/index.js +3 -0
- package/dist/cli/parser/parser.d.ts +4 -0
- package/dist/cli/parser/parser.js +146 -0
- package/dist/cli/parser/types.d.ts +41 -0
- package/dist/cli/parser/types.js +1 -0
- package/dist/cli/presets.d.ts +1 -1
- package/dist/cli/presets.js +1 -1
- package/dist/cli/router.d.ts +36 -0
- package/dist/cli/router.js +195 -0
- package/dist/cli/tui/ai-chat.js +1 -1
- package/dist/cli/tui/commands/context.d.ts +9 -0
- package/dist/cli/tui/commands/context.js +1 -0
- package/dist/cli/tui/commands/dns.d.ts +10 -0
- package/dist/cli/tui/commands/dns.js +461 -0
- package/dist/cli/tui/commands/hls.d.ts +2 -0
- package/dist/cli/tui/commands/hls.js +162 -0
- package/dist/cli/tui/commands/ip.d.ts +2 -0
- package/dist/cli/tui/commands/ip.js +45 -0
- package/dist/cli/tui/commands/network.d.ts +3 -0
- package/dist/cli/tui/commands/network.js +81 -0
- package/dist/cli/tui/commands/protocols.d.ts +6 -0
- package/dist/cli/tui/commands/protocols.js +531 -0
- package/dist/cli/tui/commands/security.d.ts +2 -0
- package/dist/cli/tui/commands/security.js +48 -0
- package/dist/cli/tui/commands/seo.d.ts +2 -0
- package/dist/cli/tui/commands/seo.js +74 -0
- package/dist/cli/tui/context.d.ts +12 -0
- package/dist/cli/tui/context.js +1 -0
- package/dist/cli/tui/shell.d.ts +11 -20
- package/dist/cli/tui/shell.js +216 -1873
- package/dist/constants/user-agents.d.ts +7 -0
- package/dist/constants/user-agents.js +7 -0
- package/dist/core/client.d.ts +2 -0
- package/dist/core/client.js +19 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/mcp/cli.js +2 -3
- package/dist/mcp/data/embeddings.json +1 -1
- package/dist/mcp/tools/network.js +298 -158
- package/dist/plugins/har-player.d.ts +23 -0
- package/dist/plugins/har-player.js +49 -0
- package/dist/plugins/har-recorder.d.ts +37 -3
- package/dist/plugins/har-recorder.js +116 -63
- package/dist/plugins/network-simulation.d.ts +7 -0
- package/dist/plugins/network-simulation.js +13 -0
- package/dist/presets/android.d.ts +2 -0
- package/dist/presets/android.js +16 -0
- package/dist/presets/chaturbate.d.ts +2 -0
- package/dist/presets/chaturbate.js +17 -0
- package/dist/presets/elevenlabs.d.ts +6 -0
- package/dist/presets/elevenlabs.js +20 -0
- package/dist/presets/enhancers.d.ts +20 -0
- package/dist/presets/enhancers.js +85 -0
- package/dist/presets/hubspot.d.ts +9 -0
- package/dist/presets/hubspot.js +28 -0
- package/dist/presets/index.d.ts +10 -0
- package/dist/presets/index.js +10 -0
- package/dist/presets/ios.d.ts +2 -0
- package/dist/presets/ios.js +13 -0
- package/dist/presets/pinecone.d.ts +8 -0
- package/dist/presets/pinecone.js +42 -0
- package/dist/presets/registry.js +60 -0
- package/dist/presets/sendgrid.d.ts +6 -0
- package/dist/presets/sendgrid.js +20 -0
- package/dist/presets/sentry.d.ts +11 -0
- package/dist/presets/sentry.js +48 -0
- package/dist/presets/square.d.ts +10 -0
- package/dist/presets/square.js +33 -0
- package/dist/recker.d.ts +3 -0
- package/dist/recker.js +4 -0
- package/dist/scrape/document.d.ts +5 -4
- package/dist/scrape/document.js +89 -76
- package/dist/scrape/element.d.ts +10 -8
- package/dist/scrape/element.js +295 -81
- package/dist/scrape/extractors.d.ts +11 -11
- package/dist/scrape/extractors.js +145 -113
- package/dist/scrape/index.d.ts +2 -0
- package/dist/scrape/index.js +1 -0
- package/dist/scrape/parser/back.d.ts +1 -0
- package/dist/scrape/parser/back.js +3 -0
- package/dist/scrape/parser/index.d.ts +20 -0
- package/dist/scrape/parser/index.js +19 -0
- package/dist/scrape/parser/matcher.d.ts +30 -0
- package/dist/scrape/parser/matcher.js +99 -0
- package/dist/scrape/parser/nodes/comment.d.ts +12 -0
- package/dist/scrape/parser/nodes/comment.js +21 -0
- package/dist/scrape/parser/nodes/html.d.ts +110 -0
- package/dist/scrape/parser/nodes/html.js +978 -0
- package/dist/scrape/parser/nodes/node.d.ts +18 -0
- package/dist/scrape/parser/nodes/node.js +31 -0
- package/dist/scrape/parser/nodes/text.d.ts +14 -0
- package/dist/scrape/parser/nodes/text.js +30 -0
- package/dist/scrape/parser/nodes/type.d.ts +6 -0
- package/dist/scrape/parser/nodes/type.js +7 -0
- package/dist/scrape/parser/parse.d.ts +1 -0
- package/dist/scrape/parser/parse.js +1 -0
- package/dist/scrape/parser/valid.d.ts +2 -0
- package/dist/scrape/parser/valid.js +5 -0
- package/dist/scrape/parser/void-tag.d.ts +7 -0
- package/dist/scrape/parser/void-tag.js +43 -0
- package/dist/scrape/spider.d.ts +19 -0
- package/dist/scrape/spider.js +28 -3
- package/dist/scrape/types.d.ts +7 -0
- package/dist/seo/analyzer.d.ts +15 -5
- package/dist/seo/analyzer.js +636 -175
- package/dist/seo/formatter.d.ts +16 -0
- package/dist/seo/formatter.js +228 -0
- package/dist/seo/index.d.ts +2 -0
- package/dist/seo/index.js +1 -0
- package/dist/seo/keywords.d.ts +16 -0
- package/dist/seo/keywords.js +55 -0
- package/dist/seo/rules/accessibility.js +96 -57
- package/dist/seo/rules/ai-search.js +44 -31
- package/dist/seo/rules/analytics.d.ts +2 -0
- package/dist/seo/rules/analytics.js +306 -0
- package/dist/seo/rules/best-practices.js +21 -14
- package/dist/seo/rules/canonical.js +53 -32
- package/dist/seo/rules/content.js +317 -31
- package/dist/seo/rules/crawl.js +55 -40
- package/dist/seo/rules/cwv.js +21 -15
- package/dist/seo/rules/ecommerce.js +82 -22
- package/dist/seo/rules/i18n.js +75 -36
- package/dist/seo/rules/images.js +109 -30
- package/dist/seo/rules/index.js +2 -0
- package/dist/seo/rules/internal-linking.js +58 -39
- package/dist/seo/rules/links.js +79 -52
- package/dist/seo/rules/local.js +49 -25
- package/dist/seo/rules/meta.js +339 -81
- package/dist/seo/rules/mobile.js +112 -2
- package/dist/seo/rules/performance.js +434 -66
- package/dist/seo/rules/pwa.js +36 -39
- package/dist/seo/rules/readability.js +31 -22
- package/dist/seo/rules/redirects.js +21 -15
- package/dist/seo/rules/resources.js +59 -42
- package/dist/seo/rules/schema.js +333 -8
- package/dist/seo/rules/security.js +142 -80
- package/dist/seo/rules/social.js +277 -47
- package/dist/seo/rules/structural.js +87 -19
- package/dist/seo/rules/technical-advanced.js +30 -24
- package/dist/seo/rules/technical.js +243 -42
- package/dist/seo/rules/types.d.ts +53 -1
- package/dist/seo/seo-spider.d.ts +22 -0
- package/dist/seo/seo-spider.js +77 -13
- package/dist/seo/types.d.ts +8 -1
- package/dist/seo/validators/llms-txt.js +19 -0
- package/dist/seo/validators/rss.d.ts +11 -0
- package/dist/seo/validators/rss.js +93 -0
- package/dist/seo/validators/sitemap.js +36 -26
- package/dist/transport/curl.d.ts +4 -0
- package/dist/transport/curl.js +101 -0
- package/dist/transport/udp.js +0 -1
- package/dist/transport/undici.js +1 -2
- package/dist/transport/worker.d.ts +18 -0
- package/dist/transport/worker.js +278 -0
- package/dist/types/index.d.ts +4 -1
- package/dist/utils/binary-manager.d.ts +4 -0
- package/dist/utils/binary-manager.js +72 -0
- package/dist/utils/optional-require.d.ts +7 -8
- package/dist/utils/optional-require.js +2 -21
- package/dist/utils/upload.d.ts +6 -0
- package/dist/utils/upload.js +11 -0
- package/dist/utils/user-agent.js +2 -13
- package/dist/version.js +1 -1
- package/package.json +12 -6
- package/dist/browser/utils/optional-require.d.ts +0 -19
- package/dist/browser/utils/optional-require.js +0 -105
|
@@ -7,8 +7,9 @@ export const accessibilityRules = [
|
|
|
7
7
|
severity: 'error',
|
|
8
8
|
description: 'Buttons must have an accessible name (text content, aria-label, or title)',
|
|
9
9
|
check: (ctx) => {
|
|
10
|
-
if (ctx.buttonsWithoutAriaLabel === undefined)
|
|
11
|
-
return
|
|
10
|
+
if (ctx.buttonsWithoutAriaLabel === undefined) {
|
|
11
|
+
return createResult({ id: 'a11y-buttons-accessible-name', name: 'Buttons Accessible Name', category: 'accessibility', severity: 'error' }, 'info', 'Not applicable (no button data available)', { recommendation: 'This rule checks that all buttons have accessible names via text content, aria-label, or title attributes' });
|
|
12
|
+
}
|
|
12
13
|
const count = ctx.buttonsWithoutAriaLabel;
|
|
13
14
|
if (count > 0) {
|
|
14
15
|
return createResult({ id: 'a11y-buttons-accessible-name', name: 'Buttons Accessible Name', category: 'accessibility', severity: 'error' }, 'fail', `${count} button(s) do not have an accessible name`, {
|
|
@@ -32,8 +33,9 @@ export const accessibilityRules = [
|
|
|
32
33
|
severity: 'error',
|
|
33
34
|
description: 'Image elements must have [alt] attributes',
|
|
34
35
|
check: (ctx) => {
|
|
35
|
-
if (ctx.imagesWithoutAlt === undefined)
|
|
36
|
-
return
|
|
36
|
+
if (ctx.imagesWithoutAlt === undefined) {
|
|
37
|
+
return createResult({ id: 'a11y-images-alt', name: 'Image Alt Attributes', category: 'accessibility', severity: 'error' }, 'info', 'Not applicable (no image data available)', { recommendation: 'This rule checks that all images have alt attributes for screen readers' });
|
|
38
|
+
}
|
|
37
39
|
const count = ctx.imagesWithoutAlt;
|
|
38
40
|
if (count > 0) {
|
|
39
41
|
return createResult({ id: 'a11y-images-alt', name: 'Image Alt Attributes', category: 'accessibility', severity: 'error' }, 'fail', `${count} image(s) do not have [alt] attributes`, {
|
|
@@ -57,8 +59,9 @@ export const accessibilityRules = [
|
|
|
57
59
|
severity: 'error',
|
|
58
60
|
description: 'Links must have a discernible name',
|
|
59
61
|
check: (ctx) => {
|
|
60
|
-
if (ctx.linksWithoutText === undefined && ctx.linksWithoutAriaLabel === undefined)
|
|
61
|
-
return
|
|
62
|
+
if (ctx.linksWithoutText === undefined && ctx.linksWithoutAriaLabel === undefined) {
|
|
63
|
+
return createResult({ id: 'a11y-links-discernible-name', name: 'Links Discernible Name', category: 'accessibility', severity: 'error' }, 'info', 'Not applicable (no link data available)', { recommendation: 'This rule checks that all links have discernible names via text content, aria-label, or title' });
|
|
64
|
+
}
|
|
62
65
|
const linksNoText = ctx.linksWithoutText ?? 0;
|
|
63
66
|
const linksNoAria = ctx.linksWithoutAriaLabel ?? 0;
|
|
64
67
|
const count = Math.max(linksNoText, linksNoAria);
|
|
@@ -84,8 +87,9 @@ export const accessibilityRules = [
|
|
|
84
87
|
severity: 'error',
|
|
85
88
|
description: 'Form elements must have associated labels',
|
|
86
89
|
check: (ctx) => {
|
|
87
|
-
if (ctx.inputsWithoutLabel === undefined)
|
|
88
|
-
return
|
|
90
|
+
if (ctx.inputsWithoutLabel === undefined) {
|
|
91
|
+
return createResult({ id: 'a11y-form-labels', name: 'Form Input Labels', category: 'accessibility', severity: 'error' }, 'info', 'Not applicable (no form input data available)', { recommendation: 'This rule checks that all form inputs have associated labels for accessibility' });
|
|
92
|
+
}
|
|
89
93
|
const count = ctx.inputsWithoutLabel;
|
|
90
94
|
if (count > 0) {
|
|
91
95
|
return createResult({ id: 'a11y-form-labels', name: 'Form Input Labels', category: 'accessibility', severity: 'error' }, 'fail', `${count} form input(s) without associated label`, {
|
|
@@ -109,8 +113,9 @@ export const accessibilityRules = [
|
|
|
109
113
|
severity: 'warning',
|
|
110
114
|
description: 'Heading elements should be in sequentially-descending order',
|
|
111
115
|
check: (ctx) => {
|
|
112
|
-
if (ctx.headingHierarchyValid === undefined)
|
|
113
|
-
return
|
|
116
|
+
if (ctx.headingHierarchyValid === undefined) {
|
|
117
|
+
return createResult({ id: 'a11y-heading-order', name: 'Heading Order', category: 'accessibility', severity: 'warning' }, 'info', 'Not applicable (no heading data available)', { recommendation: 'This rule checks that headings follow a logical order (H1 → H2 → H3) without skipping levels' });
|
|
118
|
+
}
|
|
114
119
|
if (!ctx.headingHierarchyValid) {
|
|
115
120
|
return createResult({ id: 'a11y-heading-order', name: 'Heading Order', category: 'accessibility', severity: 'warning' }, 'warn', 'Heading elements are not in sequentially-descending order', {
|
|
116
121
|
recommendation: 'Ensure headings follow a logical order (H1 → H2 → H3) without skipping levels',
|
|
@@ -132,8 +137,9 @@ export const accessibilityRules = [
|
|
|
132
137
|
severity: 'warning',
|
|
133
138
|
description: 'No element should have a [tabindex] value greater than 0',
|
|
134
139
|
check: (ctx) => {
|
|
135
|
-
if (ctx.elementsWithHighTabindex === undefined)
|
|
136
|
-
return
|
|
140
|
+
if (ctx.elementsWithHighTabindex === undefined) {
|
|
141
|
+
return createResult({ id: 'a11y-tabindex', name: 'Tabindex Values', category: 'accessibility', severity: 'warning' }, 'info', 'Not applicable (no tabindex data available)', { recommendation: 'This rule checks that no element has a tabindex value greater than 0, which can confuse keyboard users' });
|
|
142
|
+
}
|
|
137
143
|
const count = ctx.elementsWithHighTabindex;
|
|
138
144
|
if (count > 0) {
|
|
139
145
|
return createResult({ id: 'a11y-tabindex', name: 'Tabindex Values', category: 'accessibility', severity: 'warning' }, 'warn', `${count} element(s) have tabindex > 0`, {
|
|
@@ -157,8 +163,9 @@ export const accessibilityRules = [
|
|
|
157
163
|
severity: 'error',
|
|
158
164
|
description: '[aria-*] attributes must be valid and not misspelled',
|
|
159
165
|
check: (ctx) => {
|
|
160
|
-
if (ctx.invalidAriaAttributes === undefined)
|
|
161
|
-
return
|
|
166
|
+
if (ctx.invalidAriaAttributes === undefined) {
|
|
167
|
+
return createResult({ id: 'a11y-aria-valid-attrs', name: 'Valid ARIA Attributes', category: 'accessibility', severity: 'error' }, 'info', 'Not applicable (no ARIA attribute data available)', { recommendation: 'This rule checks that all aria-* attributes are valid and not misspelled' });
|
|
168
|
+
}
|
|
162
169
|
const count = ctx.invalidAriaAttributes;
|
|
163
170
|
if (count > 0) {
|
|
164
171
|
return createResult({ id: 'a11y-aria-valid-attrs', name: 'Valid ARIA Attributes', category: 'accessibility', severity: 'error' }, 'fail', `${count} invalid or misspelled aria-* attribute(s) found`, {
|
|
@@ -182,8 +189,9 @@ export const accessibilityRules = [
|
|
|
182
189
|
severity: 'error',
|
|
183
190
|
description: '[aria-*] attributes must have valid values',
|
|
184
191
|
check: (ctx) => {
|
|
185
|
-
if (ctx.invalidAriaValues === undefined)
|
|
186
|
-
return
|
|
192
|
+
if (ctx.invalidAriaValues === undefined) {
|
|
193
|
+
return createResult({ id: 'a11y-aria-valid-values', name: 'ARIA Attribute Values', category: 'accessibility', severity: 'error' }, 'info', 'Not applicable (no ARIA value data available)', { recommendation: 'This rule checks that all aria-* attributes have valid values' });
|
|
194
|
+
}
|
|
187
195
|
const count = ctx.invalidAriaValues;
|
|
188
196
|
if (count > 0) {
|
|
189
197
|
return createResult({ id: 'a11y-aria-valid-values', name: 'ARIA Attribute Values', category: 'accessibility', severity: 'error' }, 'fail', `${count} aria-* attribute(s) have invalid values`, {
|
|
@@ -207,8 +215,9 @@ export const accessibilityRules = [
|
|
|
207
215
|
severity: 'error',
|
|
208
216
|
description: '[role] values must be valid',
|
|
209
217
|
check: (ctx) => {
|
|
210
|
-
if (ctx.invalidAriaRoles === undefined)
|
|
211
|
-
return
|
|
218
|
+
if (ctx.invalidAriaRoles === undefined) {
|
|
219
|
+
return createResult({ id: 'a11y-aria-roles', name: 'Valid ARIA Roles', category: 'accessibility', severity: 'error' }, 'info', 'Not applicable (no ARIA role data available)', { recommendation: 'This rule checks that all role values are valid ARIA roles' });
|
|
220
|
+
}
|
|
212
221
|
const count = ctx.invalidAriaRoles;
|
|
213
222
|
if (count > 0) {
|
|
214
223
|
return createResult({ id: 'a11y-aria-roles', name: 'Valid ARIA Roles', category: 'accessibility', severity: 'error' }, 'fail', `${count} invalid [role] value(s) found`, {
|
|
@@ -232,8 +241,9 @@ export const accessibilityRules = [
|
|
|
232
241
|
severity: 'error',
|
|
233
242
|
description: '[role]s must have all required [aria-*] attributes',
|
|
234
243
|
check: (ctx) => {
|
|
235
|
-
if (ctx.missingRequiredAriaAttrs === undefined)
|
|
236
|
-
return
|
|
244
|
+
if (ctx.missingRequiredAriaAttrs === undefined) {
|
|
245
|
+
return createResult({ id: 'a11y-aria-required-attrs', name: 'Required ARIA Attributes', category: 'accessibility', severity: 'error' }, 'info', 'Not applicable (no required ARIA attribute data available)', { recommendation: 'This rule checks that elements with ARIA roles have all required aria-* attributes' });
|
|
246
|
+
}
|
|
237
247
|
const count = ctx.missingRequiredAriaAttrs;
|
|
238
248
|
if (count > 0) {
|
|
239
249
|
return createResult({ id: 'a11y-aria-required-attrs', name: 'Required ARIA Attributes', category: 'accessibility', severity: 'error' }, 'fail', `${count} element(s) with roles missing required aria-* attributes`, {
|
|
@@ -257,8 +267,9 @@ export const accessibilityRules = [
|
|
|
257
267
|
severity: 'error',
|
|
258
268
|
description: '[aria-hidden="true"] must not be present on the document <body>',
|
|
259
269
|
check: (ctx) => {
|
|
260
|
-
if (ctx.hasAriaHiddenBody === undefined)
|
|
261
|
-
return
|
|
270
|
+
if (ctx.hasAriaHiddenBody === undefined) {
|
|
271
|
+
return createResult({ id: 'a11y-aria-hidden-body', name: 'ARIA Hidden Body', category: 'accessibility', severity: 'error' }, 'info', 'Not applicable (no body element data available)', { recommendation: 'This rule checks that aria-hidden is not present on the document body' });
|
|
272
|
+
}
|
|
262
273
|
if (ctx.hasAriaHiddenBody) {
|
|
263
274
|
return createResult({ id: 'a11y-aria-hidden-body', name: 'ARIA Hidden Body', category: 'accessibility', severity: 'error' }, 'fail', 'aria-hidden="true" is present on document <body>', {
|
|
264
275
|
recommendation: 'Remove aria-hidden from <body> element',
|
|
@@ -280,8 +291,9 @@ export const accessibilityRules = [
|
|
|
280
291
|
severity: 'error',
|
|
281
292
|
description: '[aria-hidden="true"] elements must not contain focusable descendants',
|
|
282
293
|
check: (ctx) => {
|
|
283
|
-
if (ctx.ariaHiddenFocusableCount === undefined)
|
|
284
|
-
return
|
|
294
|
+
if (ctx.ariaHiddenFocusableCount === undefined) {
|
|
295
|
+
return createResult({ id: 'a11y-aria-hidden-focus', name: 'ARIA Hidden Focusable', category: 'accessibility', severity: 'error' }, 'info', 'Not applicable (no aria-hidden focusable data available)', { recommendation: 'This rule checks that aria-hidden elements do not contain focusable descendants' });
|
|
296
|
+
}
|
|
285
297
|
const count = ctx.ariaHiddenFocusableCount;
|
|
286
298
|
if (count > 0) {
|
|
287
299
|
return createResult({ id: 'a11y-aria-hidden-focus', name: 'ARIA Hidden Focusable', category: 'accessibility', severity: 'error' }, 'fail', `${count} aria-hidden element(s) contain focusable descendants`, {
|
|
@@ -305,8 +317,9 @@ export const accessibilityRules = [
|
|
|
305
317
|
severity: 'warning',
|
|
306
318
|
description: 'Deprecated ARIA roles should not be used',
|
|
307
319
|
check: (ctx) => {
|
|
308
|
-
if (ctx.deprecatedAriaRoles === undefined)
|
|
309
|
-
return
|
|
320
|
+
if (ctx.deprecatedAriaRoles === undefined) {
|
|
321
|
+
return createResult({ id: 'a11y-aria-deprecated', name: 'Deprecated ARIA Roles', category: 'accessibility', severity: 'warning' }, 'info', 'Not applicable (no deprecated ARIA role data available)', { recommendation: 'This rule checks that deprecated ARIA roles are not used' });
|
|
322
|
+
}
|
|
310
323
|
const count = ctx.deprecatedAriaRoles;
|
|
311
324
|
if (count > 0) {
|
|
312
325
|
return createResult({ id: 'a11y-aria-deprecated', name: 'Deprecated ARIA Roles', category: 'accessibility', severity: 'warning' }, 'warn', `${count} deprecated ARIA role(s) found`, {
|
|
@@ -330,8 +343,9 @@ export const accessibilityRules = [
|
|
|
330
343
|
severity: 'error',
|
|
331
344
|
description: 'ARIA IDs must be unique',
|
|
332
345
|
check: (ctx) => {
|
|
333
|
-
if (ctx.duplicateAriaIds === undefined)
|
|
334
|
-
return
|
|
346
|
+
if (ctx.duplicateAriaIds === undefined) {
|
|
347
|
+
return createResult({ id: 'a11y-aria-ids-unique', name: 'ARIA IDs Unique', category: 'accessibility', severity: 'error' }, 'info', 'Not applicable (no ARIA ID data available)', { recommendation: 'This rule checks that all IDs referenced by aria-labelledby, aria-describedby, etc. are unique' });
|
|
348
|
+
}
|
|
335
349
|
const count = ctx.duplicateAriaIds;
|
|
336
350
|
if (count > 0) {
|
|
337
351
|
return createResult({ id: 'a11y-aria-ids-unique', name: 'ARIA IDs Unique', category: 'accessibility', severity: 'error' }, 'fail', `${count} duplicate ARIA ID(s) found`, {
|
|
@@ -355,8 +369,9 @@ export const accessibilityRules = [
|
|
|
355
369
|
severity: 'error',
|
|
356
370
|
description: 'Elements with role="dialog" or role="alertdialog" must have accessible names',
|
|
357
371
|
check: (ctx) => {
|
|
358
|
-
if (ctx.dialogsWithoutName === undefined)
|
|
359
|
-
return
|
|
372
|
+
if (ctx.dialogsWithoutName === undefined) {
|
|
373
|
+
return createResult({ id: 'a11y-dialog-name', name: 'Dialog Accessible Name', category: 'accessibility', severity: 'error' }, 'info', 'Not applicable (no dialog data available)', { recommendation: 'This rule checks that elements with role="dialog" or role="alertdialog" have accessible names' });
|
|
374
|
+
}
|
|
360
375
|
const count = ctx.dialogsWithoutName;
|
|
361
376
|
if (count > 0) {
|
|
362
377
|
return createResult({ id: 'a11y-dialog-name', name: 'Dialog Accessible Name', category: 'accessibility', severity: 'error' }, 'fail', `${count} dialog(s) without accessible name`, {
|
|
@@ -380,14 +395,17 @@ export const accessibilityRules = [
|
|
|
380
395
|
severity: 'warning',
|
|
381
396
|
description: 'Document should have a main landmark',
|
|
382
397
|
check: (ctx) => {
|
|
383
|
-
if (ctx.hasMain === undefined)
|
|
384
|
-
return
|
|
398
|
+
if (ctx.hasMain === undefined) {
|
|
399
|
+
return createResult({ id: 'a11y-main-landmark', name: 'Main Landmark', category: 'accessibility', severity: 'warning' }, 'info', 'Not applicable (no main landmark data available)', { recommendation: 'This rule checks that document has a <main> element or role="main" for screen reader navigation' });
|
|
400
|
+
}
|
|
385
401
|
if (!ctx.hasMain) {
|
|
386
402
|
return createResult({ id: 'a11y-main-landmark', name: 'Main Landmark', category: 'accessibility', severity: 'warning' }, 'warn', 'Document does not have a <main> landmark', {
|
|
387
403
|
recommendation: 'Add a <main> element or role="main" to identify the main content area',
|
|
388
404
|
evidence: {
|
|
405
|
+
found: 'No <main> element or role="main"',
|
|
389
406
|
expected: '<main> or role="main"',
|
|
390
407
|
impact: 'Screen reader users cannot quickly navigate to main content',
|
|
408
|
+
example: '<main>\n <!-- Main content here -->\n</main>',
|
|
391
409
|
learnMore: 'https://dequeuniversity.com/rules/axe/4.4/landmark-one-main',
|
|
392
410
|
},
|
|
393
411
|
});
|
|
@@ -402,8 +420,9 @@ export const accessibilityRules = [
|
|
|
402
420
|
severity: 'info',
|
|
403
421
|
description: 'Page should contain a heading, skip link, or landmark region',
|
|
404
422
|
check: (ctx) => {
|
|
405
|
-
if (ctx.hasSkipLink === undefined && ctx.hasMain === undefined && ctx.h1Count === undefined)
|
|
406
|
-
return
|
|
423
|
+
if (ctx.hasSkipLink === undefined && ctx.hasMain === undefined && ctx.h1Count === undefined) {
|
|
424
|
+
return createResult({ id: 'a11y-skip-link', name: 'Skip Link', category: 'accessibility', severity: 'info' }, 'info', 'Not applicable (no navigation bypass data available)', { recommendation: 'This rule checks that page contains a skip link, main landmark, or heading for keyboard navigation' });
|
|
425
|
+
}
|
|
407
426
|
const hasSkip = ctx.hasSkipLink ?? false;
|
|
408
427
|
const hasMain = ctx.hasMain ?? false;
|
|
409
428
|
const hasH1 = (ctx.h1Count ?? 0) > 0;
|
|
@@ -411,8 +430,10 @@ export const accessibilityRules = [
|
|
|
411
430
|
return createResult({ id: 'a11y-skip-link', name: 'Skip Link', category: 'accessibility', severity: 'info' }, 'info', 'No skip link, main landmark, or heading found', {
|
|
412
431
|
recommendation: 'Add a skip link, <main> landmark, or at least one heading for navigation',
|
|
413
432
|
evidence: {
|
|
433
|
+
found: 'No skip link, <main>, or heading',
|
|
414
434
|
expected: 'Skip link, <main>, or heading',
|
|
415
435
|
impact: 'Keyboard users must tab through all navigation to reach content',
|
|
436
|
+
example: '<a href="#main-content" class="skip-link">Skip to main content</a>',
|
|
416
437
|
learnMore: 'https://dequeuniversity.com/rules/axe/4.4/bypass',
|
|
417
438
|
},
|
|
418
439
|
});
|
|
@@ -427,8 +448,9 @@ export const accessibilityRules = [
|
|
|
427
448
|
severity: 'info',
|
|
428
449
|
description: 'Data tables should have caption or aria-label',
|
|
429
450
|
check: (ctx) => {
|
|
430
|
-
if (ctx.tablesWithoutCaption === undefined)
|
|
431
|
-
return
|
|
451
|
+
if (ctx.tablesWithoutCaption === undefined) {
|
|
452
|
+
return createResult({ id: 'a11y-table-caption', name: 'Table Caption', category: 'accessibility', severity: 'info' }, 'info', 'Not applicable (no table data available)', { recommendation: 'This rule checks that data tables have captions or aria-labels for screen readers' });
|
|
453
|
+
}
|
|
432
454
|
const count = ctx.tablesWithoutCaption;
|
|
433
455
|
if (count > 0) {
|
|
434
456
|
return createResult({ id: 'a11y-table-caption', name: 'Table Caption', category: 'accessibility', severity: 'info' }, 'info', `${count} table(s) without caption or aria-label`, {
|
|
@@ -451,8 +473,9 @@ export const accessibilityRules = [
|
|
|
451
473
|
severity: 'warning',
|
|
452
474
|
description: '<frame> or <iframe> elements must have a title',
|
|
453
475
|
check: (ctx) => {
|
|
454
|
-
if (ctx.iframesWithoutTitle === undefined)
|
|
455
|
-
return
|
|
476
|
+
if (ctx.iframesWithoutTitle === undefined) {
|
|
477
|
+
return createResult({ id: 'a11y-iframe-title', name: 'Iframe Title', category: 'accessibility', severity: 'warning' }, 'info', 'Not applicable (no iframe data available)', { recommendation: 'This rule checks that iframes have title attributes to describe their content' });
|
|
478
|
+
}
|
|
456
479
|
const count = ctx.iframesWithoutTitle;
|
|
457
480
|
if (count > 0) {
|
|
458
481
|
return createResult({ id: 'a11y-iframe-title', name: 'Iframe Title', category: 'accessibility', severity: 'warning' }, 'warn', `${count} iframe(s) without title attribute`, {
|
|
@@ -476,8 +499,9 @@ export const accessibilityRules = [
|
|
|
476
499
|
severity: 'warning',
|
|
477
500
|
description: 'SVGs should have <title> or aria-label for accessibility',
|
|
478
501
|
check: (ctx) => {
|
|
479
|
-
if (ctx.svgsWithoutTitle === undefined)
|
|
480
|
-
return
|
|
502
|
+
if (ctx.svgsWithoutTitle === undefined) {
|
|
503
|
+
return createResult({ id: 'a11y-svg-title', name: 'SVG Title', category: 'accessibility', severity: 'warning' }, 'info', 'Not applicable (no SVG data available)', { recommendation: 'This rule checks that SVGs have <title> elements or aria-label for accessibility' });
|
|
504
|
+
}
|
|
481
505
|
const count = ctx.svgsWithoutTitle;
|
|
482
506
|
if (count > 0) {
|
|
483
507
|
return createResult({ id: 'a11y-svg-title', name: 'SVG Title', category: 'accessibility', severity: 'warning' }, 'warn', `${count} SVG(s) without accessible title`, {
|
|
@@ -487,6 +511,7 @@ export const accessibilityRules = [
|
|
|
487
511
|
found: count,
|
|
488
512
|
expected: 0,
|
|
489
513
|
impact: 'Screen readers cannot describe SVG content',
|
|
514
|
+
example: '<svg aria-label="Descriptive label">\n <title>Icon description</title>\n <!-- SVG content -->\n</svg>',
|
|
490
515
|
},
|
|
491
516
|
});
|
|
492
517
|
}
|
|
@@ -500,8 +525,9 @@ export const accessibilityRules = [
|
|
|
500
525
|
severity: 'error',
|
|
501
526
|
description: '[user-scalable="no"] should not be used and maximum-scale should not be less than 5',
|
|
502
527
|
check: (ctx) => {
|
|
503
|
-
if (ctx.viewportContent === undefined)
|
|
504
|
-
return
|
|
528
|
+
if (ctx.viewportContent === undefined) {
|
|
529
|
+
return createResult({ id: 'a11y-viewport-zoom', name: 'Viewport Zoom', category: 'accessibility', severity: 'error' }, 'info', 'Not applicable (no viewport meta tag data available)', { recommendation: 'This rule checks that viewport meta tag allows zooming for users with low vision' });
|
|
530
|
+
}
|
|
505
531
|
const viewport = ctx.viewportContent.toLowerCase();
|
|
506
532
|
const hasUserScalableNo = viewport.includes('user-scalable=no') || viewport.includes('user-scalable=0');
|
|
507
533
|
const maxScaleMatch = viewport.match(/maximum-scale\s*=\s*([\d.]+)/);
|
|
@@ -527,14 +553,17 @@ export const accessibilityRules = [
|
|
|
527
553
|
severity: 'error',
|
|
528
554
|
description: 'Document must have a <title> element',
|
|
529
555
|
check: (ctx) => {
|
|
530
|
-
if (ctx.title === undefined)
|
|
531
|
-
return
|
|
556
|
+
if (ctx.title === undefined) {
|
|
557
|
+
return createResult({ id: 'a11y-document-title', name: 'Document Title', category: 'accessibility', severity: 'error' }, 'info', 'Not applicable (no title data available)', { recommendation: 'This rule checks that document has a <title> element for screen readers' });
|
|
558
|
+
}
|
|
532
559
|
if (!ctx.title || ctx.title.trim().length === 0) {
|
|
533
560
|
return createResult({ id: 'a11y-document-title', name: 'Document Title', category: 'accessibility', severity: 'error' }, 'fail', 'Document does not have a <title> element', {
|
|
534
561
|
recommendation: 'Add a descriptive <title> element to the document',
|
|
535
562
|
evidence: {
|
|
563
|
+
found: ctx.title || 'Empty or missing <title>',
|
|
536
564
|
expected: '<title>Page Title</title>',
|
|
537
565
|
impact: 'Screen reader users cannot identify the page',
|
|
566
|
+
example: '<head>\n <title>My Page - Site Name</title>\n</head>',
|
|
538
567
|
learnMore: 'https://dequeuniversity.com/rules/axe/4.4/document-title',
|
|
539
568
|
},
|
|
540
569
|
});
|
|
@@ -549,14 +578,17 @@ export const accessibilityRules = [
|
|
|
549
578
|
severity: 'error',
|
|
550
579
|
description: '<html> element must have a [lang] attribute',
|
|
551
580
|
check: (ctx) => {
|
|
552
|
-
if (ctx.hasLang === undefined)
|
|
553
|
-
return
|
|
581
|
+
if (ctx.hasLang === undefined) {
|
|
582
|
+
return createResult({ id: 'a11y-html-lang', name: 'HTML Lang Attribute', category: 'accessibility', severity: 'error' }, 'info', 'Not applicable (no HTML lang attribute data available)', { recommendation: 'This rule checks that HTML element has a lang attribute for screen readers' });
|
|
583
|
+
}
|
|
554
584
|
if (!ctx.hasLang) {
|
|
555
585
|
return createResult({ id: 'a11y-html-lang', name: 'HTML Lang Attribute', category: 'accessibility', severity: 'error' }, 'fail', '<html> element does not have a [lang] attribute', {
|
|
556
586
|
recommendation: 'Add lang attribute to html element (e.g., <html lang="en">)',
|
|
557
587
|
evidence: {
|
|
588
|
+
found: '<html> without lang attribute',
|
|
558
589
|
expected: '<html lang="en">',
|
|
559
590
|
impact: 'Screen readers may pronounce content incorrectly',
|
|
591
|
+
example: '<html lang="en">\n <!-- page content -->\n</html>',
|
|
560
592
|
learnMore: 'https://dequeuniversity.com/rules/axe/4.4/html-has-lang',
|
|
561
593
|
},
|
|
562
594
|
});
|
|
@@ -571,8 +603,9 @@ export const accessibilityRules = [
|
|
|
571
603
|
severity: 'error',
|
|
572
604
|
description: '<html> element must have a valid value for its [lang] attribute',
|
|
573
605
|
check: (ctx) => {
|
|
574
|
-
if (!ctx.hasLang || !ctx.langValue)
|
|
575
|
-
return
|
|
606
|
+
if (!ctx.hasLang || !ctx.langValue) {
|
|
607
|
+
return createResult({ id: 'a11y-html-lang-valid', name: 'Valid Lang Attribute', category: 'accessibility', severity: 'error' }, 'info', 'Not applicable (no lang attribute present to validate)', { recommendation: 'This rule checks that HTML lang attribute has a valid BCP 47 language tag' });
|
|
608
|
+
}
|
|
576
609
|
const validLangPattern = /^[a-z]{2,3}(-[A-Za-z]{2,4})?(-[A-Za-z0-9]{2,})?$/i;
|
|
577
610
|
if (!validLangPattern.test(ctx.langValue)) {
|
|
578
611
|
return createResult({ id: 'a11y-html-lang-valid', name: 'Valid Lang Attribute', category: 'accessibility', severity: 'error' }, 'fail', `Invalid lang attribute value: ${ctx.langValue}`, {
|
|
@@ -581,6 +614,8 @@ export const accessibilityRules = [
|
|
|
581
614
|
evidence: {
|
|
582
615
|
found: ctx.langValue,
|
|
583
616
|
expected: 'Valid BCP 47 language tag',
|
|
617
|
+
impact: 'Invalid language tags prevent screen readers from selecting correct voice/pronunciation',
|
|
618
|
+
example: '<html lang="en"> or <html lang="pt-BR">',
|
|
584
619
|
learnMore: 'https://dequeuniversity.com/rules/axe/4.4/html-lang-valid',
|
|
585
620
|
},
|
|
586
621
|
});
|
|
@@ -595,8 +630,9 @@ export const accessibilityRules = [
|
|
|
595
630
|
severity: 'warning',
|
|
596
631
|
description: '<video> elements should contain a <track> element with [kind="captions"]',
|
|
597
632
|
check: (ctx) => {
|
|
598
|
-
if (ctx.videoCount === undefined || ctx.videosWithCaptions === undefined)
|
|
599
|
-
return
|
|
633
|
+
if (ctx.videoCount === undefined || ctx.videosWithCaptions === undefined) {
|
|
634
|
+
return createResult({ id: 'a11y-video-captions', name: 'Video Captions', category: 'accessibility', severity: 'warning' }, 'info', 'Not applicable (no video data available)', { recommendation: 'This rule checks that video elements have caption tracks for deaf users' });
|
|
635
|
+
}
|
|
600
636
|
const videos = ctx.videoCount;
|
|
601
637
|
const withCaptions = ctx.videosWithCaptions;
|
|
602
638
|
if (videos > 0 && withCaptions < videos) {
|
|
@@ -614,7 +650,7 @@ export const accessibilityRules = [
|
|
|
614
650
|
if (videos > 0) {
|
|
615
651
|
return createResult({ id: 'a11y-video-captions', name: 'Video Captions', category: 'accessibility', severity: 'warning' }, 'pass', `All ${videos} video(s) have captions`);
|
|
616
652
|
}
|
|
617
|
-
return
|
|
653
|
+
return createResult({ id: 'a11y-video-captions', name: 'Video Captions', category: 'accessibility', severity: 'warning' }, 'info', 'Not applicable (no video elements detected)', { recommendation: 'This rule checks that video elements have caption tracks for deaf users' });
|
|
618
654
|
},
|
|
619
655
|
},
|
|
620
656
|
{
|
|
@@ -624,8 +660,9 @@ export const accessibilityRules = [
|
|
|
624
660
|
severity: 'warning',
|
|
625
661
|
description: 'Lists must contain only <li> elements and script supporting elements',
|
|
626
662
|
check: (ctx) => {
|
|
627
|
-
if (ctx.invalidListStructure === undefined)
|
|
628
|
-
return
|
|
663
|
+
if (ctx.invalidListStructure === undefined) {
|
|
664
|
+
return createResult({ id: 'a11y-list-structure', name: 'List Structure', category: 'accessibility', severity: 'warning' }, 'info', 'Not applicable (no list structure data available)', { recommendation: 'This rule checks that lists only contain valid children (li, script, template)' });
|
|
665
|
+
}
|
|
629
666
|
const count = ctx.invalidListStructure;
|
|
630
667
|
if (count > 0) {
|
|
631
668
|
return createResult({ id: 'a11y-list-structure', name: 'List Structure', category: 'accessibility', severity: 'warning' }, 'warn', `${count} list(s) have invalid structure`, {
|
|
@@ -649,8 +686,9 @@ export const accessibilityRules = [
|
|
|
649
686
|
severity: 'warning',
|
|
650
687
|
description: 'All heading elements must contain content',
|
|
651
688
|
check: (ctx) => {
|
|
652
|
-
if (ctx.emptyHeadings === undefined)
|
|
653
|
-
return
|
|
689
|
+
if (ctx.emptyHeadings === undefined) {
|
|
690
|
+
return createResult({ id: 'a11y-headings-content', name: 'Heading Content', category: 'accessibility', severity: 'warning' }, 'info', 'Not applicable (no heading content data available)', { recommendation: 'This rule checks that all heading elements contain text content' });
|
|
691
|
+
}
|
|
654
692
|
const count = ctx.emptyHeadings;
|
|
655
693
|
if (count > 0) {
|
|
656
694
|
return createResult({ id: 'a11y-headings-content', name: 'Heading Content', category: 'accessibility', severity: 'warning' }, 'warn', `${count} empty heading element(s) found`, {
|
|
@@ -674,8 +712,9 @@ export const accessibilityRules = [
|
|
|
674
712
|
severity: 'info',
|
|
675
713
|
description: 'Image alt attributes should not be redundant',
|
|
676
714
|
check: (ctx) => {
|
|
677
|
-
if (ctx.imagesWithRedundantAlt === undefined)
|
|
678
|
-
return
|
|
715
|
+
if (ctx.imagesWithRedundantAlt === undefined) {
|
|
716
|
+
return createResult({ id: 'a11y-images-redundant-alt', name: 'Redundant Alt Text', category: 'accessibility', severity: 'info' }, 'info', 'Not applicable (no image alt text data available)', { recommendation: 'This rule checks that image alt text does not contain redundant phrases like "image of" or "picture of"' });
|
|
717
|
+
}
|
|
679
718
|
const count = ctx.imagesWithRedundantAlt;
|
|
680
719
|
if (count > 0) {
|
|
681
720
|
return createResult({ id: 'a11y-images-redundant-alt', name: 'Redundant Alt Text', category: 'accessibility', severity: 'info' }, 'info', `${count} image(s) have redundant alt text (e.g., "image of", "picture of")`, {
|
|
@@ -7,8 +7,9 @@ export const aiSearchRules = [
|
|
|
7
7
|
severity: 'info',
|
|
8
8
|
description: 'llms.txt helps AI systems understand your site (llmstxt.org)',
|
|
9
9
|
check: (ctx) => {
|
|
10
|
-
if (ctx.llmsTxt === undefined)
|
|
11
|
-
return
|
|
10
|
+
if (ctx.llmsTxt === undefined) {
|
|
11
|
+
return createResult({ id: 'ai-llms-txt-exists', name: 'llms.txt File', category: 'ai-search', severity: 'info' }, 'info', 'Not applicable (llms.txt data unavailable)', { recommendation: 'This rule checks for llms.txt file to optimize for AI/LLM discovery' });
|
|
12
|
+
}
|
|
12
13
|
if (!ctx.llmsTxt.exists) {
|
|
13
14
|
return createResult({ id: 'ai-llms-txt-exists', name: 'llms.txt File', category: 'ai-search', severity: 'info' }, 'info', 'llms.txt not found', {
|
|
14
15
|
recommendation: 'Create llms.txt to optimize for AI/LLM discovery',
|
|
@@ -37,8 +38,9 @@ export const aiSearchRules = [
|
|
|
37
38
|
severity: 'info',
|
|
38
39
|
description: 'llms.txt should have proper structure with site name and description',
|
|
39
40
|
check: (ctx) => {
|
|
40
|
-
if (!ctx.llmsTxt?.exists || !ctx.llmsTxt.parseResult)
|
|
41
|
-
return
|
|
41
|
+
if (!ctx.llmsTxt?.exists || !ctx.llmsTxt.parseResult) {
|
|
42
|
+
return createResult({ id: 'ai-llms-txt-structure', name: 'llms.txt Structure', category: 'ai-search', severity: 'info' }, 'info', 'Not applicable (llms.txt does not exist or parse data unavailable)', { recommendation: 'This rule validates llms.txt structure for AI comprehension' });
|
|
43
|
+
}
|
|
42
44
|
const { siteName, siteDescription, sections, links } = ctx.llmsTxt.parseResult;
|
|
43
45
|
const issues = [];
|
|
44
46
|
if (!siteName) {
|
|
@@ -73,8 +75,9 @@ export const aiSearchRules = [
|
|
|
73
75
|
severity: 'info',
|
|
74
76
|
description: 'Content should be well-structured for AI comprehension',
|
|
75
77
|
check: (ctx) => {
|
|
76
|
-
if (!ctx.headings?.structure)
|
|
77
|
-
return
|
|
78
|
+
if (!ctx.headings?.structure) {
|
|
79
|
+
return createResult({ id: 'ai-content-structure', name: 'AI-Friendly Content Structure', category: 'ai-search', severity: 'info' }, 'info', 'Not applicable (heading structure data unavailable)', { recommendation: 'This rule checks for well-structured content for AI comprehension' });
|
|
80
|
+
}
|
|
78
81
|
const issues = [];
|
|
79
82
|
const h1Count = ctx.headings.h1Count || 0;
|
|
80
83
|
const hasH2 = ctx.headings.structure.some((h) => h.level === 2);
|
|
@@ -105,8 +108,9 @@ export const aiSearchRules = [
|
|
|
105
108
|
severity: 'info',
|
|
106
109
|
description: 'Question headings help with AI search featured snippets',
|
|
107
110
|
check: (ctx) => {
|
|
108
|
-
if (!ctx.headings?.structure)
|
|
109
|
-
return
|
|
111
|
+
if (!ctx.headings?.structure) {
|
|
112
|
+
return createResult({ id: 'ai-question-headings', name: 'Question-Based Headings', category: 'ai-search', severity: 'info' }, 'info', 'Not applicable (heading structure data unavailable)', { recommendation: 'This rule checks for question-based headings that trigger featured snippets' });
|
|
113
|
+
}
|
|
110
114
|
const questionPatterns = [
|
|
111
115
|
/^what\s/i, /^how\s/i, /^why\s/i, /^when\s/i,
|
|
112
116
|
/^where\s/i, /^who\s/i, /^which\s/i, /^can\s/i,
|
|
@@ -130,7 +134,7 @@ export const aiSearchRules = [
|
|
|
130
134
|
},
|
|
131
135
|
});
|
|
132
136
|
}
|
|
133
|
-
return
|
|
137
|
+
return createResult({ id: 'ai-question-headings', name: 'Question-Based Headings', category: 'ai-search', severity: 'info' }, 'pass', 'Content length does not require question headings');
|
|
134
138
|
},
|
|
135
139
|
},
|
|
136
140
|
{
|
|
@@ -140,8 +144,9 @@ export const aiSearchRules = [
|
|
|
140
144
|
severity: 'info',
|
|
141
145
|
description: 'Schema.org structured data helps AI understand content',
|
|
142
146
|
check: (ctx) => {
|
|
143
|
-
if (!ctx.jsonLdTypes)
|
|
144
|
-
return
|
|
147
|
+
if (!ctx.jsonLdTypes) {
|
|
148
|
+
return createResult({ id: 'ai-structured-data', name: 'Structured Data for AI', category: 'ai-search', severity: 'info' }, 'info', 'Not applicable (structured data unavailable)', { recommendation: 'This rule checks for Schema.org structured data for AI comprehension' });
|
|
149
|
+
}
|
|
145
150
|
const aiHelpfulTypes = [
|
|
146
151
|
'Article', 'BlogPosting', 'NewsArticle', 'TechArticle',
|
|
147
152
|
'FAQPage', 'HowTo', 'QAPage',
|
|
@@ -184,8 +189,9 @@ export const aiSearchRules = [
|
|
|
184
189
|
severity: 'info',
|
|
185
190
|
description: 'FAQPage schema is highly valuable for AI search results',
|
|
186
191
|
check: (ctx) => {
|
|
187
|
-
if (!ctx.jsonLdTypes)
|
|
188
|
-
return
|
|
192
|
+
if (!ctx.jsonLdTypes) {
|
|
193
|
+
return createResult({ id: 'ai-faq-schema', name: 'FAQ Schema for AI', category: 'ai-search', severity: 'info' }, 'info', 'Not applicable (structured data unavailable)', { recommendation: 'This rule checks for FAQ/HowTo schema for AI search rich results' });
|
|
194
|
+
}
|
|
189
195
|
const hasFaqSchema = ctx.jsonLdTypes.some((t) => t.includes('FAQPage'));
|
|
190
196
|
const hasHowToSchema = ctx.jsonLdTypes.some((t) => t.includes('HowTo'));
|
|
191
197
|
const hasQASchema = ctx.jsonLdTypes.some((t) => t.includes('QAPage'));
|
|
@@ -214,7 +220,7 @@ export const aiSearchRules = [
|
|
|
214
220
|
},
|
|
215
221
|
});
|
|
216
222
|
}
|
|
217
|
-
return
|
|
223
|
+
return createResult({ id: 'ai-faq-schema', name: 'FAQ Schema for AI', category: 'ai-search', severity: 'info' }, 'pass', 'No Q&A structure detected requiring FAQ schema');
|
|
218
224
|
},
|
|
219
225
|
},
|
|
220
226
|
{
|
|
@@ -224,8 +230,9 @@ export const aiSearchRules = [
|
|
|
224
230
|
severity: 'info',
|
|
225
231
|
description: 'AI systems prefer comprehensive, in-depth content',
|
|
226
232
|
check: (ctx) => {
|
|
227
|
-
if (ctx.wordCount === undefined)
|
|
228
|
-
return
|
|
233
|
+
if (ctx.wordCount === undefined) {
|
|
234
|
+
return createResult({ id: 'ai-content-depth', name: 'Content Depth for AI', category: 'ai-search', severity: 'info' }, 'info', 'Not applicable (word count data unavailable)', { recommendation: 'This rule checks content depth for AI search visibility' });
|
|
235
|
+
}
|
|
229
236
|
const minWords = 300;
|
|
230
237
|
const goodWords = 1000;
|
|
231
238
|
const excellentWords = 2000;
|
|
@@ -259,8 +266,9 @@ export const aiSearchRules = [
|
|
|
259
266
|
severity: 'info',
|
|
260
267
|
description: 'AI systems value fresh, updated content',
|
|
261
268
|
check: (ctx) => {
|
|
262
|
-
if (!ctx.jsonLdTypes)
|
|
263
|
-
return
|
|
269
|
+
if (!ctx.jsonLdTypes) {
|
|
270
|
+
return createResult({ id: 'ai-content-freshness', name: 'Content Freshness Signals', category: 'ai-search', severity: 'info' }, 'info', 'Not applicable (structured data unavailable)', { recommendation: 'This rule checks for content freshness signals in structured data' });
|
|
271
|
+
}
|
|
264
272
|
const hasDateSignals = ctx.jsonLdTypes.some((t) => t.includes('Article') || t.includes('BlogPosting') || t.includes('NewsArticle'));
|
|
265
273
|
if (hasDateSignals) {
|
|
266
274
|
return createResult({ id: 'ai-content-freshness', name: 'Content Freshness Signals', category: 'ai-search', severity: 'info' }, 'pass', 'Article schema with date signals found');
|
|
@@ -281,8 +289,9 @@ export const aiSearchRules = [
|
|
|
281
289
|
severity: 'info',
|
|
282
290
|
description: 'Check if GPTBot (OpenAI) is allowed or blocked',
|
|
283
291
|
check: (ctx) => {
|
|
284
|
-
if (!ctx.robotsTxt?.parseResult)
|
|
285
|
-
return
|
|
292
|
+
if (!ctx.robotsTxt?.parseResult) {
|
|
293
|
+
return createResult({ id: 'ai-robots-gpt-bot', name: 'GPTBot Access', category: 'ai-search', severity: 'info' }, 'info', 'Not applicable (robots.txt data unavailable)', { recommendation: 'This rule checks if GPTBot is allowed to crawl for ChatGPT training' });
|
|
294
|
+
}
|
|
286
295
|
const { userAgentBlocks } = ctx.robotsTxt.parseResult;
|
|
287
296
|
const gptBotBlock = userAgentBlocks.find((b) => b.userAgents.some(ua => ua.toLowerCase().includes('gptbot')));
|
|
288
297
|
if (gptBotBlock) {
|
|
@@ -307,8 +316,9 @@ export const aiSearchRules = [
|
|
|
307
316
|
severity: 'info',
|
|
308
317
|
description: 'Check if Anthropic/Claude crawlers are allowed or blocked',
|
|
309
318
|
check: (ctx) => {
|
|
310
|
-
if (!ctx.robotsTxt?.parseResult)
|
|
311
|
-
return
|
|
319
|
+
if (!ctx.robotsTxt?.parseResult) {
|
|
320
|
+
return createResult({ id: 'ai-robots-anthropic', name: 'Anthropic Claude Access', category: 'ai-search', severity: 'info' }, 'info', 'Not applicable (robots.txt data unavailable)', { recommendation: 'This rule checks if Anthropic/Claude crawlers are allowed' });
|
|
321
|
+
}
|
|
312
322
|
const { userAgentBlocks } = ctx.robotsTxt.parseResult;
|
|
313
323
|
const anthropicBlock = userAgentBlocks.find((b) => b.userAgents.some(ua => ua.toLowerCase().includes('anthropic') ||
|
|
314
324
|
ua.toLowerCase().includes('claude')));
|
|
@@ -323,7 +333,7 @@ export const aiSearchRules = [
|
|
|
323
333
|
});
|
|
324
334
|
}
|
|
325
335
|
}
|
|
326
|
-
return
|
|
336
|
+
return createResult({ id: 'ai-robots-anthropic', name: 'Anthropic Claude Access', category: 'ai-search', severity: 'info' }, 'pass', 'Anthropic/Claude crawler is allowed (default)');
|
|
327
337
|
},
|
|
328
338
|
},
|
|
329
339
|
{
|
|
@@ -333,8 +343,9 @@ export const aiSearchRules = [
|
|
|
333
343
|
severity: 'info',
|
|
334
344
|
description: 'Content updated recently may perform better in AI search',
|
|
335
345
|
check: (ctx) => {
|
|
336
|
-
if (!ctx.lastModified)
|
|
337
|
-
return
|
|
346
|
+
if (!ctx.lastModified) {
|
|
347
|
+
return createResult({ id: 'ai-content-last-modified', name: 'Content Last Modified', category: 'ai-search', severity: 'info' }, 'info', 'Not applicable (Last-Modified header unavailable)', { recommendation: 'This rule checks content freshness via Last-Modified header' });
|
|
348
|
+
}
|
|
338
349
|
const lastModifiedDate = new Date(ctx.lastModified);
|
|
339
350
|
const sixMonthsAgo = new Date();
|
|
340
351
|
sixMonthsAgo.setMonth(sixMonthsAgo.getMonth() - 6);
|
|
@@ -350,7 +361,7 @@ export const aiSearchRules = [
|
|
|
350
361
|
}
|
|
351
362
|
});
|
|
352
363
|
}
|
|
353
|
-
return
|
|
364
|
+
return createResult({ id: 'ai-content-last-modified', name: 'Content Last Modified', category: 'ai-search', severity: 'info' }, 'pass', 'Content has been updated recently');
|
|
354
365
|
},
|
|
355
366
|
},
|
|
356
367
|
{
|
|
@@ -360,8 +371,9 @@ export const aiSearchRules = [
|
|
|
360
371
|
severity: 'info',
|
|
361
372
|
description: 'Very long pages may be truncated by AI search engines',
|
|
362
373
|
check: (ctx) => {
|
|
363
|
-
if (ctx.wordCount === undefined)
|
|
364
|
-
return
|
|
374
|
+
if (ctx.wordCount === undefined) {
|
|
375
|
+
return createResult({ id: 'ai-page-too-long', name: 'Page Too Long for AI', category: 'ai-search', severity: 'info' }, 'info', 'Not applicable (word count data unavailable)', { recommendation: 'This rule checks if page length may be too long for AI processing' });
|
|
376
|
+
}
|
|
365
377
|
if (ctx.wordCount > 10000) {
|
|
366
378
|
return createResult({ id: 'ai-page-too-long', name: 'Page Too Long for AI', category: 'ai-search', severity: 'info' }, 'info', `Page has ${ctx.wordCount.toLocaleString()} words (very long)`, {
|
|
367
379
|
value: ctx.wordCount,
|
|
@@ -383,7 +395,7 @@ export const aiSearchRules = [
|
|
|
383
395
|
}
|
|
384
396
|
});
|
|
385
397
|
}
|
|
386
|
-
return
|
|
398
|
+
return createResult({ id: 'ai-page-too-long', name: 'Page Too Long for AI', category: 'ai-search', severity: 'info' }, 'pass', `Page length is optimal for AI processing (${ctx.wordCount.toLocaleString()} words)`);
|
|
387
399
|
},
|
|
388
400
|
},
|
|
389
401
|
{
|
|
@@ -393,8 +405,9 @@ export const aiSearchRules = [
|
|
|
393
405
|
severity: 'info',
|
|
394
406
|
description: 'Pages should use semantic HTML for better AI understanding',
|
|
395
407
|
check: (ctx) => {
|
|
396
|
-
if (ctx.semanticHtmlRatio === undefined)
|
|
397
|
-
return
|
|
408
|
+
if (ctx.semanticHtmlRatio === undefined) {
|
|
409
|
+
return createResult({ id: 'semantic-html-ratio', name: 'Semantic HTML Ratio', category: 'ai-search', severity: 'info' }, 'info', 'Not applicable (semantic HTML data unavailable)', { recommendation: 'This rule checks for semantic HTML usage for better AI understanding' });
|
|
410
|
+
}
|
|
398
411
|
if (ctx.semanticHtmlRatio < 0.1) {
|
|
399
412
|
return createResult({ id: 'semantic-html-ratio', name: 'Semantic HTML Ratio', category: 'ai-search', severity: 'info' }, 'warn', `Low semantic HTML usage (${(ctx.semanticHtmlRatio * 100).toFixed(1)}%)`, {
|
|
400
413
|
value: ctx.semanticHtmlRatio,
|