recker 1.0.42 → 1.0.43-next.7a08602
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/recker-linux-x64 +0 -0
- package/dist/bin/recker-macos-x64 +0 -0
- package/dist/bin/recker-win-x64.exe +0 -0
- package/dist/bin/rek.cjs +93958 -0
- package/dist/browser/ai/adaptive-timeout.d.ts +50 -0
- package/dist/browser/ai/adaptive-timeout.js +208 -0
- package/dist/browser/ai/client.d.ts +22 -0
- package/dist/browser/ai/client.js +294 -0
- package/dist/browser/ai/index.d.ts +14 -0
- package/dist/browser/ai/index.js +11 -0
- package/dist/browser/ai/providers/anthropic.d.ts +63 -0
- package/dist/browser/ai/providers/anthropic.js +370 -0
- package/dist/browser/ai/providers/base.d.ts +48 -0
- package/dist/browser/ai/providers/base.js +150 -0
- package/dist/browser/ai/providers/google.d.ts +59 -0
- package/dist/browser/ai/providers/google.js +305 -0
- package/dist/browser/ai/providers/ollama.d.ts +44 -0
- package/dist/browser/ai/providers/ollama.js +240 -0
- package/dist/browser/ai/providers/openai.d.ts +64 -0
- package/dist/browser/ai/providers/openai.js +298 -0
- package/dist/browser/ai/rate-limiter.d.ts +43 -0
- package/dist/browser/ai/rate-limiter.js +215 -0
- package/dist/browser/ai/vector/index.d.ts +2 -0
- package/dist/browser/ai/vector/index.js +2 -0
- package/dist/browser/ai/vector/similarity.d.ts +2 -0
- package/dist/browser/ai/vector/similarity.js +27 -0
- package/dist/browser/ai/vector/store.d.ts +27 -0
- package/dist/browser/ai/vector/store.js +82 -0
- package/dist/browser/browser/cache.d.ts +2 -40
- package/dist/browser/browser/cache.js +2 -199
- package/dist/browser/browser/index.d.ts +8 -0
- package/dist/browser/browser/index.js +8 -0
- package/dist/browser/browser/recker.d.ts +8 -1
- package/dist/browser/browser/recker.js +8 -2
- package/dist/browser/cache/indexed-db.d.ts +10 -0
- package/dist/browser/cache/indexed-db.js +88 -0
- package/dist/browser/cache/service-worker-cache.d.ts +18 -0
- package/dist/browser/cache/service-worker-cache.js +103 -0
- package/dist/browser/cache.d.ts +2 -40
- package/dist/browser/cache.js +2 -199
- package/dist/browser/constants/user-agents.d.ts +7 -0
- package/dist/browser/constants/user-agents.js +7 -0
- package/dist/browser/core/client.d.ts +2 -0
- package/dist/browser/core/client.js +19 -1
- package/dist/browser/index.d.ts +8 -0
- package/dist/browser/index.js +8 -0
- package/dist/browser/plugins/har-recorder.d.ts +40 -0
- package/dist/browser/plugins/har-recorder.js +120 -0
- package/dist/browser/plugins/network-simulation.d.ts +7 -0
- package/dist/browser/plugins/network-simulation.js +13 -0
- package/dist/browser/presets/android.d.ts +2 -0
- package/dist/browser/presets/android.js +16 -0
- package/dist/browser/presets/anthropic.d.ts +8 -0
- package/dist/browser/presets/anthropic.js +27 -0
- package/dist/browser/presets/aws.d.ts +19 -0
- package/dist/browser/presets/aws.js +68 -0
- package/dist/browser/presets/azure-openai.d.ts +10 -0
- package/dist/browser/presets/azure-openai.js +35 -0
- package/dist/browser/presets/azure.d.ts +41 -0
- package/dist/browser/presets/azure.js +104 -0
- package/dist/browser/presets/chaturbate.d.ts +2 -0
- package/dist/browser/presets/chaturbate.js +17 -0
- package/dist/browser/presets/cloudflare.d.ts +12 -0
- package/dist/browser/presets/cloudflare.js +39 -0
- package/dist/browser/presets/cohere.d.ts +7 -0
- package/dist/browser/presets/cohere.js +22 -0
- package/dist/browser/presets/deepseek.d.ts +7 -0
- package/dist/browser/presets/deepseek.js +22 -0
- package/dist/browser/presets/digitalocean.d.ts +5 -0
- package/dist/browser/presets/digitalocean.js +16 -0
- package/dist/browser/presets/discord.d.ts +6 -0
- package/dist/browser/presets/discord.js +17 -0
- package/dist/browser/presets/elevenlabs.d.ts +6 -0
- package/dist/browser/presets/elevenlabs.js +20 -0
- package/dist/browser/presets/enhancers.d.ts +20 -0
- package/dist/browser/presets/enhancers.js +85 -0
- package/dist/browser/presets/fireworks.d.ts +7 -0
- package/dist/browser/presets/fireworks.js +22 -0
- package/dist/browser/presets/gcp.d.ts +34 -0
- package/dist/browser/presets/gcp.js +91 -0
- package/dist/browser/presets/gemini.d.ts +7 -0
- package/dist/browser/presets/gemini.js +23 -0
- package/dist/browser/presets/github.d.ts +6 -0
- package/dist/browser/presets/github.js +17 -0
- package/dist/browser/presets/gitlab.d.ts +6 -0
- package/dist/browser/presets/gitlab.js +16 -0
- package/dist/browser/presets/groq.d.ts +7 -0
- package/dist/browser/presets/groq.js +22 -0
- package/dist/browser/presets/hubspot.d.ts +9 -0
- package/dist/browser/presets/hubspot.js +28 -0
- package/dist/browser/presets/huggingface.d.ts +7 -0
- package/dist/browser/presets/huggingface.js +23 -0
- package/dist/browser/presets/index.d.ts +47 -0
- package/dist/browser/presets/index.js +47 -0
- package/dist/browser/presets/ios.d.ts +2 -0
- package/dist/browser/presets/ios.js +13 -0
- package/dist/browser/presets/linear.d.ts +5 -0
- package/dist/browser/presets/linear.js +16 -0
- package/dist/browser/presets/mailgun.d.ts +7 -0
- package/dist/browser/presets/mailgun.js +20 -0
- package/dist/browser/presets/meta.d.ts +10 -0
- package/dist/browser/presets/meta.js +33 -0
- package/dist/browser/presets/mistral.d.ts +7 -0
- package/dist/browser/presets/mistral.js +22 -0
- package/dist/browser/presets/notion.d.ts +6 -0
- package/dist/browser/presets/notion.js +17 -0
- package/dist/browser/presets/openai.d.ts +9 -0
- package/dist/browser/presets/openai.js +30 -0
- package/dist/browser/presets/oracle.d.ts +19 -0
- package/dist/browser/presets/oracle.js +117 -0
- package/dist/browser/presets/perplexity.d.ts +7 -0
- package/dist/browser/presets/perplexity.js +22 -0
- package/dist/browser/presets/pinecone.d.ts +8 -0
- package/dist/browser/presets/pinecone.js +42 -0
- package/dist/browser/presets/registry.d.ts +23 -0
- package/dist/browser/presets/registry.js +519 -0
- package/dist/browser/presets/replicate.d.ts +7 -0
- package/dist/browser/presets/replicate.js +23 -0
- package/dist/browser/presets/sendgrid.d.ts +6 -0
- package/dist/browser/presets/sendgrid.js +20 -0
- package/dist/browser/presets/sentry.d.ts +11 -0
- package/dist/browser/presets/sentry.js +48 -0
- package/dist/browser/presets/sinch.d.ts +9 -0
- package/dist/browser/presets/sinch.js +39 -0
- package/dist/browser/presets/slack.d.ts +5 -0
- package/dist/browser/presets/slack.js +16 -0
- package/dist/browser/presets/square.d.ts +10 -0
- package/dist/browser/presets/square.js +33 -0
- package/dist/browser/presets/stripe.d.ts +7 -0
- package/dist/browser/presets/stripe.js +23 -0
- package/dist/browser/presets/supabase.d.ts +6 -0
- package/dist/browser/presets/supabase.js +18 -0
- package/dist/browser/presets/tiktok.d.ts +10 -0
- package/dist/browser/presets/tiktok.js +38 -0
- package/dist/browser/presets/together.d.ts +7 -0
- package/dist/browser/presets/together.js +22 -0
- package/dist/browser/presets/twilio.d.ts +6 -0
- package/dist/browser/presets/twilio.js +17 -0
- package/dist/browser/presets/vercel.d.ts +6 -0
- package/dist/browser/presets/vercel.js +23 -0
- package/dist/browser/presets/vultr.d.ts +5 -0
- package/dist/browser/presets/vultr.js +16 -0
- package/dist/browser/presets/xai.d.ts +8 -0
- package/dist/browser/presets/xai.js +23 -0
- package/dist/browser/presets/youtube.d.ts +5 -0
- package/dist/browser/presets/youtube.js +20 -0
- package/dist/browser/recker.d.ts +8 -1
- package/dist/browser/recker.js +8 -2
- package/dist/browser/scrape/document.d.ts +5 -4
- package/dist/browser/scrape/document.js +89 -76
- package/dist/browser/scrape/element.d.ts +10 -8
- package/dist/browser/scrape/element.js +295 -81
- package/dist/browser/scrape/extractors.d.ts +11 -11
- package/dist/browser/scrape/extractors.js +145 -113
- package/dist/browser/scrape/parser/back.d.ts +1 -0
- package/dist/browser/scrape/parser/back.js +3 -0
- package/dist/browser/scrape/parser/index.d.ts +20 -0
- package/dist/browser/scrape/parser/index.js +19 -0
- package/dist/browser/scrape/parser/matcher.d.ts +30 -0
- package/dist/browser/scrape/parser/matcher.js +99 -0
- package/dist/browser/scrape/parser/nodes/comment.d.ts +12 -0
- package/dist/browser/scrape/parser/nodes/comment.js +21 -0
- package/dist/browser/scrape/parser/nodes/html.d.ts +110 -0
- package/dist/browser/scrape/parser/nodes/html.js +978 -0
- package/dist/browser/scrape/parser/nodes/node.d.ts +18 -0
- package/dist/browser/scrape/parser/nodes/node.js +31 -0
- package/dist/browser/scrape/parser/nodes/text.d.ts +14 -0
- package/dist/browser/scrape/parser/nodes/text.js +30 -0
- package/dist/browser/scrape/parser/nodes/type.d.ts +6 -0
- package/dist/browser/scrape/parser/nodes/type.js +7 -0
- package/dist/browser/scrape/parser/parse.d.ts +1 -0
- package/dist/browser/scrape/parser/parse.js +1 -0
- package/dist/browser/scrape/parser/valid.d.ts +2 -0
- package/dist/browser/scrape/parser/valid.js +5 -0
- package/dist/browser/scrape/parser/void-tag.d.ts +7 -0
- package/dist/browser/scrape/parser/void-tag.js +43 -0
- package/dist/browser/scrape/types.d.ts +7 -0
- package/dist/browser/seo/analyzer.d.ts +59 -0
- package/dist/browser/seo/analyzer.js +1399 -0
- package/dist/browser/seo/keywords.d.ts +16 -0
- package/dist/browser/seo/keywords.js +55 -0
- package/dist/browser/seo/rules/accessibility.d.ts +2 -0
- package/dist/browser/seo/rules/accessibility.js +722 -0
- package/dist/browser/seo/rules/ai-search.d.ts +2 -0
- package/dist/browser/seo/rules/ai-search.js +436 -0
- package/dist/browser/seo/rules/analytics.d.ts +2 -0
- package/dist/browser/seo/rules/analytics.js +306 -0
- package/dist/browser/seo/rules/best-practices.d.ts +2 -0
- package/dist/browser/seo/rules/best-practices.js +195 -0
- package/dist/browser/seo/rules/canonical.d.ts +12 -0
- package/dist/browser/seo/rules/canonical.js +258 -0
- package/dist/browser/seo/rules/content.d.ts +2 -0
- package/dist/browser/seo/rules/content.js +424 -0
- package/dist/browser/seo/rules/crawl.d.ts +2 -0
- package/dist/browser/seo/rules/crawl.js +435 -0
- package/dist/browser/seo/rules/cwv.d.ts +2 -0
- package/dist/browser/seo/rules/cwv.js +248 -0
- package/dist/browser/seo/rules/ecommerce.d.ts +2 -0
- package/dist/browser/seo/rules/ecommerce.js +283 -0
- package/dist/browser/seo/rules/i18n.d.ts +2 -0
- package/dist/browser/seo/rules/i18n.js +277 -0
- package/dist/browser/seo/rules/images.d.ts +2 -0
- package/dist/browser/seo/rules/images.js +235 -0
- package/dist/browser/seo/rules/index.d.ts +52 -0
- package/dist/browser/seo/rules/index.js +159 -0
- package/dist/browser/seo/rules/internal-linking.d.ts +2 -0
- package/dist/browser/seo/rules/internal-linking.js +394 -0
- package/dist/browser/seo/rules/links.d.ts +2 -0
- package/dist/browser/seo/rules/links.js +490 -0
- package/dist/browser/seo/rules/local.d.ts +2 -0
- package/dist/browser/seo/rules/local.js +289 -0
- package/dist/browser/seo/rules/meta.d.ts +2 -0
- package/dist/browser/seo/rules/meta.js +646 -0
- package/dist/browser/seo/rules/mobile.d.ts +2 -0
- package/dist/browser/seo/rules/mobile.js +161 -0
- package/dist/browser/seo/rules/performance.d.ts +2 -0
- package/dist/browser/seo/rules/performance.js +625 -0
- package/dist/browser/seo/rules/pwa.d.ts +2 -0
- package/dist/browser/seo/rules/pwa.js +299 -0
- package/dist/browser/seo/rules/readability.d.ts +2 -0
- package/dist/browser/seo/rules/readability.js +264 -0
- package/dist/browser/seo/rules/redirects.d.ts +16 -0
- package/dist/browser/seo/rules/redirects.js +199 -0
- package/dist/browser/seo/rules/resources.d.ts +2 -0
- package/dist/browser/seo/rules/resources.js +390 -0
- package/dist/browser/seo/rules/schema.d.ts +2 -0
- package/dist/browser/seo/rules/schema.js +379 -0
- package/dist/browser/seo/rules/security.d.ts +2 -0
- package/dist/browser/seo/rules/security.js +877 -0
- package/dist/browser/seo/rules/social.d.ts +2 -0
- package/dist/browser/seo/rules/social.js +603 -0
- package/dist/browser/seo/rules/structural.d.ts +2 -0
- package/dist/browser/seo/rules/structural.js +179 -0
- package/dist/browser/seo/rules/technical-advanced.d.ts +10 -0
- package/dist/browser/seo/rules/technical-advanced.js +288 -0
- package/dist/browser/seo/rules/technical.d.ts +2 -0
- package/dist/browser/seo/rules/technical.js +480 -0
- package/dist/browser/seo/rules/thresholds.d.ts +196 -0
- package/dist/browser/seo/rules/thresholds.js +118 -0
- package/dist/browser/seo/rules/types.d.ts +498 -0
- package/dist/browser/seo/rules/types.js +11 -0
- package/dist/browser/seo/types.d.ts +211 -0
- package/dist/browser/seo/types.js +1 -0
- package/dist/browser/transport/curl.d.ts +4 -0
- package/dist/browser/transport/curl.js +101 -0
- package/dist/browser/transport/undici.js +1 -2
- package/dist/browser/transport/worker.d.ts +18 -0
- package/dist/browser/transport/worker.js +278 -0
- package/dist/browser/types/index.d.ts +4 -1
- package/dist/browser/utils/binary-manager.d.ts +4 -0
- package/dist/browser/utils/binary-manager.js +72 -0
- package/dist/browser/utils/user-agent.js +2 -13
- package/dist/cache/indexed-db.d.ts +10 -0
- package/dist/cache/indexed-db.js +88 -0
- package/dist/cache/service-worker-cache.d.ts +18 -0
- package/dist/cache/service-worker-cache.js +103 -0
- package/dist/cli/commands/ai.d.ts +2 -0
- package/dist/cli/commands/ai.js +162 -0
- package/dist/cli/commands/bench.d.ts +2 -0
- package/dist/cli/commands/bench.js +51 -0
- package/dist/cli/commands/dns.d.ts +2 -0
- package/dist/cli/commands/dns.js +295 -0
- package/dist/cli/commands/har.d.ts +2 -0
- package/dist/cli/commands/har.js +171 -0
- package/dist/cli/commands/hls.d.ts +2 -0
- package/dist/cli/commands/hls.js +192 -0
- package/dist/cli/commands/network.d.ts +2 -0
- package/dist/cli/commands/network.js +288 -0
- package/dist/cli/commands/protocols.d.ts +2 -0
- package/dist/cli/commands/protocols.js +344 -0
- package/dist/cli/commands/scrape.d.ts +2 -0
- package/dist/cli/commands/scrape.js +176 -0
- package/dist/cli/commands/security.d.ts +2 -0
- package/dist/cli/commands/security.js +57 -0
- package/dist/cli/commands/seo.d.ts +2 -0
- package/dist/cli/commands/seo.js +125 -0
- package/dist/cli/commands/serve.d.ts +2 -0
- package/dist/cli/commands/serve.js +531 -0
- package/dist/cli/commands/spider.d.ts +3 -0
- package/dist/cli/commands/spider.js +456 -0
- package/dist/cli/commands/utils.d.ts +2 -0
- package/dist/cli/commands/utils.js +176 -0
- package/dist/cli/commands/vector.d.ts +2 -0
- package/dist/cli/commands/vector.js +158 -0
- package/dist/cli/handler.d.ts +2 -2
- package/dist/cli/handler.js +6 -6
- package/dist/cli/helpers.d.ts +7 -0
- package/dist/cli/helpers.js +128 -0
- package/dist/cli/index.js +96 -5228
- package/dist/cli/parser/help.d.ts +2 -0
- package/dist/cli/parser/help.js +52 -0
- package/dist/cli/parser/index.d.ts +3 -0
- package/dist/cli/parser/index.js +3 -0
- package/dist/cli/parser/parser.d.ts +4 -0
- package/dist/cli/parser/parser.js +146 -0
- package/dist/cli/parser/types.d.ts +41 -0
- package/dist/cli/parser/types.js +1 -0
- package/dist/cli/presets.d.ts +1 -1
- package/dist/cli/presets.js +1 -1
- package/dist/cli/router.d.ts +36 -0
- package/dist/cli/router.js +195 -0
- package/dist/cli/tui/ai-chat.js +1 -1
- package/dist/cli/tui/commands/context.d.ts +9 -0
- package/dist/cli/tui/commands/context.js +1 -0
- package/dist/cli/tui/commands/dns.d.ts +10 -0
- package/dist/cli/tui/commands/dns.js +461 -0
- package/dist/cli/tui/commands/hls.d.ts +2 -0
- package/dist/cli/tui/commands/hls.js +162 -0
- package/dist/cli/tui/commands/ip.d.ts +2 -0
- package/dist/cli/tui/commands/ip.js +45 -0
- package/dist/cli/tui/commands/network.d.ts +3 -0
- package/dist/cli/tui/commands/network.js +81 -0
- package/dist/cli/tui/commands/protocols.d.ts +6 -0
- package/dist/cli/tui/commands/protocols.js +531 -0
- package/dist/cli/tui/commands/security.d.ts +2 -0
- package/dist/cli/tui/commands/security.js +48 -0
- package/dist/cli/tui/commands/seo.d.ts +2 -0
- package/dist/cli/tui/commands/seo.js +74 -0
- package/dist/cli/tui/context.d.ts +12 -0
- package/dist/cli/tui/context.js +1 -0
- package/dist/cli/tui/shell.d.ts +11 -20
- package/dist/cli/tui/shell.js +216 -1873
- package/dist/constants/user-agents.d.ts +7 -0
- package/dist/constants/user-agents.js +7 -0
- package/dist/core/client.d.ts +2 -0
- package/dist/core/client.js +19 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/mcp/cli.js +2 -3
- package/dist/mcp/data/embeddings.json +1 -0
- package/dist/mcp/tools/network.js +298 -158
- package/dist/plugins/har-player.d.ts +23 -0
- package/dist/plugins/har-player.js +49 -0
- package/dist/plugins/har-recorder.d.ts +37 -3
- package/dist/plugins/har-recorder.js +116 -63
- package/dist/plugins/network-simulation.d.ts +7 -0
- package/dist/plugins/network-simulation.js +13 -0
- package/dist/presets/android.d.ts +2 -0
- package/dist/presets/android.js +16 -0
- package/dist/presets/chaturbate.d.ts +2 -0
- package/dist/presets/chaturbate.js +17 -0
- package/dist/presets/elevenlabs.d.ts +6 -0
- package/dist/presets/elevenlabs.js +20 -0
- package/dist/presets/enhancers.d.ts +20 -0
- package/dist/presets/enhancers.js +85 -0
- package/dist/presets/hubspot.d.ts +9 -0
- package/dist/presets/hubspot.js +28 -0
- package/dist/presets/index.d.ts +10 -0
- package/dist/presets/index.js +10 -0
- package/dist/presets/ios.d.ts +2 -0
- package/dist/presets/ios.js +13 -0
- package/dist/presets/pinecone.d.ts +8 -0
- package/dist/presets/pinecone.js +42 -0
- package/dist/presets/registry.js +60 -0
- package/dist/presets/sendgrid.d.ts +6 -0
- package/dist/presets/sendgrid.js +20 -0
- package/dist/presets/sentry.d.ts +11 -0
- package/dist/presets/sentry.js +48 -0
- package/dist/presets/square.d.ts +10 -0
- package/dist/presets/square.js +33 -0
- package/dist/recker.d.ts +3 -0
- package/dist/recker.js +4 -0
- package/dist/scrape/document.d.ts +5 -4
- package/dist/scrape/document.js +89 -76
- package/dist/scrape/element.d.ts +10 -8
- package/dist/scrape/element.js +295 -81
- package/dist/scrape/extractors.d.ts +11 -11
- package/dist/scrape/extractors.js +145 -113
- package/dist/scrape/index.d.ts +2 -0
- package/dist/scrape/index.js +1 -0
- package/dist/scrape/parser/back.d.ts +1 -0
- package/dist/scrape/parser/back.js +3 -0
- package/dist/scrape/parser/index.d.ts +20 -0
- package/dist/scrape/parser/index.js +19 -0
- package/dist/scrape/parser/matcher.d.ts +30 -0
- package/dist/scrape/parser/matcher.js +99 -0
- package/dist/scrape/parser/nodes/comment.d.ts +12 -0
- package/dist/scrape/parser/nodes/comment.js +21 -0
- package/dist/scrape/parser/nodes/html.d.ts +110 -0
- package/dist/scrape/parser/nodes/html.js +978 -0
- package/dist/scrape/parser/nodes/node.d.ts +18 -0
- package/dist/scrape/parser/nodes/node.js +31 -0
- package/dist/scrape/parser/nodes/text.d.ts +14 -0
- package/dist/scrape/parser/nodes/text.js +30 -0
- package/dist/scrape/parser/nodes/type.d.ts +6 -0
- package/dist/scrape/parser/nodes/type.js +7 -0
- package/dist/scrape/parser/parse.d.ts +1 -0
- package/dist/scrape/parser/parse.js +1 -0
- package/dist/scrape/parser/valid.d.ts +2 -0
- package/dist/scrape/parser/valid.js +5 -0
- package/dist/scrape/parser/void-tag.d.ts +7 -0
- package/dist/scrape/parser/void-tag.js +43 -0
- package/dist/scrape/spider.d.ts +19 -0
- package/dist/scrape/spider.js +28 -3
- package/dist/scrape/types.d.ts +7 -0
- package/dist/seo/analyzer.d.ts +15 -5
- package/dist/seo/analyzer.js +636 -175
- package/dist/seo/formatter.d.ts +16 -0
- package/dist/seo/formatter.js +228 -0
- package/dist/seo/index.d.ts +2 -0
- package/dist/seo/index.js +1 -0
- package/dist/seo/keywords.d.ts +16 -0
- package/dist/seo/keywords.js +55 -0
- package/dist/seo/rules/accessibility.js +85 -57
- package/dist/seo/rules/ai-search.js +44 -31
- package/dist/seo/rules/analytics.d.ts +2 -0
- package/dist/seo/rules/analytics.js +306 -0
- package/dist/seo/rules/best-practices.js +21 -14
- package/dist/seo/rules/canonical.js +31 -22
- package/dist/seo/rules/content.js +207 -19
- package/dist/seo/rules/crawl.js +55 -40
- package/dist/seo/rules/cwv.js +21 -15
- package/dist/seo/rules/ecommerce.js +51 -20
- package/dist/seo/rules/i18n.js +61 -33
- package/dist/seo/rules/images.js +87 -28
- package/dist/seo/rules/index.js +2 -0
- package/dist/seo/rules/internal-linking.js +58 -39
- package/dist/seo/rules/links.js +70 -51
- package/dist/seo/rules/local.js +49 -25
- package/dist/seo/rules/meta.js +161 -62
- package/dist/seo/rules/mobile.js +112 -2
- package/dist/seo/rules/performance.js +309 -54
- package/dist/seo/rules/pwa.js +36 -39
- package/dist/seo/rules/readability.js +31 -22
- package/dist/seo/rules/redirects.js +21 -15
- package/dist/seo/rules/resources.js +59 -42
- package/dist/seo/rules/schema.js +333 -8
- package/dist/seo/rules/security.js +142 -80
- package/dist/seo/rules/social.js +277 -47
- package/dist/seo/rules/structural.js +40 -16
- package/dist/seo/rules/technical-advanced.js +27 -22
- package/dist/seo/rules/technical.js +243 -42
- package/dist/seo/rules/types.d.ts +53 -1
- package/dist/seo/seo-spider.d.ts +22 -0
- package/dist/seo/seo-spider.js +77 -13
- package/dist/seo/types.d.ts +8 -1
- package/dist/seo/validators/llms-txt.js +19 -0
- package/dist/seo/validators/rss.d.ts +11 -0
- package/dist/seo/validators/rss.js +93 -0
- package/dist/seo/validators/sitemap.js +36 -26
- package/dist/transport/curl.d.ts +4 -0
- package/dist/transport/curl.js +101 -0
- package/dist/transport/udp.js +0 -1
- package/dist/transport/undici.js +1 -2
- package/dist/transport/worker.d.ts +18 -0
- package/dist/transport/worker.js +278 -0
- package/dist/types/index.d.ts +4 -1
- package/dist/utils/binary-manager.d.ts +4 -0
- package/dist/utils/binary-manager.js +72 -0
- package/dist/utils/optional-require.d.ts +7 -8
- package/dist/utils/optional-require.js +2 -21
- package/dist/utils/upload.d.ts +6 -0
- package/dist/utils/upload.js +11 -0
- package/dist/utils/user-agent.js +2 -13
- package/dist/version.js +1 -1
- package/package.json +14 -5
- package/dist/browser/utils/optional-require.d.ts +0 -19
- package/dist/browser/utils/optional-require.js +0 -105
|
@@ -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,8 +395,9 @@ 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',
|
|
@@ -402,8 +418,9 @@ export const accessibilityRules = [
|
|
|
402
418
|
severity: 'info',
|
|
403
419
|
description: 'Page should contain a heading, skip link, or landmark region',
|
|
404
420
|
check: (ctx) => {
|
|
405
|
-
if (ctx.hasSkipLink === undefined && ctx.hasMain === undefined && ctx.h1Count === undefined)
|
|
406
|
-
return
|
|
421
|
+
if (ctx.hasSkipLink === undefined && ctx.hasMain === undefined && ctx.h1Count === undefined) {
|
|
422
|
+
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' });
|
|
423
|
+
}
|
|
407
424
|
const hasSkip = ctx.hasSkipLink ?? false;
|
|
408
425
|
const hasMain = ctx.hasMain ?? false;
|
|
409
426
|
const hasH1 = (ctx.h1Count ?? 0) > 0;
|
|
@@ -427,8 +444,9 @@ export const accessibilityRules = [
|
|
|
427
444
|
severity: 'info',
|
|
428
445
|
description: 'Data tables should have caption or aria-label',
|
|
429
446
|
check: (ctx) => {
|
|
430
|
-
if (ctx.tablesWithoutCaption === undefined)
|
|
431
|
-
return
|
|
447
|
+
if (ctx.tablesWithoutCaption === undefined) {
|
|
448
|
+
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' });
|
|
449
|
+
}
|
|
432
450
|
const count = ctx.tablesWithoutCaption;
|
|
433
451
|
if (count > 0) {
|
|
434
452
|
return createResult({ id: 'a11y-table-caption', name: 'Table Caption', category: 'accessibility', severity: 'info' }, 'info', `${count} table(s) without caption or aria-label`, {
|
|
@@ -451,8 +469,9 @@ export const accessibilityRules = [
|
|
|
451
469
|
severity: 'warning',
|
|
452
470
|
description: '<frame> or <iframe> elements must have a title',
|
|
453
471
|
check: (ctx) => {
|
|
454
|
-
if (ctx.iframesWithoutTitle === undefined)
|
|
455
|
-
return
|
|
472
|
+
if (ctx.iframesWithoutTitle === undefined) {
|
|
473
|
+
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' });
|
|
474
|
+
}
|
|
456
475
|
const count = ctx.iframesWithoutTitle;
|
|
457
476
|
if (count > 0) {
|
|
458
477
|
return createResult({ id: 'a11y-iframe-title', name: 'Iframe Title', category: 'accessibility', severity: 'warning' }, 'warn', `${count} iframe(s) without title attribute`, {
|
|
@@ -476,8 +495,9 @@ export const accessibilityRules = [
|
|
|
476
495
|
severity: 'warning',
|
|
477
496
|
description: 'SVGs should have <title> or aria-label for accessibility',
|
|
478
497
|
check: (ctx) => {
|
|
479
|
-
if (ctx.svgsWithoutTitle === undefined)
|
|
480
|
-
return
|
|
498
|
+
if (ctx.svgsWithoutTitle === undefined) {
|
|
499
|
+
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' });
|
|
500
|
+
}
|
|
481
501
|
const count = ctx.svgsWithoutTitle;
|
|
482
502
|
if (count > 0) {
|
|
483
503
|
return createResult({ id: 'a11y-svg-title', name: 'SVG Title', category: 'accessibility', severity: 'warning' }, 'warn', `${count} SVG(s) without accessible title`, {
|
|
@@ -500,8 +520,9 @@ export const accessibilityRules = [
|
|
|
500
520
|
severity: 'error',
|
|
501
521
|
description: '[user-scalable="no"] should not be used and maximum-scale should not be less than 5',
|
|
502
522
|
check: (ctx) => {
|
|
503
|
-
if (ctx.viewportContent === undefined)
|
|
504
|
-
return
|
|
523
|
+
if (ctx.viewportContent === undefined) {
|
|
524
|
+
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' });
|
|
525
|
+
}
|
|
505
526
|
const viewport = ctx.viewportContent.toLowerCase();
|
|
506
527
|
const hasUserScalableNo = viewport.includes('user-scalable=no') || viewport.includes('user-scalable=0');
|
|
507
528
|
const maxScaleMatch = viewport.match(/maximum-scale\s*=\s*([\d.]+)/);
|
|
@@ -527,8 +548,9 @@ export const accessibilityRules = [
|
|
|
527
548
|
severity: 'error',
|
|
528
549
|
description: 'Document must have a <title> element',
|
|
529
550
|
check: (ctx) => {
|
|
530
|
-
if (ctx.title === undefined)
|
|
531
|
-
return
|
|
551
|
+
if (ctx.title === undefined) {
|
|
552
|
+
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' });
|
|
553
|
+
}
|
|
532
554
|
if (!ctx.title || ctx.title.trim().length === 0) {
|
|
533
555
|
return createResult({ id: 'a11y-document-title', name: 'Document Title', category: 'accessibility', severity: 'error' }, 'fail', 'Document does not have a <title> element', {
|
|
534
556
|
recommendation: 'Add a descriptive <title> element to the document',
|
|
@@ -549,8 +571,9 @@ export const accessibilityRules = [
|
|
|
549
571
|
severity: 'error',
|
|
550
572
|
description: '<html> element must have a [lang] attribute',
|
|
551
573
|
check: (ctx) => {
|
|
552
|
-
if (ctx.hasLang === undefined)
|
|
553
|
-
return
|
|
574
|
+
if (ctx.hasLang === undefined) {
|
|
575
|
+
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' });
|
|
576
|
+
}
|
|
554
577
|
if (!ctx.hasLang) {
|
|
555
578
|
return createResult({ id: 'a11y-html-lang', name: 'HTML Lang Attribute', category: 'accessibility', severity: 'error' }, 'fail', '<html> element does not have a [lang] attribute', {
|
|
556
579
|
recommendation: 'Add lang attribute to html element (e.g., <html lang="en">)',
|
|
@@ -571,8 +594,9 @@ export const accessibilityRules = [
|
|
|
571
594
|
severity: 'error',
|
|
572
595
|
description: '<html> element must have a valid value for its [lang] attribute',
|
|
573
596
|
check: (ctx) => {
|
|
574
|
-
if (!ctx.hasLang || !ctx.langValue)
|
|
575
|
-
return
|
|
597
|
+
if (!ctx.hasLang || !ctx.langValue) {
|
|
598
|
+
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' });
|
|
599
|
+
}
|
|
576
600
|
const validLangPattern = /^[a-z]{2,3}(-[A-Za-z]{2,4})?(-[A-Za-z0-9]{2,})?$/i;
|
|
577
601
|
if (!validLangPattern.test(ctx.langValue)) {
|
|
578
602
|
return createResult({ id: 'a11y-html-lang-valid', name: 'Valid Lang Attribute', category: 'accessibility', severity: 'error' }, 'fail', `Invalid lang attribute value: ${ctx.langValue}`, {
|
|
@@ -595,8 +619,9 @@ export const accessibilityRules = [
|
|
|
595
619
|
severity: 'warning',
|
|
596
620
|
description: '<video> elements should contain a <track> element with [kind="captions"]',
|
|
597
621
|
check: (ctx) => {
|
|
598
|
-
if (ctx.videoCount === undefined || ctx.videosWithCaptions === undefined)
|
|
599
|
-
return
|
|
622
|
+
if (ctx.videoCount === undefined || ctx.videosWithCaptions === undefined) {
|
|
623
|
+
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' });
|
|
624
|
+
}
|
|
600
625
|
const videos = ctx.videoCount;
|
|
601
626
|
const withCaptions = ctx.videosWithCaptions;
|
|
602
627
|
if (videos > 0 && withCaptions < videos) {
|
|
@@ -614,7 +639,7 @@ export const accessibilityRules = [
|
|
|
614
639
|
if (videos > 0) {
|
|
615
640
|
return createResult({ id: 'a11y-video-captions', name: 'Video Captions', category: 'accessibility', severity: 'warning' }, 'pass', `All ${videos} video(s) have captions`);
|
|
616
641
|
}
|
|
617
|
-
return
|
|
642
|
+
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
643
|
},
|
|
619
644
|
},
|
|
620
645
|
{
|
|
@@ -624,8 +649,9 @@ export const accessibilityRules = [
|
|
|
624
649
|
severity: 'warning',
|
|
625
650
|
description: 'Lists must contain only <li> elements and script supporting elements',
|
|
626
651
|
check: (ctx) => {
|
|
627
|
-
if (ctx.invalidListStructure === undefined)
|
|
628
|
-
return
|
|
652
|
+
if (ctx.invalidListStructure === undefined) {
|
|
653
|
+
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)' });
|
|
654
|
+
}
|
|
629
655
|
const count = ctx.invalidListStructure;
|
|
630
656
|
if (count > 0) {
|
|
631
657
|
return createResult({ id: 'a11y-list-structure', name: 'List Structure', category: 'accessibility', severity: 'warning' }, 'warn', `${count} list(s) have invalid structure`, {
|
|
@@ -649,8 +675,9 @@ export const accessibilityRules = [
|
|
|
649
675
|
severity: 'warning',
|
|
650
676
|
description: 'All heading elements must contain content',
|
|
651
677
|
check: (ctx) => {
|
|
652
|
-
if (ctx.emptyHeadings === undefined)
|
|
653
|
-
return
|
|
678
|
+
if (ctx.emptyHeadings === undefined) {
|
|
679
|
+
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' });
|
|
680
|
+
}
|
|
654
681
|
const count = ctx.emptyHeadings;
|
|
655
682
|
if (count > 0) {
|
|
656
683
|
return createResult({ id: 'a11y-headings-content', name: 'Heading Content', category: 'accessibility', severity: 'warning' }, 'warn', `${count} empty heading element(s) found`, {
|
|
@@ -674,8 +701,9 @@ export const accessibilityRules = [
|
|
|
674
701
|
severity: 'info',
|
|
675
702
|
description: 'Image alt attributes should not be redundant',
|
|
676
703
|
check: (ctx) => {
|
|
677
|
-
if (ctx.imagesWithRedundantAlt === undefined)
|
|
678
|
-
return
|
|
704
|
+
if (ctx.imagesWithRedundantAlt === undefined) {
|
|
705
|
+
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"' });
|
|
706
|
+
}
|
|
679
707
|
const count = ctx.imagesWithRedundantAlt;
|
|
680
708
|
if (count > 0) {
|
|
681
709
|
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,
|