blume 2.0.3 → 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 +48 -0
- package/README.md +1 -1
- package/dist/cli/{chunk-1d7ve1dm.js → chunk-0gz36akr.js} +18 -2
- package/dist/cli/{chunk-1d7ve1dm.js.map → chunk-0gz36akr.js.map} +3 -3
- package/dist/cli/{chunk-2eytanqx.js → chunk-1n228xqb.js} +2 -2
- package/dist/cli/{chunk-yfyb25rh.js → chunk-2f3ndkpw.js} +24 -8
- package/dist/cli/chunk-2f3ndkpw.js.map +14 -0
- package/dist/cli/{chunk-r20tn01b.js → chunk-2f7n4qzt.js} +8 -2
- package/dist/cli/{chunk-r20tn01b.js.map → chunk-2f7n4qzt.js.map} +3 -3
- package/dist/cli/{chunk-35d4wj9f.js → chunk-2n4ps1b3.js} +7 -7
- package/dist/cli/{chunk-j85vccga.js → chunk-43zcf4gz.js} +97 -57
- package/dist/cli/{chunk-j85vccga.js.map → chunk-43zcf4gz.js.map} +6 -6
- package/dist/cli/{chunk-e04dxsz1.js → chunk-4ejxbzee.js} +1 -1
- package/dist/cli/{chunk-5m5nmvyq.js → chunk-4h8z9vv5.js} +233 -39
- package/dist/cli/chunk-4h8z9vv5.js.map +16 -0
- package/dist/cli/{chunk-5f86nr5m.js → chunk-5cg2rzgf.js} +18 -16
- package/dist/cli/{chunk-5f86nr5m.js.map → chunk-5cg2rzgf.js.map} +2 -2
- package/dist/cli/{chunk-tzmab476.js → chunk-5ffywsmb.js} +4 -4
- package/dist/cli/{chunk-2z928egk.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-vg9r4eb9.js → chunk-713mngdq.js} +12 -5
- package/dist/cli/{chunk-vg9r4eb9.js.map → chunk-713mngdq.js.map} +3 -3
- package/dist/cli/{chunk-5xvm6tfj.js → chunk-9geymqns.js} +17 -15
- package/dist/cli/{chunk-5xvm6tfj.js.map → chunk-9geymqns.js.map} +2 -2
- package/dist/cli/{chunk-q58y5e6a.js → chunk-9xr4dgee.js} +26 -17
- package/dist/cli/chunk-9xr4dgee.js.map +10 -0
- package/dist/cli/{chunk-b07cmahc.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-akbpwfxc.js → chunk-cmtcnh9f.js} +2 -2
- package/dist/cli/chunk-cy5bm0j0.js +104 -0
- package/dist/cli/chunk-cy5bm0j0.js.map +11 -0
- package/dist/cli/{chunk-crgn1q09.js → chunk-e7vvk5ya.js} +4 -4
- package/dist/cli/{chunk-pehfxfta.js → chunk-g2z9x01v.js} +6 -98
- package/dist/cli/chunk-g2z9x01v.js.map +10 -0
- package/dist/cli/{chunk-tkacnehg.js → chunk-gp84za0h.js} +2 -2
- package/dist/cli/{chunk-wrr3j9w9.js → chunk-h2a48yhd.js} +186 -63
- package/dist/cli/chunk-h2a48yhd.js.map +36 -0
- package/dist/cli/{chunk-hm3vjy5s.js → chunk-jnwj5kjj.js} +14 -9
- package/dist/cli/{chunk-hm3vjy5s.js.map → chunk-jnwj5kjj.js.map} +4 -4
- package/dist/cli/chunk-kwrg63tm.js +31 -0
- package/dist/cli/chunk-kwrg63tm.js.map +10 -0
- package/dist/cli/{chunk-2hsdwb9n.js → chunk-nd1821sk.js} +18 -16
- package/dist/cli/{chunk-2hsdwb9n.js.map → chunk-nd1821sk.js.map} +2 -2
- package/dist/cli/{chunk-6dsbexzp.js → chunk-ntv4qrt7.js} +17 -15
- package/dist/cli/{chunk-6dsbexzp.js.map → chunk-ntv4qrt7.js.map} +2 -2
- package/dist/cli/{chunk-r9rcc4w7.js → chunk-p804f3pe.js} +7 -7
- package/dist/cli/{chunk-ey84smr6.js → chunk-py62fewh.js} +6 -6
- package/dist/cli/{chunk-ey84smr6.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-9t7a85s3.js → chunk-rekqz4qg.js} +23 -13
- package/dist/cli/chunk-rekqz4qg.js.map +10 -0
- package/dist/cli/{chunk-8ktnccpt.js → chunk-ta27feev.js} +1 -1
- package/dist/cli/{chunk-pv29h0wf.js → chunk-xkz5xbtv.js} +3476 -822
- package/dist/cli/chunk-xkz5xbtv.js.map +213 -0
- package/dist/cli/{chunk-a58773jm.js → chunk-y4ykbark.js} +2 -2
- package/dist/cli/{chunk-p3v96n38.js → chunk-y5h4r2y0.js} +4 -4
- package/dist/cli/{chunk-acanzt5p.js → chunk-yj17vs39.js} +6 -6
- package/dist/cli/{chunk-p73c0m7w.js → chunk-yxmk4pec.js} +16 -14
- package/dist/cli/{chunk-p73c0m7w.js.map → chunk-yxmk4pec.js.map} +2 -2
- package/dist/cli/{chunk-3em5wd2y.js → chunk-zccbjcj0.js} +4 -3
- package/dist/cli/{chunk-3em5wd2y.js.map → chunk-zccbjcj0.js.map} +2 -2
- package/dist/cli/{chunk-c8chx29p.js → chunk-zf748avv.js} +384 -207
- package/dist/cli/chunk-zf748avv.js.map +37 -0
- package/dist/cli/{chunk-g4hq16wv.js → chunk-zhvd6adm.js} +16 -14
- package/dist/cli/{chunk-g4hq16wv.js.map → chunk-zhvd6adm.js.map} +2 -2
- package/dist/cli/index.js +26 -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/site-skill.d.ts +26 -0
- package/dist/types/ai/skills.d.ts +16 -0
- package/dist/types/astro/integration.d.ts +8 -0
- 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/config-input.d.ts +187 -20
- package/dist/types/core/data.d.ts +49 -4
- package/dist/types/core/footer.d.ts +8 -0
- package/dist/types/core/i18n-ui.d.ts +86 -0
- package/dist/types/core/includes.d.ts +8 -2
- package/dist/types/core/localizable.d.ts +7 -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 +551 -56
- package/dist/types/core/sources/read.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/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/vercel.d.ts +6 -0
- package/dist/types/deploy/redirects.d.ts +39 -11
- package/dist/types/deploy/vercel-negotiation.d.ts +9 -0
- package/dist/types/markdown/code-title.d.ts +17 -0
- package/dist/types/markdown/features.d.ts +3 -2
- 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/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 +41 -5
- package/docs/03-upgrading.mdx +4 -4
- package/docs/04-migrating.mdx +1 -1
- package/docs/advanced/changelog.mdx +2 -2
- package/docs/advanced/skills.mdx +14 -0
- package/docs/cli/audit.mdx +21 -1
- package/docs/cli/index.mdx +1 -0
- package/docs/configuration/analytics.mdx +3 -1
- package/docs/configuration/assistant.mdx +188 -36
- package/docs/configuration/consent.mdx +100 -0
- package/docs/configuration/customization.mdx +4 -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 +29 -1
- package/docs/configuration/theming.mdx +3 -1
- package/docs/content/components.mdx +106 -0
- package/docs/content/frontmatter.mdx +76 -0
- package/docs/content/i18n.mdx +38 -3
- package/docs/content/includes.mdx +16 -0
- package/docs/content/meta.mdx +1 -0
- 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 +67 -2
- package/docs/content/variables.mdx +41 -0
- package/docs/discoverability/agent-discovery.mdx +29 -2
- package/docs/discoverability/index.mdx +1 -1
- package/docs/discoverability/llms-txt.mdx +1 -1
- package/docs/discoverability/metadata.mdx +17 -0
- package/docs/references/api-pages.mdx +74 -0
- package/docs/references/asyncapi.mdx +1 -1
- package/docs/references/graphql.mdx +2 -2
- package/docs/references/meta.ts +1 -1
- package/docs/references/openapi.mdx +102 -3
- 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/mintlify.md +37 -19
- package/skills/blume-migrate/references/nextra.md +2 -2
- package/skills/blume-migrate/references/starlight.md +2 -1
- package/skills/blume-migrate/scripts/mintlify-codemod.mjs +60 -14
- package/skills/blume-write-skill/SKILL.md +82 -0
- package/src/ai/agent-readability.ts +2 -1
- package/src/ai/agent-surface.ts +9 -7
- package/src/ai/ai-catalog.ts +6 -3
- 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/component-markdown.ts +75 -0
- package/src/ai/cors.ts +2 -0
- package/src/ai/index.ts +12 -0
- package/src/ai/llms.ts +6 -2
- package/src/ai/markdown.ts +2 -1
- package/src/ai/mcp/data.ts +6 -0
- package/src/ai/openapi-components.ts +9 -3
- package/src/ai/site-skill.ts +274 -0
- package/src/ai/skills.ts +35 -2
- package/src/ai/write-skill.ts +97 -0
- package/src/astro/generate.ts +132 -18
- package/src/astro/index.ts +1 -0
- package/src/astro/integration.ts +35 -0
- package/src/astro/module-types.ts +6 -0
- package/src/astro/templates.ts +283 -30
- 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 +5 -12
- package/src/audit/checks/sitemap.ts +2 -3
- package/src/audit/redirects.ts +46 -15
- package/src/audit/run.ts +2 -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/command-meta.ts +5 -0
- package/src/cli/commands/doctor.ts +22 -16
- package/src/cli/commands/skill.ts +96 -0
- package/src/cli/index.ts +5 -0
- package/src/cli/required-secrets.ts +71 -29
- package/src/components/BlumePage.astro +2 -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/Color.astro +1 -0
- 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/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/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 +104 -14
- package/src/components/islands/code-question.ts +62 -0
- package/src/components/islands/hooks.ts +110 -21
- package/src/components/islands/support-link.ts +68 -0
- package/src/components/layout/Analytics.astro +11 -2
- package/src/components/layout/Banner.astro +14 -10
- package/src/components/layout/Breadcrumbs.astro +6 -2
- 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/DrawerSwitchers.astro +51 -0
- package/src/components/layout/Empty.astro +2 -2
- package/src/components/layout/Header.astro +122 -80
- package/src/components/layout/LanguageSwitcher.astro +48 -15
- package/src/components/layout/NarrationPlayer.astro +1085 -0
- package/src/components/layout/NavSelector.astro +33 -7
- package/src/components/layout/PageFeedback.astro +54 -49
- package/src/components/layout/PageLayout.astro +71 -3
- package/src/components/layout/ReferenceLayout.astro +17 -6
- package/src/components/layout/RelatedPages.astro +42 -0
- package/src/components/layout/RootLayout.astro +308 -45
- package/src/components/layout/Search.astro +31 -17
- package/src/components/layout/SiteFooter.astro +124 -0
- package/src/components/layout/SiteMetatags.astro +16 -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/openapi/Operation.astro +161 -13
- package/src/components/openapi/RequestBody.astro +6 -4
- package/src/components/openapi/RequestPanel.astro +59 -7
- package/src/components/openapi/Responses.astro +6 -4
- package/src/components/openapi/async-snippets.ts +4 -4
- package/src/components/openapi/code-samples.ts +97 -0
- package/src/components/openapi/security.ts +6 -3
- package/src/components/openapi/snippets.ts +869 -8
- 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/builtin-tags.ts +5 -0
- package/src/core/client-address.ts +19 -0
- package/src/core/config-input.ts +194 -20
- package/src/core/data.ts +44 -4
- package/src/core/diagnostics.ts +3 -2
- package/src/core/footer.ts +26 -0
- package/src/core/graph.ts +39 -36
- package/src/core/i18n-ui.ts +82 -2
- package/src/core/includes.ts +20 -3
- package/src/core/links.ts +16 -2
- package/src/core/localizable.ts +26 -0
- package/src/core/navigation.ts +43 -12
- package/src/core/page-modes.ts +56 -0
- package/src/core/project-graph.ts +111 -5
- package/src/core/redirect-patterns.ts +491 -0
- package/src/core/related.ts +131 -0
- package/src/core/schema.ts +318 -64
- package/src/core/sources/normalize.ts +2 -1
- package/src/core/sources/read.ts +8 -3
- 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/deploy/artifacts.ts +77 -8
- package/src/deploy/cloudflare-negotiation.ts +35 -5
- package/src/deploy/headers.ts +2 -1
- package/src/deploy/node-headers.ts +63 -9
- package/src/deploy/platforms/cloudflare.ts +32 -0
- package/src/deploy/platforms/netlify.ts +33 -1
- package/src/deploy/platforms/vercel.ts +37 -2
- package/src/deploy/redirects.ts +91 -24
- package/src/deploy/vercel-negotiation.ts +36 -0
- package/src/markdown/api-rail.ts +259 -0
- package/src/markdown/code-title.ts +35 -9
- package/src/markdown/features.ts +3 -2
- package/src/markdown/fence-meta.ts +2 -0
- package/src/markdown/include.ts +11 -1
- package/src/markdown/index.ts +28 -5
- package/src/markdown/math.ts +4 -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 +7 -6
- package/src/openapi/model.ts +57 -24
- package/src/openapi/overlay.ts +256 -0
- package/src/openapi/parse.ts +55 -2
- package/src/openapi/references.ts +21 -5
- package/src/openapi/render-mdx.ts +5 -1
- 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 +20 -4
- package/src/registry/registry.ts +7 -0
- package/src/search/documents.ts +26 -2
- package/src/search/orama-index.ts +28 -3
- package/src/search/pagefind-ranking.ts +39 -0
- 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/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/prompts.ts +1 -0
- package/src/translate/validate.ts +8 -2
- package/src/upgrade/upgrade.ts +4 -171
- package/dist/cli/chunk-364znk6q.js +0 -75
- package/dist/cli/chunk-364znk6q.js.map +0 -11
- package/dist/cli/chunk-5m5nmvyq.js.map +0 -13
- package/dist/cli/chunk-9t7a85s3.js.map +0 -10
- package/dist/cli/chunk-c8chx29p.js.map +0 -36
- package/dist/cli/chunk-pehfxfta.js.map +0 -10
- package/dist/cli/chunk-pv29h0wf.js.map +0 -190
- package/dist/cli/chunk-q58y5e6a.js.map +0 -10
- package/dist/cli/chunk-wrr3j9w9.js.map +0 -35
- package/dist/cli/chunk-yfyb25rh.js.map +0 -14
- package/docs/content/sources.mdx +0 -334
- /package/dist/cli/{chunk-2eytanqx.js.map → chunk-1n228xqb.js.map} +0 -0
- /package/dist/cli/{chunk-35d4wj9f.js.map → chunk-2n4ps1b3.js.map} +0 -0
- /package/dist/cli/{chunk-e04dxsz1.js.map → chunk-4ejxbzee.js.map} +0 -0
- /package/dist/cli/{chunk-tzmab476.js.map → chunk-5ffywsmb.js.map} +0 -0
- /package/dist/cli/{chunk-2z928egk.js.map → chunk-5wj2qwn2.js.map} +0 -0
- /package/dist/cli/{chunk-b07cmahc.js.map → chunk-amrb6hgh.js.map} +0 -0
- /package/dist/cli/{chunk-akbpwfxc.js.map → chunk-cmtcnh9f.js.map} +0 -0
- /package/dist/cli/{chunk-crgn1q09.js.map → chunk-e7vvk5ya.js.map} +0 -0
- /package/dist/cli/{chunk-tkacnehg.js.map → chunk-gp84za0h.js.map} +0 -0
- /package/dist/cli/{chunk-r9rcc4w7.js.map → chunk-p804f3pe.js.map} +0 -0
- /package/dist/cli/{chunk-8ktnccpt.js.map → chunk-ta27feev.js.map} +0 -0
- /package/dist/cli/{chunk-a58773jm.js.map → chunk-y4ykbark.js.map} +0 -0
- /package/dist/cli/{chunk-p3v96n38.js.map → chunk-y5h4r2y0.js.map} +0 -0
- /package/dist/cli/{chunk-acanzt5p.js.map → chunk-yj17vs39.js.map} +0 -0
package/src/core/schema.ts
CHANGED
|
@@ -8,9 +8,19 @@ import {
|
|
|
8
8
|
} from "../ai/ask.ts";
|
|
9
9
|
import type { ComponentMarkdown } from "../ai/component-markdown.ts";
|
|
10
10
|
import { analyticsConfigSchema } from "../analytics/schema.ts";
|
|
11
|
+
import { captchaAdapterSchema } from "../captcha/schema.ts";
|
|
12
|
+
import {
|
|
13
|
+
API_ENDPOINT,
|
|
14
|
+
AUTH_METHODS,
|
|
15
|
+
PLAYGROUND_MODES,
|
|
16
|
+
} from "../components/content/api-page.ts";
|
|
17
|
+
import { consentConfigSchema } from "../consent/schema.ts";
|
|
11
18
|
import { resolvedDeploymentSchema } from "../deploy/adapters/registry.ts";
|
|
12
19
|
import type { CodeTheme } from "../markdown/themes.ts";
|
|
20
|
+
import { narrationProviderSchema } from "../narration/provider.ts";
|
|
13
21
|
import { normalizeRoute } from "../openapi/references.ts";
|
|
22
|
+
import { rateLimitConfigSchema } from "../ratelimit/schema.ts";
|
|
23
|
+
import { playgroundSchema } from "../reference/options.ts";
|
|
14
24
|
import {
|
|
15
25
|
referenceConfigSchema,
|
|
16
26
|
removedReferenceKeysHint,
|
|
@@ -21,6 +31,7 @@ import {
|
|
|
21
31
|
resolvedSearchAdapterSchema,
|
|
22
32
|
} from "../search/adapters/registry.ts";
|
|
23
33
|
import type { SearchAdapterInput } from "../search/adapters/registry.ts";
|
|
34
|
+
import { ownedMetatag } from "../seo/metatags.ts";
|
|
24
35
|
import { normalizeXHandle } from "../seo/x-handle.ts";
|
|
25
36
|
import { filesystem } from "../sources/filesystem.ts";
|
|
26
37
|
import {
|
|
@@ -28,14 +39,19 @@ import {
|
|
|
28
39
|
resolvedSourceAdapterSchema,
|
|
29
40
|
} from "../sources/registry.ts";
|
|
30
41
|
import { FONT_SLUGS, isFontSlug } from "../theme/fonts.ts";
|
|
31
|
-
import { normalizeBasePath } from "./base-path.ts";
|
|
42
|
+
import { isExternalUrl, normalizeBasePath } from "./base-path.ts";
|
|
43
|
+
import { FOOTER_SOCIALS } from "./footer.ts";
|
|
32
44
|
import { PUBLIC_HOST_URL } from "./github.ts";
|
|
33
45
|
import { uiLocaleOverridesSchema } from "./i18n-ui.ts";
|
|
34
46
|
import { openInChatProviders } from "./open-in-chat.ts";
|
|
47
|
+
import { PAGE_MODES } from "./page-modes.ts";
|
|
48
|
+
import { redirectPatternError } from "./redirect-patterns.ts";
|
|
49
|
+
import { MAX_RELATED } from "./related.ts";
|
|
35
50
|
import { isStandardSchema } from "./standard-schema.ts";
|
|
36
51
|
import type { StandardSchema } from "./standard-schema.ts";
|
|
37
52
|
import { trimEnd } from "./trim.ts";
|
|
38
53
|
import { unrecognizedKeysMessage } from "./unrecognized-keys.ts";
|
|
54
|
+
import { VARIABLE_NAME } from "./variables.ts";
|
|
39
55
|
|
|
40
56
|
/**
|
|
41
57
|
* An absolute HTTP(S) URL, for any field that lands verbatim in an `href` —
|
|
@@ -119,6 +135,15 @@ const dateSchema = z
|
|
|
119
135
|
const sidebarDisplaySchema = z.enum(["flat", "group", "page"]);
|
|
120
136
|
export type SidebarDisplay = z.infer<typeof sidebarDisplaySchema>;
|
|
121
137
|
|
|
138
|
+
/**
|
|
139
|
+
* How a group's own page lists the group's other pages below its content:
|
|
140
|
+
* `card` a grid of cards, `accordion` a list with each subgroup collapsible,
|
|
141
|
+
* `none` no listing (the default). A group without its own page has nowhere
|
|
142
|
+
* to show one. Nested groups inherit the nearest setting above them.
|
|
143
|
+
*/
|
|
144
|
+
const directoryModeSchema = z.enum(["accordion", "card", "none"]);
|
|
145
|
+
export type DirectoryMode = z.infer<typeof directoryModeSchema>;
|
|
146
|
+
|
|
122
147
|
// ---------------------------------------------------------------------------
|
|
123
148
|
// Page frontmatter
|
|
124
149
|
// ---------------------------------------------------------------------------
|
|
@@ -155,16 +180,18 @@ const seoMetaSchema = z.strictObject({
|
|
|
155
180
|
x: z.strictObject({ creator: xHandleSchema }).optional(),
|
|
156
181
|
});
|
|
157
182
|
|
|
158
|
-
const searchMetaSchema = z.strictObject(
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
)
|
|
183
|
+
const searchMetaSchema = z.strictObject({
|
|
184
|
+
/**
|
|
185
|
+
* Multiply the page's search relevance: above 1 ranks it higher, below 1
|
|
186
|
+
* lower. Keep it to a few key pages, and under 10: a large boost floats a
|
|
187
|
+
* page over better matches.
|
|
188
|
+
*/
|
|
189
|
+
boost: z.number().positive().optional(),
|
|
190
|
+
exclude: z.boolean().default(false),
|
|
191
|
+
/** Extra terms the page is found by, beyond its own text. */
|
|
192
|
+
keywords: z.array(z.string()).optional(),
|
|
193
|
+
tags: z.array(z.string()).optional(),
|
|
194
|
+
});
|
|
168
195
|
|
|
169
196
|
const aiMetaSchema = z.strictObject({
|
|
170
197
|
/** Exclude this page from llms.txt and llms-full.txt. */
|
|
@@ -201,9 +228,31 @@ const authorSchema = z.union([
|
|
|
201
228
|
// `.default({})` on an object with inner defaults (or a transform) would
|
|
202
229
|
// resolve to a bare `{}` instead of the fully-defaulted shape.
|
|
203
230
|
|
|
231
|
+
/** A `related` link: a root-relative page path or an absolute URL. */
|
|
232
|
+
const relatedLinkSchema = z
|
|
233
|
+
.string()
|
|
234
|
+
.refine((link) => link.startsWith("/") || isExternalUrl(link), {
|
|
235
|
+
message:
|
|
236
|
+
'A related link is a root-relative path ("/guides/setup") or an absolute URL.',
|
|
237
|
+
});
|
|
238
|
+
|
|
204
239
|
/** Frontmatter accepted on any content page. */
|
|
205
240
|
const pageMetaBaseSchema = z.strictObject({
|
|
206
241
|
ai: aiMetaSchema.prefault({}),
|
|
242
|
+
/**
|
|
243
|
+
* A hand-written endpoint: an HTTP method and a path or full URL
|
|
244
|
+
* (`POST /v1/users`). The page's `<ParamField>`s build its playground and
|
|
245
|
+
* request samples.
|
|
246
|
+
*/
|
|
247
|
+
api: z
|
|
248
|
+
.string()
|
|
249
|
+
.refine((value) => API_ENDPOINT.test(value.trim()), {
|
|
250
|
+
message:
|
|
251
|
+
'api takes an HTTP method and a path or URL, like "POST /v1/users" or "GET https://api.acme.com/v1/users/{id}".',
|
|
252
|
+
})
|
|
253
|
+
.optional(),
|
|
254
|
+
/** How this page's endpoint authenticates, over the site's `api.auth`. */
|
|
255
|
+
authMethod: z.enum(AUTH_METHODS).optional(),
|
|
207
256
|
/** Post author(s) for blog/changelog content; preserved, not yet rendered. */
|
|
208
257
|
authors: z.union([authorSchema, z.array(authorSchema)]).optional(),
|
|
209
258
|
changelog: changelogMetaSchema.optional(),
|
|
@@ -216,7 +265,37 @@ const pageMetaBaseSchema = z.strictObject({
|
|
|
216
265
|
icon: iconName.optional(),
|
|
217
266
|
/** Overrides the git-derived last-modified date when `lastModified` is on. */
|
|
218
267
|
lastModified: dateSchema.optional(),
|
|
268
|
+
/** What the page shows around its content (see `core/page-modes.ts`). */
|
|
269
|
+
mode: z.enum(PAGE_MODES).optional(),
|
|
270
|
+
/** `false` keeps the "Listen to this page" player off this page. */
|
|
271
|
+
narration: z.boolean().default(true),
|
|
219
272
|
noindex: z.boolean().default(false),
|
|
273
|
+
/** `false` drops the previous/next links at the foot of this page. */
|
|
274
|
+
pagination: z.boolean().default(true),
|
|
275
|
+
/** What an `api` page's playground shows: `interactive`, `simple` (samples only), or `none`. */
|
|
276
|
+
playground: z.enum(PLAYGROUND_MODES).optional(),
|
|
277
|
+
/**
|
|
278
|
+
* Pages to suggest at the foot of this one: root-relative paths, absolute
|
|
279
|
+
* URLs, or `{ Title: link }` to name a link. `false` lists none.
|
|
280
|
+
*/
|
|
281
|
+
related: z
|
|
282
|
+
.union([
|
|
283
|
+
z.literal(false),
|
|
284
|
+
z
|
|
285
|
+
.array(
|
|
286
|
+
z.union([
|
|
287
|
+
relatedLinkSchema,
|
|
288
|
+
z
|
|
289
|
+
.record(z.string().min(1), relatedLinkSchema)
|
|
290
|
+
.refine((entry) => Object.keys(entry).length === 1, {
|
|
291
|
+
message:
|
|
292
|
+
'A titled related entry has one title and one link: { "Title": "/path" }.',
|
|
293
|
+
}),
|
|
294
|
+
])
|
|
295
|
+
)
|
|
296
|
+
.max(MAX_RELATED),
|
|
297
|
+
])
|
|
298
|
+
.optional(),
|
|
220
299
|
search: searchMetaSchema.prefault({}),
|
|
221
300
|
seo: seoMetaSchema.prefault({}),
|
|
222
301
|
sidebar: sidebarMetaSchema.prefault({}),
|
|
@@ -292,6 +371,8 @@ const customKeySchemaRecord = (where: string) =>
|
|
|
292
371
|
|
|
293
372
|
export const folderMetaSchema = z.strictObject({
|
|
294
373
|
collapsed: z.boolean().optional(),
|
|
374
|
+
/** List the group's pages on its index page; inherited by nested folders. */
|
|
375
|
+
directory: directoryModeSchema.optional(),
|
|
295
376
|
/** Render mode for this group; overrides `navigation.sidebar.display`. */
|
|
296
377
|
display: sidebarDisplaySchema.optional(),
|
|
297
378
|
icon: iconName.optional(),
|
|
@@ -333,19 +414,77 @@ const logoConfigSchema = z.union([
|
|
|
333
414
|
}),
|
|
334
415
|
]);
|
|
335
416
|
|
|
336
|
-
/**
|
|
417
|
+
/**
|
|
418
|
+
* A label that may localize (header tabs and links, the banner, the footer):
|
|
419
|
+
* a plain string, or a map of locale code to label (`{ en: "Docs", ja: "ドキュメント" }`).
|
|
420
|
+
* Resolved per locale by `core/localizable.ts` — the active locale's entry
|
|
421
|
+
* wins, then the default locale's, then the map's first entry — so a
|
|
422
|
+
* single-locale site can keep plain strings and an i18n site can translate
|
|
423
|
+
* its labels without forking the config.
|
|
424
|
+
*/
|
|
425
|
+
const localizableLabelSchema = z.union([
|
|
426
|
+
z.string(),
|
|
427
|
+
z
|
|
428
|
+
.record(z.string(), z.string())
|
|
429
|
+
.refine((value) => Object.keys(value).length > 0, {
|
|
430
|
+
message: "Provide at least one locale's label.",
|
|
431
|
+
}),
|
|
432
|
+
]);
|
|
433
|
+
|
|
434
|
+
export type LocalizableLabel = z.infer<typeof localizableLabelSchema>;
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Site-wide announcement banner: a string, or text with an optional link. The
|
|
438
|
+
* object form's `content` and link `text` may be per-locale maps.
|
|
439
|
+
*/
|
|
337
440
|
const bannerConfigSchema = z.union([
|
|
338
441
|
z.string(),
|
|
339
442
|
z.strictObject({
|
|
340
|
-
content:
|
|
443
|
+
content: localizableLabelSchema,
|
|
341
444
|
/** Show a dismiss button; the choice is remembered per visitor. */
|
|
342
445
|
dismissible: z.boolean().default(false),
|
|
343
|
-
/**
|
|
446
|
+
/**
|
|
447
|
+
* Stable key for remembering dismissal; defaults to the content (the
|
|
448
|
+
* default locale's, for a per-locale map).
|
|
449
|
+
*/
|
|
344
450
|
id: z.string().optional(),
|
|
345
|
-
link: z
|
|
451
|
+
link: z
|
|
452
|
+
.strictObject({ href: z.string(), text: localizableLabelSchema })
|
|
453
|
+
.optional(),
|
|
346
454
|
}),
|
|
347
455
|
]);
|
|
348
456
|
|
|
457
|
+
/**
|
|
458
|
+
* Defaults for hand-written endpoint pages (`api` frontmatter): the server a
|
|
459
|
+
* path joins, how requests authenticate, and the playground, which takes an
|
|
460
|
+
* OpenAPI reference's `playground` option.
|
|
461
|
+
*/
|
|
462
|
+
const apiConfigSchema = z.strictObject({
|
|
463
|
+
auth: z
|
|
464
|
+
.strictObject({
|
|
465
|
+
method: z.enum(AUTH_METHODS),
|
|
466
|
+
/** The header an API key goes in. Defaults to `x-api-key`. */
|
|
467
|
+
name: z.string().optional(),
|
|
468
|
+
})
|
|
469
|
+
.optional(),
|
|
470
|
+
playground: playgroundSchema,
|
|
471
|
+
/** The base URL an `api` path joins (`https://api.acme.com/v1`). */
|
|
472
|
+
server: z.string().optional(),
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* The site footer: a row of links, and social profile icons. Unset, the site
|
|
477
|
+
* has no footer (a `components.ts` `Footer` still renders).
|
|
478
|
+
*/
|
|
479
|
+
const footerConfigSchema = z.strictObject({
|
|
480
|
+
/** Links in one row, in the order written. */
|
|
481
|
+
links: z
|
|
482
|
+
.array(z.strictObject({ href: z.string(), label: localizableLabelSchema }))
|
|
483
|
+
.default([]),
|
|
484
|
+
/** Social profiles, platform to URL, shown as icons in the order written. */
|
|
485
|
+
socials: z.partialRecord(z.enum(FOOTER_SOCIALS), z.string()).default({}),
|
|
486
|
+
});
|
|
487
|
+
|
|
349
488
|
/** A validated `content.sources` entry: an adapter descriptor from `blume/sources`. */
|
|
350
489
|
export type { ContentSourceAdapter } from "../sources/registry.ts";
|
|
351
490
|
|
|
@@ -426,25 +565,6 @@ const contentConfigSchema = z
|
|
|
426
565
|
],
|
|
427
566
|
}));
|
|
428
567
|
|
|
429
|
-
/**
|
|
430
|
-
* A header label that may localize: a plain string, or a map of locale code to
|
|
431
|
-
* label (`{ en: "Docs", ja: "ドキュメント" }`). Resolved when each locale's
|
|
432
|
-
* navigation is built — the active locale's entry wins, then the default
|
|
433
|
-
* locale's, then the map's first entry — so a single-locale site can keep
|
|
434
|
-
* plain strings and an i18n site can translate its header without forking the
|
|
435
|
-
* config.
|
|
436
|
-
*/
|
|
437
|
-
const localizableLabelSchema = z.union([
|
|
438
|
-
z.string(),
|
|
439
|
-
z
|
|
440
|
-
.record(z.string(), z.string())
|
|
441
|
-
.refine((value) => Object.keys(value).length > 0, {
|
|
442
|
-
message: "Provide at least one locale's label.",
|
|
443
|
-
}),
|
|
444
|
-
]);
|
|
445
|
-
|
|
446
|
-
export type LocalizableLabel = z.infer<typeof localizableLabelSchema>;
|
|
447
|
-
|
|
448
568
|
const navTabSchema = z.strictObject({
|
|
449
569
|
// Rejected empty rather than accepted: an empty `href` would render a link to
|
|
450
570
|
// nowhere, and it can't mean "resolve it for me" either — that's what
|
|
@@ -484,9 +604,6 @@ const navSelectorSchema = z.strictObject({
|
|
|
484
604
|
label: z.string(),
|
|
485
605
|
});
|
|
486
606
|
|
|
487
|
-
const directoryModeSchema = z.enum(["accordion", "card", "none"]);
|
|
488
|
-
export type DirectoryMode = z.infer<typeof directoryModeSchema>;
|
|
489
|
-
|
|
490
607
|
/** A node in an explicit sidebar config: a page reference or a group/link. */
|
|
491
608
|
export type SidebarItemConfig =
|
|
492
609
|
| string
|
|
@@ -842,6 +959,9 @@ const aiConfigFields = {
|
|
|
842
959
|
// origin. Each URL is reduced to its origin so a trailing slash or path
|
|
843
960
|
// can't defeat the exact match the route performs. Read by the
|
|
844
961
|
// generated route only; an external `endpoint` owns its own CORS.
|
|
962
|
+
// A bot check from `blume/captcha`: the panel sends a token with each
|
|
963
|
+
// question and the generated route verifies it before the model runs.
|
|
964
|
+
captcha: captchaAdapterSchema.optional(),
|
|
845
965
|
cors: z
|
|
846
966
|
.array(
|
|
847
967
|
z.union([
|
|
@@ -890,6 +1010,27 @@ const aiConfigFields = {
|
|
|
890
1010
|
})
|
|
891
1011
|
)
|
|
892
1012
|
.default([]),
|
|
1013
|
+
// Where the panel's Contact support link goes: an email address
|
|
1014
|
+
// (`mailto:`, which starts an email with the conversation), a URL,
|
|
1015
|
+
// or a root-relative page (which get the conversation's `thread` id).
|
|
1016
|
+
support: z
|
|
1017
|
+
.string()
|
|
1018
|
+
.refine(
|
|
1019
|
+
(link) =>
|
|
1020
|
+
link.startsWith("mailto:") ||
|
|
1021
|
+
link.startsWith("/") ||
|
|
1022
|
+
isExternalUrl(link),
|
|
1023
|
+
{
|
|
1024
|
+
message:
|
|
1025
|
+
'ai.assistant.support is a mailto: address, a URL, or a root-relative path, like "mailto:help@example.com".',
|
|
1026
|
+
}
|
|
1027
|
+
)
|
|
1028
|
+
.optional(),
|
|
1029
|
+
// Search and read-page tools the model can call over several steps.
|
|
1030
|
+
// No default here: unset follows the adapter (`toolsByDefault` in
|
|
1031
|
+
// `ai/ask.ts`), on for the hosted catalogs and off for an
|
|
1032
|
+
// OpenAI-compatible backend that may not speak tool calling.
|
|
1033
|
+
tools: z.boolean().optional(),
|
|
893
1034
|
},
|
|
894
1035
|
assistantMovedFieldsHint
|
|
895
1036
|
)
|
|
@@ -946,7 +1087,7 @@ const aiConfigSchema = z.strictObject(
|
|
|
946
1087
|
const featuredLinkSchema = z.strictObject({
|
|
947
1088
|
href: z.string(),
|
|
948
1089
|
icon: iconName.optional(),
|
|
949
|
-
label:
|
|
1090
|
+
label: localizableLabelSchema,
|
|
950
1091
|
});
|
|
951
1092
|
|
|
952
1093
|
/**
|
|
@@ -956,7 +1097,7 @@ const featuredLinkSchema = z.strictObject({
|
|
|
956
1097
|
*/
|
|
957
1098
|
const headerActionSchema = z.strictObject({
|
|
958
1099
|
href: z.string(),
|
|
959
|
-
label:
|
|
1100
|
+
label: localizableLabelSchema,
|
|
960
1101
|
});
|
|
961
1102
|
|
|
962
1103
|
const navigationConfigSchema = z.strictObject({
|
|
@@ -973,7 +1114,7 @@ const navigationConfigSchema = z.strictObject({
|
|
|
973
1114
|
/** Pinned links shown above the generated sidebar sections. */
|
|
974
1115
|
featured: z.array(featuredLinkSchema).default([]),
|
|
975
1116
|
/**
|
|
976
|
-
* The GitHub link in the
|
|
1117
|
+
* The GitHub link in the footer. `true` derives it from `github`, `false`
|
|
977
1118
|
* hides it, and an absolute URL points it anywhere on GitHub — an
|
|
978
1119
|
* organization, say, when the docs repo itself is private and `github` has
|
|
979
1120
|
* to stay unset. The mark stays the GitHub one, so a URL elsewhere belongs in
|
|
@@ -1026,6 +1167,45 @@ const exportConfigSchema = z
|
|
|
1026
1167
|
isBoolean(value) ? { epub: value, pdf: value } : value
|
|
1027
1168
|
);
|
|
1028
1169
|
|
|
1170
|
+
// The "Was this page helpful?" rating, on by default. `comments: true` adds a
|
|
1171
|
+
// box after the rating where the reader can say more, sent through the
|
|
1172
|
+
// analytics adapters as a `feedback_comment` event. Both forms normalize to
|
|
1173
|
+
// `{ enabled, comments }`.
|
|
1174
|
+
const feedbackConfigSchema = z
|
|
1175
|
+
.union([
|
|
1176
|
+
z.boolean(),
|
|
1177
|
+
z.strictObject({
|
|
1178
|
+
comments: z.boolean().default(false),
|
|
1179
|
+
enabled: z.boolean().default(true),
|
|
1180
|
+
}),
|
|
1181
|
+
])
|
|
1182
|
+
// With the rating off there is nothing to comment on.
|
|
1183
|
+
.transform((value) =>
|
|
1184
|
+
isBoolean(value)
|
|
1185
|
+
? { comments: false, enabled: value }
|
|
1186
|
+
: { comments: value.enabled && value.comments, enabled: value.enabled }
|
|
1187
|
+
);
|
|
1188
|
+
|
|
1189
|
+
// "Listen to this page". Off by default. `true` reads pages aloud with the
|
|
1190
|
+
// reader's browser voices, which needs no key and works on any host; an object
|
|
1191
|
+
// with a `provider` (`gateway()` from `blume/ai`) generates neural audio at
|
|
1192
|
+
// build instead, one cached clip per sentence, and falls back to the browser's
|
|
1193
|
+
// voices where no clips exist (`blume dev`, a build without the key). Both
|
|
1194
|
+
// normalize to `{ enabled, provider }`.
|
|
1195
|
+
const narrationConfigSchema = z
|
|
1196
|
+
.union([
|
|
1197
|
+
z.boolean(),
|
|
1198
|
+
z.strictObject({
|
|
1199
|
+
enabled: z.boolean().default(true),
|
|
1200
|
+
provider: narrationProviderSchema.optional(),
|
|
1201
|
+
}),
|
|
1202
|
+
])
|
|
1203
|
+
.transform((value) =>
|
|
1204
|
+
isBoolean(value)
|
|
1205
|
+
? { enabled: value, provider: null }
|
|
1206
|
+
: { enabled: value.enabled, provider: value.provider ?? null }
|
|
1207
|
+
);
|
|
1208
|
+
|
|
1029
1209
|
/** A configured locale: ISO-ish code plus display metadata for the switcher. */
|
|
1030
1210
|
const localeSchema = z.strictObject({
|
|
1031
1211
|
code: z.string().min(1),
|
|
@@ -1055,6 +1235,12 @@ const i18nConfigSchema = z
|
|
|
1055
1235
|
locales: z.array(localeSchema).min(1),
|
|
1056
1236
|
/** `"dir"`: locale directories (`fr/page.mdx`). `"dot"`: filename suffix (`page.fr.mdx`). */
|
|
1057
1237
|
parser: z.enum(["dir", "dot"]).default("dir"),
|
|
1238
|
+
/**
|
|
1239
|
+
* Send a visitor who lands on the default language's home page to the
|
|
1240
|
+
* home page in their browser's preferred language, until they pick a
|
|
1241
|
+
* language with the switcher. Off by default.
|
|
1242
|
+
*/
|
|
1243
|
+
routeByBrowserLanguage: z.boolean().default(false),
|
|
1058
1244
|
/** Per-locale UI string overrides: `{ fr: { search: { button: "…" } } }`. */
|
|
1059
1245
|
ui: uiLocaleOverridesSchema.optional(),
|
|
1060
1246
|
})
|
|
@@ -1156,13 +1342,6 @@ const versionsConfigSchema = z
|
|
|
1156
1342
|
}
|
|
1157
1343
|
});
|
|
1158
1344
|
|
|
1159
|
-
/**
|
|
1160
|
-
* A pattern segment in a redirect path: a named `:param` segment or a `*`
|
|
1161
|
-
* splat. `from` is matched as an exact path, and hosts disagree on patterns —
|
|
1162
|
-
* a static build would even write a literal `:slug` folder — so both ends are
|
|
1163
|
-
* checked. An absolute `to` URL's own scheme and host are skipped.
|
|
1164
|
-
*/
|
|
1165
|
-
const REDIRECT_PATTERN = /(?:^|\/):[A-Za-z_]|\*/u;
|
|
1166
1345
|
const URL_ORIGIN = /^[a-z][\d+.a-z-]*:\/\/[^/]*/iu;
|
|
1167
1346
|
|
|
1168
1347
|
/**
|
|
@@ -1183,23 +1362,33 @@ const REDIRECT_START = {
|
|
|
1183
1362
|
},
|
|
1184
1363
|
};
|
|
1185
1364
|
|
|
1186
|
-
const
|
|
1187
|
-
z
|
|
1188
|
-
.
|
|
1189
|
-
|
|
1190
|
-
message: REDIRECT_START[end].message,
|
|
1191
|
-
})
|
|
1192
|
-
.refine((path) => !REDIRECT_PATTERN.test(path.replace(URL_ORIGIN, "")), {
|
|
1193
|
-
message: `redirects take exact paths: \`${end}\` can't hold a \`:param\` segment or a \`*\` wildcard. Add one redirect per path, or put pattern rules in your host's redirect config (vercel.json, _redirects).`,
|
|
1194
|
-
});
|
|
1365
|
+
const redirectPath = (end: "from" | "to") =>
|
|
1366
|
+
z.string().refine(REDIRECT_START[end].allows, {
|
|
1367
|
+
message: REDIRECT_START[end].message,
|
|
1368
|
+
});
|
|
1195
1369
|
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1370
|
+
/**
|
|
1371
|
+
* A redirect: an exact path, or a pattern (`/beta/:slug*`, `/old/*`) whose
|
|
1372
|
+
* captures `to` may read (see `core/redirect-patterns.ts`).
|
|
1373
|
+
*/
|
|
1374
|
+
const redirectSchema = z
|
|
1375
|
+
.strictObject({
|
|
1376
|
+
from: redirectPath("from"),
|
|
1377
|
+
status: z
|
|
1378
|
+
.union([z.literal(301), z.literal(302), z.literal(307), z.literal(308)])
|
|
1379
|
+
.default(301),
|
|
1380
|
+
to: redirectPath("to"),
|
|
1381
|
+
})
|
|
1382
|
+
.superRefine((redirect, ctx) => {
|
|
1383
|
+
const error = redirectPatternError(redirect.from, redirect.to);
|
|
1384
|
+
if (error) {
|
|
1385
|
+
ctx.addIssue({
|
|
1386
|
+
code: z.ZodIssueCode.custom,
|
|
1387
|
+
message: error.message,
|
|
1388
|
+
path: [error.end],
|
|
1389
|
+
});
|
|
1390
|
+
}
|
|
1391
|
+
});
|
|
1203
1392
|
|
|
1204
1393
|
/**
|
|
1205
1394
|
* One authorized remote image source, passed through to Astro's
|
|
@@ -1417,6 +1606,26 @@ type SoftwareResolved = z.output<typeof softwareConfigSchema>;
|
|
|
1417
1606
|
|
|
1418
1607
|
/** Discoverability features: OG images, feeds, sitemap, structured data. */
|
|
1419
1608
|
const seoConfigFields = {
|
|
1609
|
+
/**
|
|
1610
|
+
* Meta tags written into every page's head, name to content: site
|
|
1611
|
+
* verification, `theme-color`, and anything else Blume has no setting for.
|
|
1612
|
+
* A tag Blume writes itself is refused with the setting that controls it.
|
|
1613
|
+
*/
|
|
1614
|
+
metatags: z
|
|
1615
|
+
.record(z.string().min(1), z.string())
|
|
1616
|
+
.optional()
|
|
1617
|
+
.superRefine((tags, ctx) => {
|
|
1618
|
+
for (const name of Object.keys(tags ?? {})) {
|
|
1619
|
+
const owner = ownedMetatag(name);
|
|
1620
|
+
if (owner) {
|
|
1621
|
+
ctx.addIssue({
|
|
1622
|
+
code: "custom",
|
|
1623
|
+
message: `seo.metatags can't set "${name}": Blume writes that tag. Set it with ${owner}.`,
|
|
1624
|
+
path: [name],
|
|
1625
|
+
});
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
}),
|
|
1420
1629
|
og: ogConfigSchema.default({}),
|
|
1421
1630
|
/** The organization behind the site, as an `Organization` JSON-LD node. */
|
|
1422
1631
|
organization: organizationConfigSchema.optional(),
|
|
@@ -1519,6 +1728,14 @@ const agentsConfigSchema = z.strictObject({
|
|
|
1519
1728
|
.default({}),
|
|
1520
1729
|
/** Expose the docs as an MCP server for connecting agents. */
|
|
1521
1730
|
mcp: mcpConfigSchema.prefault({}),
|
|
1731
|
+
/**
|
|
1732
|
+
* Generate the site's own agent skill: a `SKILL.md` named after the site,
|
|
1733
|
+
* built from its navigation, page descriptions, and agent surfaces, served
|
|
1734
|
+
* at `/skill.md` and in the skills discovery index. Needs a
|
|
1735
|
+
* `deployment.site`. A skill in `agents.skills` with the same name replaces
|
|
1736
|
+
* it. On by default.
|
|
1737
|
+
*/
|
|
1738
|
+
skillMd: z.boolean().default(true),
|
|
1522
1739
|
/**
|
|
1523
1740
|
* Publish Agent Skills for discovery: a directory (resolved against the
|
|
1524
1741
|
* project root) whose subdirectories each hold a `SKILL.md`. The build
|
|
@@ -1867,6 +2084,7 @@ export const blumeConfigSchema = z
|
|
|
1867
2084
|
ai: aiConfigSchema.prefault({}),
|
|
1868
2085
|
// Adapters from `blume/analytics`, each a serializable descriptor.
|
|
1869
2086
|
analytics: analyticsConfigSchema,
|
|
2087
|
+
api: apiConfigSchema.prefault({}),
|
|
1870
2088
|
banner: bannerConfigSchema.optional(),
|
|
1871
2089
|
/**
|
|
1872
2090
|
* Site-wide mount point prepended to every generated route (e.g. `/docs`),
|
|
@@ -1884,6 +2102,8 @@ export const blumeConfigSchema = z
|
|
|
1884
2102
|
})
|
|
1885
2103
|
.optional()
|
|
1886
2104
|
.transform((value) => normalizeBasePath(value)),
|
|
2105
|
+
// An adapter from `blume/consent`; analytics waits for the reader.
|
|
2106
|
+
consent: consentConfigSchema,
|
|
1887
2107
|
content: contentConfigSchema.prefault({}),
|
|
1888
2108
|
/**
|
|
1889
2109
|
* Date presentation for the "last updated" stamp and the changelog timeline.
|
|
@@ -1907,7 +2127,8 @@ export const blumeConfigSchema = z
|
|
|
1907
2127
|
*/
|
|
1908
2128
|
examples: examplesConfigSchema.prefault("examples"),
|
|
1909
2129
|
export: exportConfigSchema.prefault(false),
|
|
1910
|
-
feedback:
|
|
2130
|
+
feedback: feedbackConfigSchema.prefault(true),
|
|
2131
|
+
footer: footerConfigSchema.optional(),
|
|
1911
2132
|
/** Opt-in custom frontmatter keys, validated by user-supplied schemas. */
|
|
1912
2133
|
frontmatter: frontmatterConfigSchema.prefault({}),
|
|
1913
2134
|
github: githubConfigSchema.optional(),
|
|
@@ -1917,7 +2138,10 @@ export const blumeConfigSchema = z
|
|
|
1917
2138
|
lastModified: lastModifiedConfigSchema.default(false),
|
|
1918
2139
|
logo: logoConfigSchema.optional(),
|
|
1919
2140
|
markdown: markdownConfigSchema.prefault({}),
|
|
2141
|
+
narration: narrationConfigSchema.prefault(false),
|
|
1920
2142
|
navigation: navigationConfigSchema.prefault({}),
|
|
2143
|
+
// An adapter from `blume/ratelimit`, `memory()` by default; `false` is off.
|
|
2144
|
+
rateLimit: rateLimitConfigSchema,
|
|
1921
2145
|
react: reactConfigSchema.prefault({}),
|
|
1922
2146
|
redirects: z.array(redirectSchema).default([]),
|
|
1923
2147
|
/** API references: adapters from `blume/reference`, each a serializable descriptor. */
|
|
@@ -1927,6 +2151,23 @@ export const blumeConfigSchema = z
|
|
|
1927
2151
|
theme: themeConfigSchema.prefault({}),
|
|
1928
2152
|
title: z.string().default("Documentation"),
|
|
1929
2153
|
toc: tocConfigSchema,
|
|
2154
|
+
/**
|
|
2155
|
+
* Content variables: `{{name}}` in a page body reads the value (see
|
|
2156
|
+
* `core/variables.ts`). Names take letters, digits, `_`, and `-`;
|
|
2157
|
+
* values are one line, so a substitution never moves the lines a
|
|
2158
|
+
* diagnostic points at.
|
|
2159
|
+
*/
|
|
2160
|
+
variables: z
|
|
2161
|
+
.record(
|
|
2162
|
+
z.string().regex(VARIABLE_NAME, {
|
|
2163
|
+
message:
|
|
2164
|
+
"Variable names take letters, digits, `_`, and `-`, like `version` or `api-url`.",
|
|
2165
|
+
}),
|
|
2166
|
+
z.string().refine((value) => !/[\r\n]/u.test(value), {
|
|
2167
|
+
message: "A variable's value must fit on one line.",
|
|
2168
|
+
})
|
|
2169
|
+
)
|
|
2170
|
+
.default({}),
|
|
1930
2171
|
versions: versionsConfigSchema.optional(),
|
|
1931
2172
|
},
|
|
1932
2173
|
{
|
|
@@ -1987,6 +2228,19 @@ export const blumeConfigSchema = z
|
|
|
1987
2228
|
}
|
|
1988
2229
|
}
|
|
1989
2230
|
}
|
|
2231
|
+
// Cloudflare's rate limiting is a Worker binding, which only a Cloudflare
|
|
2232
|
+
// deployment has.
|
|
2233
|
+
if (
|
|
2234
|
+
config.rateLimit?.kind === "cloudflare" &&
|
|
2235
|
+
config.deployment.kind !== "cloudflare"
|
|
2236
|
+
) {
|
|
2237
|
+
ctx.addIssue({
|
|
2238
|
+
code: z.ZodIssueCode.custom,
|
|
2239
|
+
message:
|
|
2240
|
+
'rateLimit: cloudflare() counts with a Workers binding, so it needs `deployment: cloudflare()` from "blume/deploy". On another host, use upstash() or memory() from "blume/ratelimit".',
|
|
2241
|
+
path: ["rateLimit"],
|
|
2242
|
+
});
|
|
2243
|
+
}
|
|
1990
2244
|
});
|
|
1991
2245
|
|
|
1992
2246
|
/** Resolved config: every field present after defaults are applied. */
|
|
@@ -14,6 +14,7 @@ import { occupySlug, parseHeadingMarkers } from "../heading-markers.ts";
|
|
|
14
14
|
import { localePlacement, localizeRoute } from "../i18n.ts";
|
|
15
15
|
import { titleWord } from "../navigation.ts";
|
|
16
16
|
import { stripOrderingPrefix } from "../ordering-prefix.ts";
|
|
17
|
+
import { relatedPageLinks } from "../related.ts";
|
|
17
18
|
import { pageMetaSchema } from "../schema.ts";
|
|
18
19
|
import type {
|
|
19
20
|
FrontmatterExtend,
|
|
@@ -1554,7 +1555,7 @@ export const normalizeEntry = (
|
|
|
1554
1555
|
id: `${ctx.source.name}:${entry.ref}`,
|
|
1555
1556
|
includes: entryIncludes(entry),
|
|
1556
1557
|
lastModified: meta.lastModified ?? entry.lastModified,
|
|
1557
|
-
links: entryLinks(entry),
|
|
1558
|
+
links: [...entryLinks(entry), ...relatedPageLinks(meta.related)],
|
|
1558
1559
|
meta,
|
|
1559
1560
|
monolingual: ctx.source.monolingual,
|
|
1560
1561
|
navPath,
|
package/src/core/sources/read.ts
CHANGED
|
@@ -2,10 +2,14 @@ import { readFile } from "node:fs/promises";
|
|
|
2
2
|
|
|
3
3
|
import { expandIncludes, hasIncludeStatements } from "../includes.ts";
|
|
4
4
|
import type { PageRecord } from "../types.ts";
|
|
5
|
+
import { substituteVariablesInBody } from "../variables.ts";
|
|
6
|
+
import type { ContentVariables } from "../variables.ts";
|
|
5
7
|
import type { ContentSource } from "./types.ts";
|
|
6
8
|
|
|
7
9
|
/** The subset of a scanned project the entry reader needs. */
|
|
8
10
|
export interface EntryReadContext {
|
|
11
|
+
/** The site's content variables, substituted into what's read. */
|
|
12
|
+
config?: { variables?: ContentVariables };
|
|
9
13
|
/** The instantiated sources, keyed for `read()` lookups. */
|
|
10
14
|
sources?: ContentSource[];
|
|
11
15
|
}
|
|
@@ -48,9 +52,10 @@ export const readExpandedEntryText = async (
|
|
|
48
52
|
ctx: EntryReadContext,
|
|
49
53
|
page: PageRecord
|
|
50
54
|
): Promise<string> => {
|
|
55
|
+
const variables = ctx.config?.variables;
|
|
51
56
|
const raw = await readEntryText(ctx, page);
|
|
52
57
|
if (!(page.sourcePath && hasIncludeStatements(raw))) {
|
|
53
|
-
return raw;
|
|
58
|
+
return substituteVariablesInBody(raw, variables);
|
|
54
59
|
}
|
|
55
60
|
const source = ctx.sources?.find(
|
|
56
61
|
(candidate) => candidate.name === page.source.name
|
|
@@ -61,11 +66,11 @@ export const readExpandedEntryText = async (
|
|
|
61
66
|
// here would splice text the rest of the pipeline never validated, with no
|
|
62
67
|
// content-root bound on resolution.
|
|
63
68
|
if (!source || source.staged || !source.contentRoot) {
|
|
64
|
-
return raw;
|
|
69
|
+
return substituteVariablesInBody(raw, variables);
|
|
65
70
|
}
|
|
66
71
|
const expansion = await expandIncludes(raw, {
|
|
67
72
|
contentRoot: source.contentRoot,
|
|
68
73
|
sourcePath: page.sourcePath,
|
|
69
74
|
});
|
|
70
|
-
return expansion.text;
|
|
75
|
+
return substituteVariablesInBody(expansion.text, variables);
|
|
71
76
|
};
|
package/src/core/types.ts
CHANGED
|
@@ -316,7 +316,7 @@ export interface Navigation {
|
|
|
316
316
|
cta?: HeaderAction | null;
|
|
317
317
|
/** Pinned links shown above the sidebar sections, unscoped by tab. */
|
|
318
318
|
featured: FeaturedLink[];
|
|
319
|
-
/** Repo URL for the
|
|
319
|
+
/** Repo URL for the footer's GitHub link, or null when hidden (`navigation.repo`). */
|
|
320
320
|
repoUrl?: string | null;
|
|
321
321
|
/**
|
|
322
322
|
* Where the header's brand link points in this locale: `logo.href` moved
|