blume 2.0.2 → 2.1.0
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/AGENTS.md +1 -0
- package/CHANGELOG.md +113 -0
- package/README.md +1 -1
- package/dist/cli/{chunk-6k4ftwze.js → chunk-0gz36akr.js} +18 -2
- package/dist/cli/{chunk-6k4ftwze.js.map → chunk-0gz36akr.js.map} +3 -3
- package/dist/cli/{chunk-zp79m0ts.js → chunk-1n228xqb.js} +2 -2
- package/dist/cli/{chunk-ddndchfr.js → chunk-2f3ndkpw.js} +57 -29
- package/dist/cli/chunk-2f3ndkpw.js.map +14 -0
- package/dist/cli/{chunk-fsmrqk8a.js → chunk-2f7n4qzt.js} +8 -2
- package/dist/cli/{chunk-fsmrqk8a.js.map → chunk-2f7n4qzt.js.map} +3 -3
- package/dist/cli/{chunk-ppzjqwx2.js → chunk-2n4ps1b3.js} +17 -18
- package/dist/cli/{chunk-ppzjqwx2.js.map → chunk-2n4ps1b3.js.map} +3 -3
- package/dist/cli/{chunk-5r8g91qn.js → chunk-43zcf4gz.js} +398 -159
- package/dist/cli/chunk-43zcf4gz.js.map +36 -0
- package/dist/cli/chunk-4ejxbzee.js +39 -0
- package/dist/cli/chunk-4ejxbzee.js.map +10 -0
- package/dist/cli/{chunk-hr8ne106.js → chunk-4h8z9vv5.js} +296 -77
- package/dist/cli/chunk-4h8z9vv5.js.map +16 -0
- package/dist/cli/{chunk-k7pj68a8.js → chunk-5cg2rzgf.js} +18 -16
- package/dist/cli/{chunk-k7pj68a8.js.map → chunk-5cg2rzgf.js.map} +2 -2
- package/dist/cli/{chunk-h2ez8dzb.js → chunk-5ffywsmb.js} +4 -4
- package/dist/cli/{chunk-g698a744.js → chunk-5wj2qwn2.js} +5 -5
- package/dist/cli/chunk-6bm0xpgd.js +18 -0
- package/dist/cli/chunk-6bm0xpgd.js.map +10 -0
- package/dist/cli/{chunk-3yce002v.js → chunk-713mngdq.js} +15 -8
- package/dist/cli/chunk-713mngdq.js.map +14 -0
- package/dist/cli/{chunk-91ws1n6j.js → chunk-9geymqns.js} +17 -15
- package/dist/cli/{chunk-91ws1n6j.js.map → chunk-9geymqns.js.map} +2 -2
- package/dist/cli/{chunk-6dtt0zfn.js → chunk-9xr4dgee.js} +27 -18
- package/dist/cli/chunk-9xr4dgee.js.map +10 -0
- package/dist/cli/{chunk-sqw4ekg1.js → chunk-amrb6hgh.js} +2 -2
- package/dist/cli/chunk-cm89scdv.js +443 -0
- package/dist/cli/chunk-cm89scdv.js.map +11 -0
- package/dist/cli/{chunk-wdrt2k2v.js → chunk-cmtcnh9f.js} +90 -26
- package/dist/cli/chunk-cmtcnh9f.js.map +10 -0
- package/dist/cli/chunk-cy5bm0j0.js +104 -0
- package/dist/cli/chunk-cy5bm0j0.js.map +11 -0
- package/dist/cli/{chunk-7mbqtmgb.js → chunk-e7vvk5ya.js} +21 -12
- package/dist/cli/chunk-e7vvk5ya.js.map +10 -0
- package/dist/cli/{chunk-qkb5a8sa.js → chunk-g2z9x01v.js} +6 -98
- package/dist/cli/chunk-g2z9x01v.js.map +10 -0
- package/dist/cli/{chunk-273ygyr4.js → chunk-ga0pf4aj.js} +7 -7
- package/dist/cli/{chunk-273ygyr4.js.map → chunk-ga0pf4aj.js.map} +3 -3
- package/dist/cli/{chunk-h7k3nq3v.js → chunk-gp84za0h.js} +2 -2
- package/dist/cli/{chunk-hqp2ajnh.js → chunk-h2a48yhd.js} +195 -73
- package/dist/cli/chunk-h2a48yhd.js.map +36 -0
- package/dist/cli/{chunk-pbg5a4s3.js → chunk-jnwj5kjj.js} +71 -47
- package/dist/cli/chunk-jnwj5kjj.js.map +19 -0
- package/dist/cli/chunk-kwrg63tm.js +31 -0
- package/dist/cli/chunk-kwrg63tm.js.map +10 -0
- package/dist/cli/{chunk-8g8ytmgx.js → chunk-nd1821sk.js} +21 -19
- package/dist/cli/{chunk-8g8ytmgx.js.map → chunk-nd1821sk.js.map} +2 -2
- package/dist/cli/{chunk-w4bxdvsa.js → chunk-ntv4qrt7.js} +17 -15
- package/dist/cli/{chunk-w4bxdvsa.js.map → chunk-ntv4qrt7.js.map} +2 -2
- package/dist/cli/{chunk-bfwp9vp6.js → chunk-p804f3pe.js} +7 -7
- package/dist/cli/{chunk-4e9b9ra6.js → chunk-py62fewh.js} +6 -6
- package/dist/cli/{chunk-4e9b9ra6.js.map → chunk-py62fewh.js.map} +1 -1
- package/dist/cli/chunk-q3wxxq39.js +126 -0
- package/dist/cli/chunk-q3wxxq39.js.map +11 -0
- package/dist/cli/{chunk-j85scx15.js → chunk-rekqz4qg.js} +142 -46
- package/dist/cli/chunk-rekqz4qg.js.map +10 -0
- package/dist/cli/{chunk-00gs3wqs.js → chunk-ta27feev.js} +1 -1
- package/dist/cli/{chunk-v6ya5kcb.js → chunk-xkz5xbtv.js} +4031 -1132
- package/dist/cli/chunk-xkz5xbtv.js.map +213 -0
- package/dist/cli/{chunk-bbnwccaz.js → chunk-y4ykbark.js} +2 -2
- package/dist/cli/{chunk-n1yg3tj3.js → chunk-y5h4r2y0.js} +6 -6
- package/dist/cli/{chunk-n1yg3tj3.js.map → chunk-y5h4r2y0.js.map} +3 -3
- package/dist/cli/{chunk-nfcyttvj.js → chunk-yj17vs39.js} +9 -9
- package/dist/cli/{chunk-nfcyttvj.js.map → chunk-yj17vs39.js.map} +1 -1
- package/dist/cli/{chunk-7vtckvaw.js → chunk-yxmk4pec.js} +16 -14
- package/dist/cli/{chunk-7vtckvaw.js.map → chunk-yxmk4pec.js.map} +2 -2
- package/dist/cli/{chunk-gs7r695n.js → chunk-zccbjcj0.js} +22 -8
- package/dist/cli/{chunk-gs7r695n.js.map → chunk-zccbjcj0.js.map} +3 -3
- package/dist/cli/{chunk-xh43dwgw.js → chunk-zf748avv.js} +415 -225
- package/dist/cli/chunk-zf748avv.js.map +37 -0
- package/dist/cli/{chunk-d1v5rhy0.js → chunk-zhvd6adm.js} +16 -14
- package/dist/cli/{chunk-d1v5rhy0.js.map → chunk-zhvd6adm.js.map} +2 -2
- package/dist/cli/index.js +28 -20
- package/dist/cli/index.js.map +5 -5
- package/dist/types/ai/agent-surface.d.ts +8 -7
- package/dist/types/ai/ai-catalog.d.ts +7 -0
- package/dist/types/ai/ask-context.d.ts +22 -0
- package/dist/types/ai/ask.d.ts +271 -58
- package/dist/types/ai/index.d.ts +2 -2
- package/dist/types/ai/link-headers.d.ts +8 -1
- package/dist/types/ai/openapi-components.d.ts +5 -2
- package/dist/types/ai/relative-links.d.ts +9 -7
- package/dist/types/ai/site-skill.d.ts +26 -0
- package/dist/types/ai/skills.d.ts +20 -1
- package/dist/types/ai/tar.d.ts +1 -3
- package/dist/types/analytics/index.d.ts +2 -0
- package/dist/types/analytics/one-dollar-stats.d.ts +48 -0
- package/dist/types/analytics/schema.d.ts +14 -0
- package/dist/types/astro/integration.d.ts +11 -2
- package/dist/types/captcha/hcaptcha.d.ts +27 -0
- package/dist/types/captcha/index.d.ts +28 -0
- package/dist/types/captcha/schema.d.ts +29 -0
- package/dist/types/captcha/turnstile.d.ts +28 -0
- package/dist/types/components/content/api-field.d.ts +59 -0
- package/dist/types/components/content/api-page.d.ts +95 -0
- package/dist/types/components/layout/nav-utils.d.ts +26 -0
- package/dist/types/components/openapi/helpers.d.ts +137 -0
- package/dist/types/components/openapi/security.d.ts +85 -0
- package/dist/types/consent/ethyca.d.ts +48 -0
- package/dist/types/consent/index.d.ts +37 -0
- package/dist/types/consent/native.d.ts +28 -0
- package/dist/types/consent/osano.d.ts +42 -0
- package/dist/types/consent/schema.d.ts +110 -0
- package/dist/types/core/base-path.d.ts +21 -7
- package/dist/types/core/config-input.d.ts +196 -27
- package/dist/types/core/data.d.ts +49 -4
- package/dist/types/core/directive-diagnostics.d.ts +12 -0
- package/dist/types/core/footer.d.ts +8 -0
- package/dist/types/core/heading-markers.d.ts +5 -7
- package/dist/types/core/i18n-ui.d.ts +86 -0
- package/dist/types/core/i18n.d.ts +2 -0
- package/dist/types/core/includes.d.ts +8 -2
- package/dist/types/core/last-modified.d.ts +10 -0
- package/dist/types/core/localizable.d.ts +7 -0
- package/dist/types/core/meta.d.ts +8 -0
- package/dist/types/core/page-modes.d.ts +29 -0
- package/dist/types/core/redirect-patterns.d.ts +144 -0
- package/dist/types/core/related.d.ts +40 -0
- package/dist/types/core/schema.d.ts +558 -56
- package/dist/types/core/sources/lower.d.ts +29 -16
- package/dist/types/core/sources/normalize.d.ts +13 -1
- package/dist/types/core/sources/read.d.ts +5 -0
- package/dist/types/core/sources/watch.d.ts +12 -5
- package/dist/types/core/standard-schema.d.ts +5 -0
- package/dist/types/core/types.d.ts +1 -1
- package/dist/types/core/variables.d.ts +42 -0
- package/dist/types/deploy/artifacts.d.ts +6 -4
- package/dist/types/deploy/headers.d.ts +5 -0
- package/dist/types/deploy/node-headers.d.ts +13 -2
- package/dist/types/deploy/platforms/cloudflare.d.ts +5 -0
- package/dist/types/deploy/platforms/netlify.d.ts +7 -0
- package/dist/types/deploy/platforms/types.d.ts +8 -0
- package/dist/types/deploy/platforms/vercel.d.ts +6 -0
- package/dist/types/deploy/redirects.d.ts +61 -22
- package/dist/types/deploy/vercel-negotiation.d.ts +9 -0
- package/dist/types/markdown/code-title.d.ts +17 -0
- package/dist/types/markdown/directives.d.ts +62 -0
- package/dist/types/markdown/features.d.ts +24 -2
- package/dist/types/markdown/mdast.d.ts +63 -0
- package/dist/types/narration/build.d.ts +70 -0
- package/dist/types/narration/provider.d.ts +26 -0
- package/dist/types/narration/script.d.ts +154 -0
- package/dist/types/openapi/asyncapi.d.ts +4 -2
- package/dist/types/openapi/model.d.ts +12 -6
- package/dist/types/openapi/overlay.d.ts +54 -0
- package/dist/types/openapi/parse.d.ts +13 -0
- package/dist/types/openapi/references.d.ts +14 -4
- package/dist/types/ratelimit/cloudflare.d.ts +39 -0
- package/dist/types/ratelimit/index.d.ts +27 -0
- package/dist/types/ratelimit/memory.d.ts +36 -0
- package/dist/types/ratelimit/schema.d.ts +111 -0
- package/dist/types/ratelimit/upstash.d.ts +21 -0
- package/dist/types/ratelimit/wrangler.d.ts +23 -0
- package/dist/types/reference/asyncapi.d.ts +10 -10
- package/dist/types/reference/graphql.d.ts +13 -10
- package/dist/types/reference/openapi.d.ts +33 -12
- package/dist/types/reference/options.d.ts +45 -5
- package/dist/types/reference/scalar.d.ts +13 -0
- package/dist/types/reference/schema.d.ts +60 -36
- package/dist/types/search/documents.d.ts +12 -1
- package/dist/types/search/orama-index.d.ts +4 -0
- package/dist/types/seo/metatags.d.ts +20 -0
- package/docs/02-deployment.mdx +44 -6
- package/docs/03-upgrading.mdx +4 -4
- package/docs/04-migrating.mdx +1 -1
- package/docs/08-faq.mdx +1 -1
- package/docs/advanced/changelog.mdx +2 -2
- package/docs/advanced/custom-pages.mdx +3 -3
- package/docs/advanced/skills.mdx +14 -0
- package/docs/cli/audit.mdx +38 -2
- package/docs/cli/evals.mdx +3 -3
- package/docs/cli/index.mdx +1 -0
- package/docs/cli/translate.mdx +3 -3
- package/docs/cli/version.mdx +1 -1
- package/docs/configuration/analytics.mdx +22 -1
- package/docs/configuration/assistant.mdx +188 -36
- package/docs/configuration/consent.mdx +100 -0
- package/docs/configuration/customization.mdx +6 -4
- package/docs/configuration/index.mdx +63 -5
- package/docs/configuration/meta.ts +3 -0
- package/docs/configuration/narration.mdx +119 -0
- package/docs/configuration/rate-limiting.mdx +69 -0
- package/docs/configuration/search.mdx +30 -2
- package/docs/configuration/theming.mdx +3 -1
- package/docs/content/components.mdx +107 -1
- package/docs/content/frontmatter.mdx +77 -1
- package/docs/content/i18n.mdx +40 -3
- package/docs/content/includes.mdx +16 -0
- package/docs/content/index.mdx +4 -2
- package/docs/content/islands.mdx +1 -1
- package/docs/content/meta.mdx +4 -1
- package/docs/content/meta.ts +1 -0
- package/docs/content/navigation.mdx +28 -6
- package/docs/content/sources/contentful.mdx +32 -0
- package/docs/content/sources/custom.mdx +39 -0
- package/docs/content/sources/github-releases.mdx +31 -0
- package/docs/content/sources/index.mdx +91 -0
- package/docs/content/sources/meta.ts +16 -0
- package/docs/content/sources/notion.mdx +32 -0
- package/docs/content/sources/obsidian.mdx +37 -0
- package/docs/content/sources/payload.mdx +30 -0
- package/docs/content/sources/remote-mdx.mdx +40 -0
- package/docs/content/sources/sanity.mdx +29 -0
- package/docs/content/sources/strapi.mdx +31 -0
- package/docs/content/syntax.mdx +81 -2
- package/docs/content/variables.mdx +41 -0
- package/docs/content/versioning.mdx +1 -1
- package/docs/discoverability/agent-discovery.mdx +30 -3
- package/docs/discoverability/index.mdx +3 -2
- package/docs/discoverability/llms-txt.mdx +1 -1
- package/docs/discoverability/markdown.mdx +2 -2
- package/docs/discoverability/metadata.mdx +17 -0
- package/docs/discoverability/open-graph.mdx +5 -3
- package/docs/discoverability/rss.mdx +1 -1
- package/docs/references/api-pages.mdx +74 -0
- package/docs/references/asyncapi.mdx +2 -2
- package/docs/references/graphql.mdx +3 -3
- package/docs/references/meta.ts +1 -1
- package/docs/references/openapi.mdx +106 -5
- package/docs/references/scalar.mdx +3 -1
- package/package.json +36 -1
- package/skills/blume/SKILL.md +3 -0
- package/skills/blume-migrate/SKILL.md +5 -5
- package/skills/blume-migrate/references/docusaurus.md +3 -3
- package/skills/blume-migrate/references/fumadocs.md +1 -1
- package/skills/blume-migrate/references/mintlify.md +37 -19
- package/skills/blume-migrate/references/nextra.md +3 -3
- package/skills/blume-migrate/references/starlight.md +2 -1
- package/skills/blume-migrate/scripts/mintlify-codemod.mjs +67 -16
- package/skills/blume-write-skill/SKILL.md +82 -0
- package/src/ai/agent-readability.ts +4 -3
- package/src/ai/agent-surface.ts +9 -7
- package/src/ai/ai-catalog.ts +12 -5
- package/src/ai/api/handlers.ts +2 -2
- package/src/ai/api/spec.ts +2 -2
- package/src/ai/api-catalog.ts +6 -2
- package/src/ai/ask-context.ts +73 -20
- package/src/ai/ask-data.ts +6 -0
- package/src/ai/ask-tools.ts +142 -0
- package/src/ai/ask.ts +354 -76
- package/src/ai/changelog-markdown.ts +2 -2
- package/src/ai/component-markdown.ts +75 -0
- package/src/ai/cors.ts +2 -0
- package/src/ai/index.ts +12 -0
- package/src/ai/link-headers.ts +14 -5
- package/src/ai/llms.ts +24 -9
- package/src/ai/markdown.ts +2 -1
- package/src/ai/mcp/data.ts +6 -0
- package/src/ai/mcp/discovery.ts +2 -2
- package/src/ai/mcp/query.ts +2 -2
- package/src/ai/mcp/server.ts +2 -2
- package/src/ai/openapi-components.ts +31 -9
- package/src/ai/relative-links.ts +42 -26
- package/src/ai/serializers.ts +2 -1
- package/src/ai/site-skill.ts +274 -0
- package/src/ai/skills.ts +49 -3
- package/src/ai/tar.ts +139 -12
- package/src/ai/write-skill.ts +97 -0
- package/src/analytics/head.ts +4 -0
- package/src/analytics/index.ts +5 -0
- package/src/analytics/one-dollar-stats.ts +86 -0
- package/src/analytics/schema.ts +2 -0
- package/src/astro/generate.ts +133 -19
- package/src/astro/include-hmr.ts +31 -12
- package/src/astro/include-refresh.ts +19 -8
- package/src/astro/index.ts +1 -0
- package/src/astro/integration.ts +143 -14
- package/src/astro/module-types.ts +6 -0
- package/src/astro/runtime-modules.ts +28 -13
- package/src/astro/templates.ts +364 -67
- package/src/astro/variables.ts +31 -0
- package/src/audit/catalog.ts +19 -0
- package/src/audit/checks/contrast.ts +183 -0
- package/src/audit/checks/i18n.ts +2 -3
- package/src/audit/checks/indexability.ts +2 -3
- package/src/audit/checks/links.ts +13 -13
- package/src/audit/checks/sitemap.ts +2 -3
- package/src/audit/graph.ts +3 -1
- package/src/audit/redirects.ts +46 -15
- package/src/audit/run.ts +14 -9
- package/src/audit/snapshot.ts +5 -0
- package/src/audit/types.ts +2 -1
- package/src/blume-modules.d.ts +7 -0
- package/src/captcha/client.ts +152 -0
- package/src/captcha/hcaptcha.ts +36 -0
- package/src/captcha/index.ts +28 -0
- package/src/captcha/schema.ts +32 -0
- package/src/captcha/turnstile.ts +38 -0
- package/src/captcha/verify.ts +71 -0
- package/src/cli/args.ts +32 -0
- package/src/cli/command-meta.ts +5 -0
- package/src/cli/commands/audit.ts +4 -1
- package/src/cli/commands/doctor.ts +25 -17
- package/src/cli/commands/eval.ts +19 -11
- package/src/cli/commands/skill.ts +96 -0
- package/src/cli/commands/translate.ts +9 -12
- package/src/cli/commands/validate.ts +3 -1
- package/src/cli/dev-lock.ts +157 -37
- package/src/cli/index.ts +5 -0
- package/src/cli/report-format.ts +11 -6
- package/src/cli/required-secrets.ts +71 -29
- package/src/components/BlumePage.astro +2 -0
- package/src/components/colors.ts +19 -0
- package/src/components/content/ApiEndpoint.astro +18 -0
- package/src/components/content/ApiField.astro +79 -0
- package/src/components/content/ApiPlayground.astro +62 -0
- package/src/components/content/ApiRail.astro +19 -0
- package/src/components/content/Badge.astro +5 -3
- package/src/components/content/Color.astro +1 -0
- package/src/components/content/Component.astro +2 -2
- package/src/components/content/FileTree.astro +1 -0
- package/src/components/content/GithubInfo.astro +1 -0
- package/src/components/content/Panel.astro +1 -0
- package/src/components/content/ParamField.astro +25 -0
- package/src/components/content/RequestExample.astro +17 -0
- package/src/components/content/ResponseExample.astro +17 -0
- package/src/components/content/ResponseField.astro +32 -0
- package/src/components/content/Step.astro +1 -0
- package/src/components/content/Tab.astro +0 -1
- package/src/components/content/Tabs.astro +4 -0
- package/src/components/content/Tile.astro +1 -0
- package/src/components/content/Tooltip.astro +1 -0
- package/src/components/content/Tree.astro +1 -0
- package/src/components/content/TypeTable.astro +4 -1
- package/src/components/content/View.astro +21 -0
- package/src/components/content/ViewSwitcher.astro +168 -0
- package/src/components/content/api-field.ts +100 -0
- package/src/components/content/api-page.ts +224 -0
- package/src/components/content/badge-color.ts +5 -2
- package/src/components/content/base-href.ts +13 -36
- package/src/components/content/view-switcher.ts +71 -0
- package/src/components/index.ts +2 -0
- package/src/components/islands/Assistant.astro +10 -1
- package/src/components/islands/assistant.tsx +125 -18
- package/src/components/islands/base-path.ts +47 -10
- package/src/components/islands/code-question.ts +62 -0
- package/src/components/islands/hooks.ts +145 -40
- package/src/components/islands/support-link.ts +68 -0
- package/src/components/islands/webmcp.ts +4 -2
- package/src/components/layout/Analytics.astro +11 -2
- package/src/components/layout/Banner.astro +14 -10
- package/src/components/layout/Breadcrumbs.astro +11 -4
- package/src/components/layout/ConsentBanner.astro +62 -0
- package/src/components/layout/ConsentHead.astro +38 -0
- package/src/components/layout/Directory.astro +94 -0
- package/src/components/layout/DiscoveryLinks.astro +9 -5
- package/src/components/layout/DrawerSwitchers.astro +51 -0
- package/src/components/layout/Empty.astro +2 -2
- package/src/components/layout/Header.astro +124 -82
- package/src/components/layout/LanguageSwitcher.astro +50 -17
- package/src/components/layout/NarrationPlayer.astro +1085 -0
- package/src/components/layout/NavSelector.astro +35 -9
- package/src/components/layout/NavTabMenu.astro +3 -3
- package/src/components/layout/NavTree.astro +16 -6
- package/src/components/layout/PageActions.astro +4 -3
- package/src/components/layout/PageFeedback.astro +54 -49
- package/src/components/layout/PageLayout.astro +78 -9
- package/src/components/layout/Pagination.astro +3 -3
- package/src/components/layout/ReferenceLayout.astro +17 -6
- package/src/components/layout/RelatedPages.astro +42 -0
- package/src/components/layout/RootLayout.astro +313 -50
- package/src/components/layout/Search.astro +64 -30
- package/src/components/layout/SiteFooter.astro +124 -0
- package/src/components/layout/SiteMetatags.astro +16 -0
- package/src/components/layout/VersionBanner.astro +2 -2
- package/src/components/layout/analytics-client.ts +12 -0
- package/src/components/layout/consent/native.ts +79 -0
- package/src/components/layout/consent/types.ts +37 -0
- package/src/components/layout/footer-links.ts +53 -0
- package/src/components/layout/footer-socials.ts +87 -0
- package/src/components/layout/locale-redirect.ts +107 -0
- package/src/components/layout/nav-utils.ts +83 -0
- package/src/components/layout/page-feedback.ts +96 -0
- package/src/components/layout/search/analytics.ts +98 -0
- package/src/components/layout/search/flexsearch.ts +17 -5
- package/src/components/layout/search/orama-cloud.ts +26 -14
- package/src/components/layout/search/types.ts +20 -0
- package/src/components/layout/search/typesense.ts +5 -1
- package/src/components/layout/toc-active.ts +41 -0
- package/src/components/layout/toc-element.ts +8 -14
- package/src/components/openapi/ApiTagOperations.astro +2 -2
- package/src/components/openapi/AsyncApiOperation.astro +7 -4
- package/src/components/openapi/GraphqlChip.astro +2 -2
- package/src/components/openapi/Operation.astro +163 -13
- package/src/components/openapi/Playground.astro +4 -4
- package/src/components/openapi/RequestBody.astro +6 -4
- package/src/components/openapi/RequestPanel.astro +63 -8
- package/src/components/openapi/Responses.astro +6 -4
- package/src/components/openapi/SchemaTable.astro +7 -0
- package/src/components/openapi/async-snippets.ts +4 -4
- package/src/components/openapi/async.ts +38 -6
- package/src/components/openapi/code-samples.ts +97 -0
- package/src/components/openapi/helpers.ts +19 -9
- package/src/components/openapi/message-composer.ts +6 -1
- package/src/components/openapi/message.ts +17 -2
- package/src/components/openapi/operation-model.ts +81 -11
- package/src/components/openapi/panel.ts +4 -2
- package/src/components/openapi/param-style.ts +181 -0
- package/src/components/openapi/playground-client.ts +73 -16
- package/src/components/openapi/request.ts +190 -26
- package/src/components/openapi/schema-tree.ts +30 -24
- package/src/components/openapi/security.ts +6 -3
- package/src/components/openapi/snippets.ts +953 -13
- package/src/components/openapi/ws-client.ts +18 -2
- package/src/components/props.ts +6 -0
- package/src/consent/client.ts +95 -0
- package/src/consent/clients.ts +19 -0
- package/src/consent/ethyca.ts +71 -0
- package/src/consent/head.ts +24 -0
- package/src/consent/index.ts +37 -0
- package/src/consent/init.ts +29 -0
- package/src/consent/native.ts +36 -0
- package/src/consent/osano.ts +59 -0
- package/src/consent/schema.ts +30 -0
- package/src/core/base-path.ts +62 -16
- package/src/core/builtin-tags.ts +5 -0
- package/src/core/client-address.ts +19 -0
- package/src/core/config-input.ts +203 -27
- package/src/core/data.ts +44 -4
- package/src/core/diagnostics.ts +5 -2
- package/src/core/directive-diagnostics.ts +99 -0
- package/src/core/footer.ts +26 -0
- package/src/core/frontmatter.ts +21 -18
- package/src/core/graph.ts +39 -36
- package/src/core/heading-markers.ts +5 -18
- package/src/core/i18n-ui.ts +82 -2
- package/src/core/i18n.ts +31 -3
- package/src/core/includes.ts +20 -3
- package/src/core/last-modified.ts +25 -3
- package/src/core/links.ts +16 -2
- package/src/core/locale-links.ts +5 -1
- package/src/core/localizable.ts +26 -0
- package/src/core/meta.ts +33 -19
- package/src/core/navigation.ts +71 -20
- package/src/core/page-modes.ts +56 -0
- package/src/core/project-graph.ts +138 -10
- package/src/core/redirect-patterns.ts +491 -0
- package/src/core/related.ts +131 -0
- package/src/core/schema.ts +342 -68
- package/src/core/sources/contentful-rich-text.ts +27 -20
- package/src/core/sources/filesystem.ts +20 -2
- package/src/core/sources/github-releases.ts +25 -6
- package/src/core/sources/lexical.ts +23 -18
- package/src/core/sources/lower.ts +201 -34
- package/src/core/sources/mdx-remote.ts +51 -16
- package/src/core/sources/normalize.ts +327 -91
- package/src/core/sources/notion.ts +52 -25
- package/src/core/sources/obsidian.ts +23 -6
- package/src/core/sources/portable-text.ts +38 -21
- package/src/core/sources/read.ts +8 -3
- package/src/core/sources/strapi-blocks.ts +20 -16
- package/src/core/sources/watch.ts +20 -7
- package/src/core/standard-schema.ts +10 -6
- package/src/core/types.ts +1 -1
- package/src/core/ui-packs/ar.ts +43 -0
- package/src/core/ui-packs/bg.ts +45 -0
- package/src/core/ui-packs/bn.ts +43 -0
- package/src/core/ui-packs/ca.ts +46 -0
- package/src/core/ui-packs/cs.ts +44 -0
- package/src/core/ui-packs/da.ts +45 -0
- package/src/core/ui-packs/de.ts +46 -0
- package/src/core/ui-packs/el.ts +45 -0
- package/src/core/ui-packs/es.ts +46 -0
- package/src/core/ui-packs/fa.ts +45 -0
- package/src/core/ui-packs/fi.ts +45 -0
- package/src/core/ui-packs/fr.ts +46 -0
- package/src/core/ui-packs/he.ts +43 -0
- package/src/core/ui-packs/hi.ts +44 -0
- package/src/core/ui-packs/hr.ts +45 -0
- package/src/core/ui-packs/hu.ts +44 -0
- package/src/core/ui-packs/id.ts +45 -0
- package/src/core/ui-packs/it.ts +45 -0
- package/src/core/ui-packs/ja.ts +45 -0
- package/src/core/ui-packs/ko.ts +44 -0
- package/src/core/ui-packs/nl.ts +46 -0
- package/src/core/ui-packs/no.ts +44 -0
- package/src/core/ui-packs/pl.ts +45 -0
- package/src/core/ui-packs/pt-br.ts +46 -0
- package/src/core/ui-packs/pt.ts +45 -0
- package/src/core/ui-packs/ro.ts +45 -0
- package/src/core/ui-packs/ru.ts +45 -0
- package/src/core/ui-packs/sk.ts +44 -0
- package/src/core/ui-packs/sr.ts +45 -0
- package/src/core/ui-packs/sv.ts +44 -0
- package/src/core/ui-packs/th.ts +43 -0
- package/src/core/ui-packs/tr.ts +45 -0
- package/src/core/ui-packs/uk.ts +45 -0
- package/src/core/ui-packs/vi.ts +45 -0
- package/src/core/ui-packs/zh-tw.ts +43 -0
- package/src/core/ui-packs/zh.ts +43 -0
- package/src/core/variables.ts +103 -0
- package/src/core/version-cut.ts +52 -15
- package/src/deploy/artifacts.ts +104 -14
- package/src/deploy/cloudflare-negotiation.ts +39 -17
- package/src/deploy/headers.ts +10 -5
- package/src/deploy/node-headers.ts +64 -10
- package/src/deploy/platforms/cloudflare.ts +44 -3
- package/src/deploy/platforms/netlify.ts +34 -1
- package/src/deploy/platforms/node.ts +1 -0
- package/src/deploy/platforms/static.ts +1 -0
- package/src/deploy/platforms/types.ts +8 -0
- package/src/deploy/platforms/vercel.ts +38 -2
- package/src/deploy/redirects.ts +139 -36
- package/src/deploy/robots.ts +2 -2
- package/src/deploy/rss.ts +4 -3
- package/src/deploy/sitemap.ts +7 -5
- package/src/deploy/vercel-negotiation.ts +36 -0
- package/src/markdown/api-rail.ts +259 -0
- package/src/markdown/base-links.ts +34 -36
- package/src/markdown/code-title.ts +35 -9
- package/src/markdown/directives.ts +242 -36
- package/src/markdown/features.ts +20 -2
- package/src/markdown/fence-meta.ts +2 -0
- package/src/markdown/include.ts +11 -1
- package/src/markdown/index.ts +35 -11
- package/src/markdown/math.ts +4 -4
- package/src/markdown/mdast.ts +5 -2
- package/src/markdown/relative-links.ts +12 -4
- package/src/markdown/variables.ts +56 -0
- package/src/markdown/views.ts +82 -0
- package/src/narration/build.ts +412 -0
- package/src/narration/player.ts +249 -0
- package/src/narration/provider.ts +29 -0
- package/src/narration/script.ts +592 -0
- package/src/og/card.ts +136 -11
- package/src/og/derive.ts +41 -31
- package/src/og/index.ts +1 -0
- package/src/openapi/asyncapi.ts +4 -2
- package/src/openapi/model.ts +77 -36
- package/src/openapi/overlay.ts +256 -0
- package/src/openapi/parse.ts +55 -2
- package/src/openapi/proxy.ts +63 -10
- package/src/openapi/references.ts +21 -5
- package/src/openapi/render-mdx.ts +46 -3
- package/src/openapi/scalar.ts +40 -6
- package/src/openapi/source.ts +1 -1
- package/src/ratelimit/cloudflare.ts +54 -0
- package/src/ratelimit/index.ts +30 -0
- package/src/ratelimit/memory.ts +45 -0
- package/src/ratelimit/runtime.ts +229 -0
- package/src/ratelimit/schema.ts +28 -0
- package/src/ratelimit/upstash.ts +30 -0
- package/src/ratelimit/wrangler.ts +70 -0
- package/src/reference/asyncapi.ts +2 -2
- package/src/reference/graphql.ts +5 -2
- package/src/reference/openapi.ts +21 -7
- package/src/reference/options.ts +51 -7
- package/src/reference/scalar.ts +33 -8
- package/src/registry/eject.ts +201 -28
- package/src/registry/registry.ts +7 -0
- package/src/search/adapters/version-scope.ts +30 -0
- package/src/search/documents.ts +93 -28
- package/src/search/orama-index.ts +28 -3
- package/src/search/pagefind-ranking.ts +39 -0
- package/src/search/popular.ts +2 -1
- package/src/search/sync/algolia.ts +15 -4
- package/src/search/sync/orama-cloud.ts +3 -0
- package/src/search/sync/typesense.ts +5 -0
- package/src/seo/jsonld.ts +7 -3
- package/src/seo/metatags.ts +67 -0
- package/src/theme/chrome-icons.ts +1 -0
- package/src/theme/color.ts +500 -0
- package/src/theme/entry.ts +56 -3
- package/src/theme/palette.ts +121 -45
- package/src/translate/meta.ts +68 -24
- package/src/translate/prompts.ts +1 -0
- package/src/translate/run.ts +3 -3
- package/src/translate/validate.ts +12 -3
- package/src/translate/work-list.ts +56 -17
- package/src/upgrade/upgrade.ts +4 -171
- package/dist/cli/chunk-3yce002v.js.map +0 -14
- package/dist/cli/chunk-5r8g91qn.js.map +0 -34
- package/dist/cli/chunk-6dtt0zfn.js.map +0 -10
- package/dist/cli/chunk-7mbqtmgb.js.map +0 -10
- package/dist/cli/chunk-ddndchfr.js.map +0 -14
- package/dist/cli/chunk-esh98wmb.js +0 -23
- package/dist/cli/chunk-esh98wmb.js.map +0 -10
- package/dist/cli/chunk-hqp2ajnh.js.map +0 -35
- package/dist/cli/chunk-hr8ne106.js.map +0 -13
- package/dist/cli/chunk-j85scx15.js.map +0 -10
- package/dist/cli/chunk-mqc662a6.js +0 -75
- package/dist/cli/chunk-mqc662a6.js.map +0 -11
- package/dist/cli/chunk-pbg5a4s3.js.map +0 -19
- package/dist/cli/chunk-qkb5a8sa.js.map +0 -10
- package/dist/cli/chunk-v6ya5kcb.js.map +0 -189
- package/dist/cli/chunk-wdrt2k2v.js.map +0 -10
- package/dist/cli/chunk-xh43dwgw.js.map +0 -36
- package/docs/content/sources.mdx +0 -338
- /package/dist/cli/{chunk-zp79m0ts.js.map → chunk-1n228xqb.js.map} +0 -0
- /package/dist/cli/{chunk-h2ez8dzb.js.map → chunk-5ffywsmb.js.map} +0 -0
- /package/dist/cli/{chunk-g698a744.js.map → chunk-5wj2qwn2.js.map} +0 -0
- /package/dist/cli/{chunk-sqw4ekg1.js.map → chunk-amrb6hgh.js.map} +0 -0
- /package/dist/cli/{chunk-h7k3nq3v.js.map → chunk-gp84za0h.js.map} +0 -0
- /package/dist/cli/{chunk-bfwp9vp6.js.map → chunk-p804f3pe.js.map} +0 -0
- /package/dist/cli/{chunk-00gs3wqs.js.map → chunk-ta27feev.js.map} +0 -0
- /package/dist/cli/{chunk-bbnwccaz.js.map → chunk-y4ykbark.js.map} +0 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { substituteVariables } from "../core/variables.ts";
|
|
2
|
+
import type { ContentVariables } from "../core/variables.ts";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The MDX half of content variables (see `core/variables.ts`): a Vite plugin
|
|
6
|
+
* that replaces `{{name}}` in an `.mdx` module's source before the MDX
|
|
7
|
+
* compiler reads it. It has to run first: the compiler parses `{{version}}`
|
|
8
|
+
* as a JavaScript expression (a `ReferenceError` at render) and rejects
|
|
9
|
+
* `{{api-url}}` outright, so no plugin after parsing could see either.
|
|
10
|
+
*/
|
|
11
|
+
/** The Vite plugin slice this needs (structurally typed, like Blume's other plugins). */
|
|
12
|
+
export interface VariablesVitePlugin {
|
|
13
|
+
enforce: "pre";
|
|
14
|
+
name: string;
|
|
15
|
+
transform: (code: string, id: string) => { code: string; map: null } | null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const variablesVitePlugin = (
|
|
19
|
+
variables: ContentVariables
|
|
20
|
+
): VariablesVitePlugin => ({
|
|
21
|
+
enforce: "pre",
|
|
22
|
+
name: "blume:variables",
|
|
23
|
+
transform(code, id) {
|
|
24
|
+
const [path = ""] = id.split("?");
|
|
25
|
+
if (!path.endsWith(".mdx")) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
const next = substituteVariables(code, variables);
|
|
29
|
+
return next === code ? null : { code: next, map: null };
|
|
30
|
+
},
|
|
31
|
+
});
|
package/src/audit/catalog.ts
CHANGED
|
@@ -498,6 +498,25 @@ export const CHECKS = [
|
|
|
498
498
|
title: "Hreflang group has a language conflict",
|
|
499
499
|
},
|
|
500
500
|
|
|
501
|
+
// Accessibility. Theme colors are one per site, not per page: these run
|
|
502
|
+
// on the config, the way the build resolves it.
|
|
503
|
+
{
|
|
504
|
+
category: "accessibility",
|
|
505
|
+
fix: "Pick a darker shade for light mode or a lighter one for dark mode, per mode with `theme.accent: { light, dark }`.",
|
|
506
|
+
id: "BLUME_AUDIT_THEME_CONTRAST_LOW",
|
|
507
|
+
severity: "warning",
|
|
508
|
+
tier: "static",
|
|
509
|
+
title: "Theme colors miss WCAG AA contrast",
|
|
510
|
+
},
|
|
511
|
+
{
|
|
512
|
+
category: "accessibility",
|
|
513
|
+
fix: "Write the color as hex, `rgb()`, `hsl()`, `hwb()`, `lab()`, `lch()`, `oklab()`, `oklch()`, or a named color so its contrast can be checked.",
|
|
514
|
+
id: "BLUME_AUDIT_THEME_COLOR_UNCHECKED",
|
|
515
|
+
severity: "info",
|
|
516
|
+
tier: "static",
|
|
517
|
+
title: "Theme color can't be checked for contrast",
|
|
518
|
+
},
|
|
519
|
+
|
|
501
520
|
// Assets
|
|
502
521
|
{
|
|
503
522
|
category: "assets",
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import type { ResolvedConfig } from "../../core/schema.ts";
|
|
2
|
+
import type { Diagnostic } from "../../core/types.ts";
|
|
3
|
+
import { composite, contrastRatio, parseColor } from "../../theme/color.ts";
|
|
4
|
+
import type { Rgba } from "../../theme/color.ts";
|
|
5
|
+
import {
|
|
6
|
+
MIN_TEXT_CONTRAST,
|
|
7
|
+
labelOn,
|
|
8
|
+
resolveAccent,
|
|
9
|
+
resolveAction,
|
|
10
|
+
themeInk,
|
|
11
|
+
} from "../../theme/palette.ts";
|
|
12
|
+
import type { ColorMode } from "../../theme/palette.ts";
|
|
13
|
+
import { finding } from "../catalog.ts";
|
|
14
|
+
import type { CheckModule } from "../types.ts";
|
|
15
|
+
|
|
16
|
+
const MODES = ["light", "dark"] as const satisfies readonly ColorMode[];
|
|
17
|
+
|
|
18
|
+
/** The theme config key a color comes from. */
|
|
19
|
+
type ThemeKey = "theme.accent" | "theme.action" | "theme.background";
|
|
20
|
+
|
|
21
|
+
/** A color the theme draws text in, over the surface it sits on. */
|
|
22
|
+
interface TextPair {
|
|
23
|
+
fix: string;
|
|
24
|
+
key: ThemeKey;
|
|
25
|
+
/** What the pair is, for the message: "Accent text on the page". */
|
|
26
|
+
label: string;
|
|
27
|
+
surface: Rgba;
|
|
28
|
+
text: Rgba;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** A theme color, resolved for one mode, and the config key it came from. */
|
|
32
|
+
interface ThemeColor {
|
|
33
|
+
key: ThemeKey;
|
|
34
|
+
value: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** What one mode's check found: pairs to measure, colors it couldn't read. */
|
|
38
|
+
interface ModeColors {
|
|
39
|
+
pairs: TextPair[];
|
|
40
|
+
unread: ThemeColor[];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** A fill and the label the build puts on it (`labelOn`, as `readableOn`). */
|
|
44
|
+
const fillPair = (
|
|
45
|
+
key: "theme.accent" | "theme.action",
|
|
46
|
+
fill: Rgba,
|
|
47
|
+
page: Rgba,
|
|
48
|
+
mode: ColorMode
|
|
49
|
+
): TextPair => ({
|
|
50
|
+
fix: `Neither white nor dark text reaches ${MIN_TEXT_CONTRAST}:1 on this shade. Move \`${key}\`'s ${mode} shade lighter or darker.`,
|
|
51
|
+
key,
|
|
52
|
+
label:
|
|
53
|
+
key === "theme.action"
|
|
54
|
+
? "The call-to-action label"
|
|
55
|
+
: "Button labels on the accent",
|
|
56
|
+
surface: composite(fill, page),
|
|
57
|
+
text: parseColor(labelOn(fill, mode)) ?? fill,
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
/** The accent's pairs: accent text on the page, and labels on accent fills. */
|
|
61
|
+
const accentPairs = (fill: Rgba, page: Rgba, mode: ColorMode): TextPair[] => [
|
|
62
|
+
{
|
|
63
|
+
fix: `${mode === "light" ? "Darken" : "Lighten"} \`theme.accent\`'s ${mode} shade (\`theme.accent: { light, dark }\`).`,
|
|
64
|
+
key: "theme.accent",
|
|
65
|
+
label: "Accent text on the page",
|
|
66
|
+
surface: page,
|
|
67
|
+
text: fill,
|
|
68
|
+
},
|
|
69
|
+
fillPair("theme.accent", fill, page, mode),
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
/** The pairs a custom page background sets: body and secondary text on it. */
|
|
73
|
+
const backgroundPairs = (page: Rgba, mode: ColorMode): TextPair[] => {
|
|
74
|
+
const fix = `Pick a \`theme.background\` ${mode} color further from the text color.`;
|
|
75
|
+
return [
|
|
76
|
+
{
|
|
77
|
+
fix,
|
|
78
|
+
key: "theme.background",
|
|
79
|
+
label: "Body text on the page",
|
|
80
|
+
surface: page,
|
|
81
|
+
text: themeInk(mode, "foreground"),
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
fix,
|
|
85
|
+
key: "theme.background",
|
|
86
|
+
label: "Secondary text on the page",
|
|
87
|
+
surface: page,
|
|
88
|
+
text: themeInk(mode, "mutedForeground"),
|
|
89
|
+
},
|
|
90
|
+
];
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
/** Everything one mode draws text in, from the theme config. */
|
|
94
|
+
const modeColors = (
|
|
95
|
+
theme: ResolvedConfig["theme"],
|
|
96
|
+
mode: ColorMode
|
|
97
|
+
): ModeColors => {
|
|
98
|
+
const unread: ThemeColor[] = [];
|
|
99
|
+
const read = (color: ThemeColor): Rgba | null => {
|
|
100
|
+
const parsed = parseColor(color.value);
|
|
101
|
+
if (!parsed) {
|
|
102
|
+
unread.push(color);
|
|
103
|
+
}
|
|
104
|
+
return parsed;
|
|
105
|
+
};
|
|
106
|
+
const custom = theme.background?.[mode];
|
|
107
|
+
const customPage =
|
|
108
|
+
custom === undefined
|
|
109
|
+
? null
|
|
110
|
+
: read({ key: "theme.background", value: custom });
|
|
111
|
+
// Text on a page color the audit can't read can't be measured.
|
|
112
|
+
if (custom !== undefined && !customPage) {
|
|
113
|
+
return { pairs: [], unread };
|
|
114
|
+
}
|
|
115
|
+
const page = customPage ?? themeInk(mode, "background");
|
|
116
|
+
const pairs = customPage ? backgroundPairs(customPage, mode) : [];
|
|
117
|
+
const accent = read({
|
|
118
|
+
key: "theme.accent",
|
|
119
|
+
value: resolveAccent(theme)[mode],
|
|
120
|
+
});
|
|
121
|
+
if (accent) {
|
|
122
|
+
pairs.push(...accentPairs(accent, page, mode));
|
|
123
|
+
}
|
|
124
|
+
const action = resolveAction(theme);
|
|
125
|
+
const actionFill =
|
|
126
|
+
action && read({ key: "theme.action", value: action[mode] });
|
|
127
|
+
if (actionFill) {
|
|
128
|
+
pairs.push(fillPair("theme.action", actionFill, page, mode));
|
|
129
|
+
}
|
|
130
|
+
return { pairs, unread };
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Theme contrast: the text colors a configured theme draws — accent text,
|
|
135
|
+
* labels on accent and call-to-action fills, and body text on a custom
|
|
136
|
+
* background — measured against WCAG AA's 4.5:1 in both color modes, resolved
|
|
137
|
+
* the way the build resolves them (presets per mode, labels by `labelOn`).
|
|
138
|
+
* One finding per failing pair, pointed at blume.config.ts, since a theme
|
|
139
|
+
* color is a site-wide choice, not a page's.
|
|
140
|
+
*/
|
|
141
|
+
export const contrastChecks: CheckModule = {
|
|
142
|
+
category: "accessibility",
|
|
143
|
+
run(context) {
|
|
144
|
+
const { theme } = context.project.config;
|
|
145
|
+
const site = {
|
|
146
|
+
file: context.project.context.configFile ?? undefined,
|
|
147
|
+
url: "/",
|
|
148
|
+
};
|
|
149
|
+
const found: Diagnostic[] = [];
|
|
150
|
+
const reported = new Set<string>();
|
|
151
|
+
for (const mode of MODES) {
|
|
152
|
+
const { pairs, unread } = modeColors(theme, mode);
|
|
153
|
+
for (const color of unread) {
|
|
154
|
+
const id = `${color.key}:${color.value}`;
|
|
155
|
+
if (!reported.has(id)) {
|
|
156
|
+
reported.add(id);
|
|
157
|
+
found.push(
|
|
158
|
+
finding(
|
|
159
|
+
"BLUME_AUDIT_THEME_COLOR_UNCHECKED",
|
|
160
|
+
site,
|
|
161
|
+
`\`${color.key}\` is "${color.value}", which the audit can't read as a color, so its contrast went unchecked.`
|
|
162
|
+
)
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
for (const pair of pairs) {
|
|
167
|
+
const ratio = contrastRatio(pair.text, pair.surface);
|
|
168
|
+
if (ratio < MIN_TEXT_CONTRAST) {
|
|
169
|
+
found.push(
|
|
170
|
+
finding(
|
|
171
|
+
"BLUME_AUDIT_THEME_CONTRAST_LOW",
|
|
172
|
+
site,
|
|
173
|
+
`${pair.label} in ${mode} mode is ${ratio.toFixed(2)}:1, under the ${MIN_TEXT_CONTRAST}:1 WCAG AA asks of body text (\`${pair.key}\`).`,
|
|
174
|
+
pair.fix
|
|
175
|
+
)
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return found;
|
|
181
|
+
},
|
|
182
|
+
tier: "static",
|
|
183
|
+
};
|
package/src/audit/checks/i18n.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { normalizeBasePath, stripBasePath } from "../../core/base-path.ts";
|
|
|
2
2
|
import type { Diagnostic } from "../../core/types.ts";
|
|
3
3
|
import { finding } from "../catalog.ts";
|
|
4
4
|
import { pageSite } from "../locate.ts";
|
|
5
|
+
import { redirectAt } from "../redirects.ts";
|
|
5
6
|
import type { AuditContext, CheckModule, PageSnapshot } from "../types.ts";
|
|
6
7
|
import { decodePath, normalizePath } from "../url.ts";
|
|
7
8
|
import { isArchivedLatestCanonical } from "./indexability.ts";
|
|
@@ -134,9 +135,7 @@ const hreflangChecks = (
|
|
|
134
135
|
}
|
|
135
136
|
|
|
136
137
|
const target = context.byUrl.get(path);
|
|
137
|
-
const redirect = context.redirects
|
|
138
|
-
(entry) => normalizePath(entry.from) === path
|
|
139
|
-
);
|
|
138
|
+
const redirect = redirectAt(context.redirects, path);
|
|
140
139
|
if (redirect) {
|
|
141
140
|
found.push(
|
|
142
141
|
finding(
|
|
@@ -3,6 +3,7 @@ import type { Diagnostic } from "../../core/types.ts";
|
|
|
3
3
|
import { deployPlatform } from "../../deploy/platforms/index.ts";
|
|
4
4
|
import { finding } from "../catalog.ts";
|
|
5
5
|
import { pageSite } from "../locate.ts";
|
|
6
|
+
import { redirectAt } from "../redirects.ts";
|
|
6
7
|
import { ERROR_ROUTES } from "../types.ts";
|
|
7
8
|
import type { AuditContext, CheckModule, PageSnapshot } from "../types.ts";
|
|
8
9
|
import { decodePath, normalizePath, siteOrigin } from "../url.ts";
|
|
@@ -118,9 +119,7 @@ const canonicalChecks = (
|
|
|
118
119
|
|
|
119
120
|
// The canonical points at another page on this site. It must exist, and it
|
|
120
121
|
// must not itself redirect — a canonical to a redirect is a dead end.
|
|
121
|
-
const redirect = context.redirects
|
|
122
|
-
(entry) => normalizePath(entry.from) === target
|
|
123
|
-
);
|
|
122
|
+
const redirect = redirectAt(context.redirects, target);
|
|
124
123
|
if (redirect) {
|
|
125
124
|
found.push(
|
|
126
125
|
finding(
|
|
@@ -3,17 +3,16 @@ import type { Diagnostic } from "../../core/types.ts";
|
|
|
3
3
|
import { finding } from "../catalog.ts";
|
|
4
4
|
import { orphanPages } from "../graph.ts";
|
|
5
5
|
import { pageSite } from "../locate.ts";
|
|
6
|
-
import
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
PageSnapshot,
|
|
10
|
-
SnapshotLink,
|
|
11
|
-
} from "../types.ts";
|
|
12
|
-
import { isServed, normalizePath, resolveHref, siteOrigin } from "../url.ts";
|
|
6
|
+
import { redirectAt } from "../redirects.ts";
|
|
7
|
+
import type { CheckModule, PageSnapshot, SnapshotLink } from "../types.ts";
|
|
8
|
+
import { isServed, resolveHref, siteOrigin } from "../url.ts";
|
|
13
9
|
|
|
14
10
|
/** Browser-magic fragments that scroll without needing a matching id. */
|
|
15
11
|
const MAGIC_FRAGMENTS = new Set(["", "top"]);
|
|
16
12
|
|
|
13
|
+
/** Where a fragment's directives (text fragments) start. */
|
|
14
|
+
const FRAGMENT_DIRECTIVE = ":~:";
|
|
15
|
+
|
|
17
16
|
/** Whether a fragment lands on an id of the target page. */
|
|
18
17
|
const anchorResolves = (target: PageSnapshot, fragment: string): boolean => {
|
|
19
18
|
if (MAGIC_FRAGMENTS.has(fragment)) {
|
|
@@ -30,9 +29,6 @@ const anchorResolves = (target: PageSnapshot, fragment: string): boolean => {
|
|
|
30
29
|
}
|
|
31
30
|
};
|
|
32
31
|
|
|
33
|
-
const redirectFrom = (context: AuditContext, path: string) =>
|
|
34
|
-
context.redirects.find((entry) => normalizePath(entry.from) === path);
|
|
35
|
-
|
|
36
32
|
/**
|
|
37
33
|
* Internal links, and what they land on.
|
|
38
34
|
*
|
|
@@ -65,8 +61,12 @@ export const linkChecks: CheckModule = {
|
|
|
65
61
|
page: PageSnapshot,
|
|
66
62
|
link: SnapshotLink,
|
|
67
63
|
target: PageSnapshot,
|
|
68
|
-
|
|
64
|
+
hash: string
|
|
69
65
|
): void => {
|
|
66
|
+
// A text fragment (`#:~:text=…`) is a directive the browser strips
|
|
67
|
+
// before it looks for an id; only the part in front of `:~:` names an
|
|
68
|
+
// element, and an empty one scrolls to the text instead.
|
|
69
|
+
const [fragment = ""] = hash.split(FRAGMENT_DIRECTIVE);
|
|
70
70
|
if (anchorResolves(target, fragment)) {
|
|
71
71
|
return;
|
|
72
72
|
}
|
|
@@ -143,7 +143,7 @@ export const linkChecks: CheckModule = {
|
|
|
143
143
|
}
|
|
144
144
|
|
|
145
145
|
const { path } = resolved;
|
|
146
|
-
const redirect =
|
|
146
|
+
const redirect = redirectAt(context.redirects, path);
|
|
147
147
|
if (redirect) {
|
|
148
148
|
if (link.content) {
|
|
149
149
|
found.push(
|
|
@@ -193,7 +193,7 @@ export const linkChecks: CheckModule = {
|
|
|
193
193
|
);
|
|
194
194
|
}
|
|
195
195
|
for (const [path, page] of redirectedChrome) {
|
|
196
|
-
const redirect =
|
|
196
|
+
const redirect = redirectAt(context.redirects, path);
|
|
197
197
|
found.push(
|
|
198
198
|
finding(
|
|
199
199
|
"BLUME_AUDIT_LINK_TO_REDIRECT",
|
|
@@ -2,6 +2,7 @@ import { normalizeBasePath, stripBasePath } from "../../core/base-path.ts";
|
|
|
2
2
|
import type { Diagnostic } from "../../core/types.ts";
|
|
3
3
|
import { finding } from "../catalog.ts";
|
|
4
4
|
import { pageSite } from "../locate.ts";
|
|
5
|
+
import { redirectAt } from "../redirects.ts";
|
|
5
6
|
import type { AuditContext, CheckModule } from "../types.ts";
|
|
6
7
|
import { decodePath, normalizePath, siteOrigin } from "../url.ts";
|
|
7
8
|
|
|
@@ -100,9 +101,7 @@ const checkListedUrl = (
|
|
|
100
101
|
);
|
|
101
102
|
const page = context.byUrl.get(path);
|
|
102
103
|
if (!page) {
|
|
103
|
-
const redirect = context.redirects
|
|
104
|
-
(entry) => normalizePath(entry.from) === path
|
|
105
|
-
);
|
|
104
|
+
const redirect = redirectAt(context.redirects, path);
|
|
106
105
|
return [
|
|
107
106
|
finding(
|
|
108
107
|
"BLUME_AUDIT_SITEMAP_BAD_URL",
|
package/src/audit/graph.ts
CHANGED
|
@@ -58,7 +58,8 @@ export const buildGraph = (
|
|
|
58
58
|
* Non-indexable pages are excluded — a `noindex` page is *meant* to be
|
|
59
59
|
* unreachable — as is the home page, which is nobody's job to link to. Under a
|
|
60
60
|
* `basePath` the home page's built URL is the base itself, so the caller names
|
|
61
|
-
* it via `homeUrl`.
|
|
61
|
+
* it via `homeUrl`. An i18n fallback copy is excluded too: it stands in for a
|
|
62
|
+
* translation nobody has written yet, so the page to link to is the original.
|
|
62
63
|
*/
|
|
63
64
|
export const orphanPages = (
|
|
64
65
|
pages: PageSnapshot[],
|
|
@@ -68,6 +69,7 @@ export const orphanPages = (
|
|
|
68
69
|
pages.filter(
|
|
69
70
|
(page) =>
|
|
70
71
|
page.indexable &&
|
|
72
|
+
page.route?.fallback !== true &&
|
|
71
73
|
page.url !== "/" &&
|
|
72
74
|
page.url !== homeUrl &&
|
|
73
75
|
(graph.contentIn.get(page.url)?.size ?? 0) === 0
|
package/src/audit/redirects.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isPatternPath,
|
|
3
|
+
patternDestination,
|
|
4
|
+
} from "../core/redirect-patterns.ts";
|
|
1
5
|
import type { RedirectResolution } from "./types.ts";
|
|
2
6
|
import { normalizePath } from "./url.ts";
|
|
3
7
|
|
|
@@ -7,18 +11,6 @@ interface ConfiguredRedirect {
|
|
|
7
11
|
status: number;
|
|
8
12
|
}
|
|
9
13
|
|
|
10
|
-
/**
|
|
11
|
-
* Follow every configured redirect through to its destination, classifying what
|
|
12
|
-
* it lands on.
|
|
13
|
-
*
|
|
14
|
-
* - `loop` — the chain revisits a hop it has already been to. Never resolves.
|
|
15
|
-
* - `broken` — the chain ends somewhere the build does not serve.
|
|
16
|
-
* - `chain` — it resolves, but through at least one intermediate redirect.
|
|
17
|
-
* - `ok` — one hop, straight to a real page.
|
|
18
|
-
*
|
|
19
|
-
* An external destination (`https://…`) is always `ok`: it's outside the site,
|
|
20
|
-
* so there's no local page to check it against.
|
|
21
|
-
*/
|
|
22
14
|
/**
|
|
23
15
|
* A destination's page path: the part before any query string or fragment. A
|
|
24
16
|
* redirect to `/guide#setup` or `/search?q=x` lands on the `/guide` / `/search`
|
|
@@ -30,6 +22,22 @@ const pathOnly = (value: string): string => {
|
|
|
30
22
|
return cut === -1 ? value : value.slice(0, cut);
|
|
31
23
|
};
|
|
32
24
|
|
|
25
|
+
/**
|
|
26
|
+
* Follow every configured redirect through to its destination, classifying what
|
|
27
|
+
* it lands on.
|
|
28
|
+
*
|
|
29
|
+
* - `loop` — the chain revisits a hop it has already been to. Never resolves.
|
|
30
|
+
* - `broken` — the chain ends somewhere the build does not serve.
|
|
31
|
+
* - `chain` — it resolves, but through at least one intermediate redirect.
|
|
32
|
+
* - `ok` — one hop, straight to a real page.
|
|
33
|
+
* - `pattern` — a pattern (`/beta/:slug*`), which covers paths rather than
|
|
34
|
+
* naming one, so there is no single chain to walk; {@link redirectAt}
|
|
35
|
+
* resolves a path it covers.
|
|
36
|
+
*
|
|
37
|
+
* An external destination (`https://…`) is always `ok`: it's outside the site,
|
|
38
|
+
* so there's no local page to check it against. A hop onto a path a pattern
|
|
39
|
+
* covers continues through that pattern.
|
|
40
|
+
*/
|
|
33
41
|
export const resolveRedirects = (
|
|
34
42
|
redirects: readonly ConfiguredRedirect[],
|
|
35
43
|
/** Whether the build serves a normalized path — see `isServed`. */
|
|
@@ -39,9 +47,14 @@ export const resolveRedirects = (
|
|
|
39
47
|
for (const redirect of redirects) {
|
|
40
48
|
byFrom.set(normalizePath(redirect.from), redirect);
|
|
41
49
|
}
|
|
50
|
+
const hopFrom = (path: string): string | undefined =>
|
|
51
|
+
byFrom.get(path)?.to ?? patternDestination(redirects, path);
|
|
42
52
|
|
|
43
53
|
return redirects.map((redirect) => {
|
|
44
54
|
const from = normalizePath(redirect.from);
|
|
55
|
+
if (isPatternPath(redirect.from)) {
|
|
56
|
+
return { ...redirect, chain: [from], outcome: "pattern" as const };
|
|
57
|
+
}
|
|
45
58
|
const chain: string[] = [from];
|
|
46
59
|
const seen = new Set<string>([from]);
|
|
47
60
|
let current = redirect.to;
|
|
@@ -63,11 +76,11 @@ export const resolveRedirects = (
|
|
|
63
76
|
}
|
|
64
77
|
chain.push(next);
|
|
65
78
|
seen.add(next);
|
|
66
|
-
const hop =
|
|
67
|
-
if (
|
|
79
|
+
const hop = hopFrom(next);
|
|
80
|
+
if (hop === undefined) {
|
|
68
81
|
break;
|
|
69
82
|
}
|
|
70
|
-
current = hop
|
|
83
|
+
current = hop;
|
|
71
84
|
}
|
|
72
85
|
|
|
73
86
|
const destination = chain.at(-1) ?? from;
|
|
@@ -84,3 +97,21 @@ export const resolveRedirects = (
|
|
|
84
97
|
};
|
|
85
98
|
});
|
|
86
99
|
};
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* The configured redirect a path takes, for the checks that report a link,
|
|
103
|
+
* canonical, sitemap entry, or `hreflang` pointing through one: the exact
|
|
104
|
+
* redirect from that path, else the pattern covering it, its `to` filled in
|
|
105
|
+
* for this path.
|
|
106
|
+
*/
|
|
107
|
+
export const redirectAt = (
|
|
108
|
+
redirects: readonly RedirectResolution[],
|
|
109
|
+
path: string
|
|
110
|
+
): { to: string } | undefined => {
|
|
111
|
+
const exact = redirects.find((entry) => normalizePath(entry.from) === path);
|
|
112
|
+
if (exact) {
|
|
113
|
+
return exact;
|
|
114
|
+
}
|
|
115
|
+
const to = patternDestination(redirects, path);
|
|
116
|
+
return to === undefined ? undefined : { to };
|
|
117
|
+
};
|
package/src/audit/run.ts
CHANGED
|
@@ -2,14 +2,17 @@ import { readFile } from "node:fs/promises";
|
|
|
2
2
|
|
|
3
3
|
import pMap from "p-map";
|
|
4
4
|
|
|
5
|
-
import { normalizeBasePath
|
|
5
|
+
import { normalizeBasePath } from "../core/base-path.ts";
|
|
6
|
+
import { routeSetFor } from "../core/locale-links.ts";
|
|
6
7
|
import type { BlumeProject } from "../core/project-graph.ts";
|
|
7
8
|
import type { Diagnostic } from "../core/types.ts";
|
|
8
9
|
import { deployStaticDir } from "../deploy/adapter-output.ts";
|
|
10
|
+
import { applyBaseToAstroRedirects } from "../deploy/redirects.ts";
|
|
9
11
|
import { CHECKS } from "./catalog.ts";
|
|
10
12
|
import type { CheckId } from "./catalog.ts";
|
|
11
13
|
import { assetChecks } from "./checks/assets.ts";
|
|
12
14
|
import { contentChecks } from "./checks/content.ts";
|
|
15
|
+
import { contrastChecks } from "./checks/contrast.ts";
|
|
13
16
|
import { dnsAidChecks } from "./checks/dns-aid.ts";
|
|
14
17
|
import { duplicateChecks } from "./checks/duplicates.ts";
|
|
15
18
|
import { i18nChecks } from "./checks/i18n.ts";
|
|
@@ -49,6 +52,7 @@ const MODULES: CheckModule[] = [
|
|
|
49
52
|
ogImageChecks,
|
|
50
53
|
i18nChecks,
|
|
51
54
|
assetChecks,
|
|
55
|
+
contrastChecks,
|
|
52
56
|
sitemapChecks,
|
|
53
57
|
robotsChecks,
|
|
54
58
|
llmsChecks,
|
|
@@ -162,14 +166,15 @@ export const runAudit = async (options: AuditOptions): Promise<AuditResult> => {
|
|
|
162
166
|
redirects: resolveRedirects(
|
|
163
167
|
// Redirects are authored as if mounted at root; the built page URLs they
|
|
164
168
|
// are checked against carry `basePath` (it's a real directory in the
|
|
165
|
-
// build), so both sides gain it here
|
|
166
|
-
// `
|
|
167
|
-
//
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
169
|
+
// build), so both sides gain it here, based exactly as the build bases
|
|
170
|
+
// them — a `to` naming a public file stays at the root, and external
|
|
171
|
+
// `to` URLs pass through.
|
|
172
|
+
applyBaseToAstroRedirects(
|
|
173
|
+
project.config.redirects,
|
|
174
|
+
basePath,
|
|
175
|
+
"",
|
|
176
|
+
routeSetFor(project.manifest.routes)
|
|
177
|
+
),
|
|
173
178
|
// Pages, static files, and server routes alike: a redirect may
|
|
174
179
|
// legitimately land on a served asset (`/old-whitepaper` ->
|
|
175
180
|
// `/files/whitepaper.pdf`), and the same predicate the link and llms.txt
|
package/src/audit/snapshot.ts
CHANGED
|
@@ -196,8 +196,13 @@ export const buildSnapshot = (options: {
|
|
|
196
196
|
scripts: collectAssets(document, "script[src]", "src"),
|
|
197
197
|
source: route?.sourcePath,
|
|
198
198
|
styles: collectAssets(document, 'link[rel="stylesheet"][href]', "href"),
|
|
199
|
+
// Only the document's own titles: an accessible inline SVG carries a
|
|
200
|
+
// `<title>` of its own (an SVG-namespace element, not the page title), and
|
|
201
|
+
// counting it would flag BLUME_AUDIT_TITLE_MULTIPLE — or stand in for a
|
|
202
|
+
// head `<title>` that is genuinely missing.
|
|
199
203
|
titles: document
|
|
200
204
|
.querySelectorAll("title")
|
|
205
|
+
.filter((title) => !title.closest("svg"))
|
|
201
206
|
.map((title) => title.text.trim())
|
|
202
207
|
.filter((text) => text.length > 0),
|
|
203
208
|
twitter: prefixedMeta(document, "name", "twitter:"),
|
package/src/audit/types.ts
CHANGED
|
@@ -13,6 +13,7 @@ import type {
|
|
|
13
13
|
export type AuditTier = "static" | "network" | "external";
|
|
14
14
|
|
|
15
15
|
export type AuditCategory =
|
|
16
|
+
| "accessibility"
|
|
16
17
|
| "content"
|
|
17
18
|
| "duplicates"
|
|
18
19
|
| "indexability"
|
|
@@ -110,7 +111,7 @@ export interface RedirectResolution {
|
|
|
110
111
|
status: number;
|
|
111
112
|
/** Every hop from `from` to the final target, inclusive. */
|
|
112
113
|
chain: string[];
|
|
113
|
-
outcome: "ok" | "loop" | "broken" | "chain";
|
|
114
|
+
outcome: "ok" | "loop" | "broken" | "chain" | "pattern";
|
|
114
115
|
}
|
|
115
116
|
|
|
116
117
|
/** One urlset file behind a sitemap, for the per-file size limits. */
|
package/src/blume-modules.d.ts
CHANGED
|
@@ -15,6 +15,13 @@ declare module "blume:search-client" {
|
|
|
15
15
|
export const createSearch: () => Fn | Promise<Fn>;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
declare module "blume:consent-client" {
|
|
19
|
+
/** Start the configured consent adapter's browser module (see `consentClientTemplate`); a no-op for one without. */
|
|
20
|
+
// oxlint-disable-next-line typescript/consistent-type-imports
|
|
21
|
+
type Consent = import("./components/layout/consent/types.ts").BlumeConsent;
|
|
22
|
+
export const startConsentClient: (consent: Consent) => void;
|
|
23
|
+
}
|
|
24
|
+
|
|
18
25
|
declare module "blume:ask" {
|
|
19
26
|
/** The generated Ask trigger (see `askComponentTemplate`); empty when Ask is off. */
|
|
20
27
|
// Mirrors the generated `.blume/src/env.d.ts` declaration: the trigger's type
|