blume 2.0.3 → 2.1.1
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 +142 -0
- package/README.md +1 -1
- package/dist/cli/{chunk-5f86nr5m.js → chunk-073rdvgb.js} +21 -48
- package/dist/cli/{chunk-5f86nr5m.js.map → chunk-073rdvgb.js.map} +4 -5
- package/dist/cli/{chunk-ga0pf4aj.js → chunk-0n9xkp3d.js} +12 -3
- package/dist/cli/{chunk-ga0pf4aj.js.map → chunk-0n9xkp3d.js.map} +3 -3
- package/dist/cli/chunk-150jk7km.js +52 -0
- package/dist/cli/chunk-150jk7km.js.map +10 -0
- package/dist/cli/{chunk-9t7a85s3.js → chunk-313vnn40.js} +30 -19
- package/dist/cli/chunk-313vnn40.js.map +10 -0
- package/dist/cli/{chunk-tkacnehg.js → chunk-3v3ffbrj.js} +2 -2
- package/dist/cli/{chunk-q58y5e6a.js → chunk-3x6x3pph.js} +46 -24
- package/dist/cli/chunk-3x6x3pph.js.map +10 -0
- package/dist/cli/{chunk-1d7ve1dm.js → chunk-42t4jxdx.js} +37 -5
- package/dist/cli/chunk-42t4jxdx.js.map +12 -0
- package/dist/cli/{chunk-8ktnccpt.js → chunk-54ztw1nj.js} +21 -2
- package/dist/cli/chunk-54ztw1nj.js.map +10 -0
- package/dist/cli/{chunk-hm3vjy5s.js → chunk-5vjtgs6e.js} +28 -10
- package/dist/cli/{chunk-hm3vjy5s.js.map → chunk-5vjtgs6e.js.map} +4 -4
- package/dist/cli/chunk-66w9j7p6.js +322 -0
- package/dist/cli/chunk-66w9j7p6.js.map +14 -0
- package/dist/cli/{chunk-2hsdwb9n.js → chunk-7ppf1rva.js} +34 -18
- package/dist/cli/chunk-7ppf1rva.js.map +10 -0
- package/dist/cli/{chunk-r9rcc4w7.js → chunk-8y2kmjv8.js} +7 -7
- package/dist/cli/{chunk-r20tn01b.js → chunk-c1y538kd.js} +8 -2
- package/dist/cli/{chunk-r20tn01b.js.map → chunk-c1y538kd.js.map} +3 -3
- package/dist/cli/{chunk-2eytanqx.js → chunk-c887fj0s.js} +2 -2
- package/dist/cli/{chunk-p3v96n38.js → chunk-cfyg47zg.js} +16 -6
- package/dist/cli/chunk-cfyg47zg.js.map +10 -0
- package/dist/cli/chunk-cm89scdv.js +443 -0
- package/dist/cli/chunk-cm89scdv.js.map +11 -0
- package/dist/cli/{chunk-akbpwfxc.js → chunk-dzwehpy7.js} +2 -2
- package/dist/cli/{chunk-wrr3j9w9.js → chunk-e6atmn4h.js} +299 -72
- package/dist/cli/chunk-e6atmn4h.js.map +36 -0
- package/dist/cli/{chunk-tzmab476.js → chunk-e8xw9qkh.js} +8 -6
- package/dist/cli/{chunk-tzmab476.js.map → chunk-e8xw9qkh.js.map} +3 -3
- package/dist/cli/{chunk-6dsbexzp.js → chunk-e9q7a2ak.js} +17 -15
- package/dist/cli/{chunk-6dsbexzp.js.map → chunk-e9q7a2ak.js.map} +2 -2
- package/dist/cli/{chunk-pv29h0wf.js → chunk-ehweg4fr.js} +5155 -1249
- package/dist/cli/chunk-ehweg4fr.js.map +218 -0
- package/dist/cli/{chunk-c8chx29p.js → chunk-ep6nd0rn.js} +390 -318
- package/dist/cli/chunk-ep6nd0rn.js.map +36 -0
- package/dist/cli/{chunk-p73c0m7w.js → chunk-fdrkhpjy.js} +18 -15
- package/dist/cli/{chunk-p73c0m7w.js.map → chunk-fdrkhpjy.js.map} +3 -3
- package/dist/cli/{chunk-2z928egk.js → chunk-ft9vfys6.js} +5 -5
- package/dist/cli/{chunk-5m5nmvyq.js → chunk-gb420vvz.js} +316 -79
- package/dist/cli/chunk-gb420vvz.js.map +17 -0
- package/dist/cli/{chunk-vg9r4eb9.js → chunk-hebn0tpx.js} +86 -13
- package/dist/cli/chunk-hebn0tpx.js.map +14 -0
- package/dist/cli/{chunk-35d4wj9f.js → chunk-htrwgabr.js} +41 -23
- package/dist/cli/chunk-htrwgabr.js.map +15 -0
- package/dist/cli/{chunk-g4hq16wv.js → chunk-hx3xqnk9.js} +16 -14
- package/dist/cli/{chunk-g4hq16wv.js.map → chunk-hx3xqnk9.js.map} +2 -2
- package/dist/cli/chunk-j134vrp2.js +65 -0
- package/dist/cli/chunk-j134vrp2.js.map +10 -0
- package/dist/cli/{chunk-a58773jm.js → chunk-kma9f6sf.js} +2 -2
- package/dist/cli/chunk-kwrg63tm.js +31 -0
- package/dist/cli/chunk-kwrg63tm.js.map +10 -0
- package/dist/cli/{chunk-ey84smr6.js → chunk-mrcccgnh.js} +6 -6
- package/dist/cli/{chunk-ey84smr6.js.map → chunk-mrcccgnh.js.map} +1 -1
- package/dist/cli/{chunk-yfyb25rh.js → chunk-n0v7m8ry.js} +24 -8
- package/dist/cli/chunk-n0v7m8ry.js.map +14 -0
- package/dist/cli/{chunk-j85vccga.js → chunk-ncj7w596.js} +576 -367
- package/dist/cli/chunk-ncj7w596.js.map +35 -0
- package/dist/cli/{chunk-5xvm6tfj.js → chunk-nq1d20w2.js} +17 -15
- package/dist/cli/{chunk-5xvm6tfj.js.map → chunk-nq1d20w2.js.map} +2 -2
- package/dist/cli/{chunk-pehfxfta.js → chunk-nrns10mj.js} +6 -98
- package/dist/cli/chunk-nrns10mj.js.map +10 -0
- package/dist/cli/{chunk-crgn1q09.js → chunk-sds923fa.js} +4 -4
- package/dist/cli/chunk-t2m5j4pr.js +104 -0
- package/dist/cli/chunk-t2m5j4pr.js.map +11 -0
- package/dist/cli/{chunk-acanzt5p.js → chunk-tdqhyxfz.js} +6 -6
- package/dist/cli/chunk-tphac74f.js +126 -0
- package/dist/cli/chunk-tphac74f.js.map +11 -0
- package/dist/cli/chunk-w1r4vase.js +18 -0
- package/dist/cli/chunk-w1r4vase.js.map +10 -0
- 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/index.js +31 -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 +26 -0
- package/dist/types/ai/ask.d.ts +271 -58
- package/dist/types/ai/component-markdown.d.ts +2 -0
- package/dist/types/ai/index.d.ts +2 -2
- package/dist/types/ai/openapi-components.d.ts +4 -1
- 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 +9 -0
- package/dist/types/astro/markdown-negotiation.d.ts +3 -0
- package/dist/types/astro/pages.d.ts +10 -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/cli/init/scaffold.d.ts +1 -1
- package/dist/types/components/content/api-field.d.ts +62 -0
- package/dist/types/components/content/api-page.d.ts +100 -0
- package/dist/types/components/layout/nav-utils.d.ts +26 -0
- package/dist/types/components/openapi/code-samples.d.ts +29 -0
- package/dist/types/components/openapi/helpers.d.ts +158 -0
- package/dist/types/components/openapi/security.d.ts +95 -0
- package/dist/types/components/openapi/snippets.d.ts +60 -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/changelog-index.d.ts +8 -0
- package/dist/types/core/config-input.d.ts +217 -23
- package/dist/types/core/data.d.ts +53 -4
- package/dist/types/core/footer.d.ts +8 -0
- package/dist/types/core/i18n-ui.d.ts +88 -0
- package/dist/types/core/includes.d.ts +8 -2
- package/dist/types/core/links.d.ts +8 -0
- package/dist/types/core/localizable.d.ts +7 -0
- package/dist/types/core/page-modes.d.ts +29 -0
- package/dist/types/core/probe.d.ts +2 -0
- package/dist/types/core/project-graph.d.ts +6 -0
- package/dist/types/core/redirect-patterns.d.ts +152 -0
- package/dist/types/core/related.d.ts +40 -0
- package/dist/types/core/schema.d.ts +670 -56
- package/dist/types/core/sources/cache.d.ts +3 -0
- package/dist/types/core/sources/contentful-rich-text.d.ts +11 -2
- package/dist/types/core/sources/contentful.d.ts +17 -1
- package/dist/types/core/sources/lexical.d.ts +5 -0
- package/dist/types/core/sources/normalize.d.ts +20 -1
- package/dist/types/core/sources/read.d.ts +5 -0
- package/dist/types/core/sources/remote.d.ts +37 -10
- package/dist/types/core/sources/sanity.d.ts +1 -1
- package/dist/types/core/types.d.ts +18 -1
- package/dist/types/core/variables.d.ts +42 -0
- package/dist/types/deploy/adapter-output.d.ts +2 -1
- package/dist/types/deploy/adapters/cloudflare.d.ts +7 -5
- package/dist/types/deploy/adapters/node.d.ts +58 -23
- package/dist/types/deploy/adapters/registry.d.ts +10 -0
- package/dist/types/deploy/adapters/types.d.ts +3 -2
- package/dist/types/deploy/node-headers.d.ts +13 -2
- package/dist/types/deploy/platforms/cloudflare.d.ts +15 -7
- package/dist/types/deploy/platforms/netlify.d.ts +7 -0
- package/dist/types/deploy/platforms/node.d.ts +3 -1
- package/dist/types/deploy/platforms/types.d.ts +18 -7
- package/dist/types/deploy/platforms/vercel.d.ts +26 -1
- package/dist/types/deploy/redirects.d.ts +55 -11
- package/dist/types/deploy/sitemap.d.ts +2 -1
- package/dist/types/deploy/vercel-negotiation.d.ts +51 -21
- 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/checks.d.ts +27 -0
- package/dist/types/openapi/graphql.d.ts +6 -0
- package/dist/types/openapi/model.d.ts +27 -10
- package/dist/types/openapi/overlay.d.ts +54 -0
- package/dist/types/openapi/parse.d.ts +16 -0
- package/dist/types/openapi/references.d.ts +30 -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 +119 -0
- package/dist/types/ratelimit/upstash.d.ts +46 -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 +89 -11
- package/dist/types/reference/index.d.ts +1 -1
- package/dist/types/reference/openapi.d.ts +33 -12
- package/dist/types/reference/options.d.ts +82 -6
- package/dist/types/reference/scalar.d.ts +13 -0
- package/dist/types/reference/schema.d.ts +144 -36
- package/dist/types/search/adapters/registry.d.ts +3 -0
- package/dist/types/search/adapters/typesense.d.ts +14 -4
- package/dist/types/search/build.d.ts +3 -1
- package/dist/types/search/documents.d.ts +12 -1
- package/dist/types/search/orama-index.d.ts +13 -9
- package/dist/types/search/sync/algolia.d.ts +5 -2
- package/dist/types/search/sync/index.d.ts +3 -1
- package/dist/types/search/sync/typesense.d.ts +11 -7
- package/dist/types/seo/metatags.d.ts +20 -0
- package/dist/types/sources/contentful.d.ts +16 -0
- package/dist/types/sources/github-releases.d.ts +8 -1
- package/dist/types/sources/payload.d.ts +2 -0
- package/dist/types/sources/registry.d.ts +54 -0
- package/dist/types/sources/shared.d.ts +3 -0
- package/dist/types/sources/strapi.d.ts +2 -0
- package/docs/01-quickstart.mdx +4 -2
- package/docs/02-deployment.mdx +71 -17
- package/docs/03-upgrading.mdx +4 -4
- package/docs/04-migrating.mdx +1 -1
- package/docs/advanced/changelog.mdx +13 -2
- package/docs/advanced/skills.mdx +15 -1
- package/docs/cli/audit.mdx +51 -8
- package/docs/cli/doctor.mdx +5 -1
- package/docs/cli/evals.mdx +4 -4
- package/docs/cli/index.mdx +1 -0
- package/docs/cli/translate.mdx +1 -0
- package/docs/cli/validate.mdx +4 -3
- package/docs/configuration/analytics.mdx +3 -1
- package/docs/configuration/assistant.mdx +194 -38
- package/docs/configuration/consent.mdx +100 -0
- package/docs/configuration/customization.mdx +8 -6
- package/docs/configuration/export.mdx +5 -3
- package/docs/configuration/index.mdx +64 -5
- package/docs/configuration/meta.ts +3 -0
- package/docs/configuration/narration.mdx +119 -0
- package/docs/configuration/rate-limiting.mdx +90 -0
- package/docs/configuration/search.mdx +63 -12
- package/docs/configuration/theming.mdx +3 -1
- package/docs/content/components.mdx +108 -2
- package/docs/content/frontmatter.mdx +80 -2
- package/docs/content/i18n.mdx +39 -4
- 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 +32 -10
- package/docs/content/sources/contentful.mdx +35 -0
- package/docs/content/sources/custom.mdx +43 -0
- package/docs/content/sources/github-releases.mdx +39 -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 +31 -0
- package/docs/content/sources/remote-mdx.mdx +44 -0
- package/docs/content/sources/sanity.mdx +29 -0
- package/docs/content/sources/strapi.mdx +32 -0
- package/docs/content/syntax.mdx +69 -4
- package/docs/content/variables.mdx +41 -0
- package/docs/discoverability/agent-discovery.mdx +29 -2
- package/docs/discoverability/index.mdx +2 -2
- package/docs/discoverability/json-api.mdx +2 -0
- package/docs/discoverability/llms-txt.mdx +2 -2
- package/docs/discoverability/markdown.mdx +3 -1
- package/docs/discoverability/mcp.mdx +4 -0
- package/docs/discoverability/metadata.mdx +17 -0
- package/docs/discoverability/sitemap-and-robots.mdx +1 -1
- package/docs/references/api-pages.mdx +74 -0
- package/docs/references/asyncapi.mdx +4 -2
- package/docs/references/graphql.mdx +14 -2
- package/docs/references/meta.ts +1 -1
- package/docs/references/openapi.mdx +148 -7
- package/docs/references/scalar.mdx +3 -1
- package/package.json +43 -8
- package/skills/blume/SKILL.md +3 -0
- package/skills/blume-migrate/SKILL.md +6 -6
- package/skills/blume-migrate/references/docusaurus.md +7 -7
- package/skills/blume-migrate/references/fumadocs.md +17 -13
- package/skills/blume-migrate/references/mintlify.md +37 -19
- package/skills/blume-migrate/references/nextra.md +10 -7
- package/skills/blume-migrate/references/starlight.md +11 -8
- 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 +103 -20
- package/src/ai/ask-data.ts +6 -0
- package/src/ai/ask-limits.ts +13 -0
- package/src/ai/ask-tools.ts +142 -0
- package/src/ai/ask.ts +354 -76
- package/src/ai/changelog-markdown.ts +11 -6
- package/src/ai/component-markdown.ts +76 -1
- package/src/ai/cors.ts +14 -3
- 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/mcp/query.ts +14 -6
- package/src/ai/openapi-components.ts +36 -6
- package/src/ai/relative-links.ts +4 -38
- 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 +210 -66
- package/src/astro/index.ts +1 -0
- package/src/astro/integration.ts +42 -4
- package/src/astro/markdown-negotiation.ts +3 -0
- package/src/astro/module-types.ts +9 -1
- package/src/astro/pages.ts +23 -0
- package/src/astro/templates.ts +406 -108
- package/src/astro/variables.ts +31 -0
- package/src/audit/catalog.ts +38 -3
- 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 +19 -13
- package/src/audit/checks/network.ts +110 -6
- package/src/audit/checks/redirects.ts +6 -4
- package/src/audit/checks/robots.ts +58 -2
- package/src/audit/checks/sitemap.ts +2 -3
- package/src/audit/redirects.ts +46 -15
- package/src/audit/run.ts +5 -0
- package/src/audit/types.ts +4 -1
- package/src/audit/url.ts +14 -5
- package/src/blume-modules.d.ts +12 -1
- 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 +51 -0
- package/src/cli/build-failure.ts +23 -0
- package/src/cli/command-meta.ts +5 -0
- package/src/cli/commands/audit.ts +10 -1
- package/src/cli/commands/dev.ts +18 -2
- package/src/cli/commands/doctor.ts +67 -28
- package/src/cli/commands/eval.ts +36 -4
- package/src/cli/commands/init.ts +6 -4
- package/src/cli/commands/skill.ts +96 -0
- package/src/cli/commands/validate.ts +10 -1
- package/src/cli/index.ts +5 -0
- package/src/cli/init/scaffold.ts +3 -3
- package/src/cli/prepare.ts +6 -3
- package/src/cli/required-secrets.ts +72 -30
- 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 +66 -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 +103 -0
- package/src/components/content/api-page.ts +248 -0
- package/src/components/content/mermaid-element.ts +15 -1
- 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 +136 -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/PageActions.astro +139 -24
- 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 +318 -47
- package/src/components/layout/Search.astro +47 -19
- package/src/components/layout/SiteFooter.astro +124 -0
- package/src/components/layout/SiteMetatags.astro +16 -0
- package/src/components/layout/TableOfContents.astro +24 -17
- 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/head-scripts.ts +6 -1
- 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/algolia.ts +10 -1
- package/src/components/layout/search/analytics.ts +126 -0
- package/src/components/layout/search/endpoint.ts +48 -5
- package/src/components/layout/search/flexsearch.ts +17 -5
- package/src/components/layout/search/orama-cloud.ts +26 -14
- package/src/components/layout/search/pagefind.ts +114 -5
- package/src/components/layout/search/types.ts +32 -0
- package/src/components/layout/search/typesense.ts +19 -3
- package/src/components/openapi/AsyncApiOperation.astro +14 -7
- package/src/components/openapi/Authorization.astro +10 -0
- package/src/components/openapi/GraphqlOperation.astro +5 -0
- package/src/components/openapi/Operation.astro +184 -13
- package/src/components/openapi/ParametersTable.astro +8 -4
- package/src/components/openapi/Playground.astro +7 -1
- package/src/components/openapi/RequestBody.astro +24 -5
- package/src/components/openapi/RequestPanel.astro +59 -7
- package/src/components/openapi/Responses.astro +7 -4
- package/src/components/openapi/SchemaTable.astro +15 -3
- package/src/components/openapi/async-snippets.ts +65 -9
- package/src/components/openapi/async.ts +123 -29
- package/src/components/openapi/code-samples.ts +97 -0
- package/src/components/openapi/graphql-helpers.ts +35 -12
- package/src/components/openapi/helpers.ts +36 -2
- package/src/components/openapi/message-model.ts +54 -11
- package/src/components/openapi/message.ts +14 -4
- package/src/components/openapi/operation-model.ts +16 -7
- package/src/components/openapi/playground-client.ts +24 -5
- package/src/components/openapi/playground-schema.ts +1 -1
- package/src/components/openapi/schema-tree.ts +44 -14
- package/src/components/openapi/security.ts +16 -3
- package/src/components/openapi/snippets.ts +873 -10
- package/src/components/openapi/validate-json.ts +4 -3
- 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/changelog-index.ts +31 -1
- package/src/core/client-address.ts +19 -0
- package/src/core/component-diagnostics.ts +53 -0
- package/src/core/component-overrides.ts +32 -0
- package/src/core/config-input.ts +225 -23
- package/src/core/data.ts +46 -4
- package/src/core/diagnostics.ts +8 -2
- package/src/core/footer.ts +26 -0
- package/src/core/graph.ts +39 -36
- package/src/core/i18n-ui.ts +84 -2
- package/src/core/includes.ts +21 -4
- package/src/core/links.ts +63 -19
- 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/probe.ts +26 -6
- package/src/core/project-graph.ts +163 -16
- package/src/core/redirect-patterns.ts +520 -0
- package/src/core/related.ts +131 -0
- package/src/core/schema.ts +339 -66
- package/src/core/sources/cache.ts +6 -0
- package/src/core/sources/contentful-rich-text.ts +29 -6
- package/src/core/sources/contentful.ts +135 -25
- package/src/core/sources/filesystem.ts +26 -4
- package/src/core/sources/github-releases.ts +12 -1
- package/src/core/sources/lexical.ts +15 -10
- package/src/core/sources/mdx-remote.ts +62 -10
- package/src/core/sources/normalize.ts +228 -41
- package/src/core/sources/notion.ts +7 -1
- package/src/core/sources/obsidian.ts +16 -3
- package/src/core/sources/payload.ts +38 -15
- package/src/core/sources/read.ts +8 -3
- package/src/core/sources/remote.ts +72 -15
- package/src/core/sources/resolve.ts +11 -1
- package/src/core/sources/sanity.ts +43 -25
- package/src/core/sources/strapi.ts +2 -2
- package/src/core/types.ts +19 -1
- package/src/core/ui-packs/ar.ts +44 -0
- package/src/core/ui-packs/bg.ts +46 -0
- package/src/core/ui-packs/bn.ts +44 -0
- package/src/core/ui-packs/ca.ts +47 -0
- package/src/core/ui-packs/cs.ts +45 -0
- package/src/core/ui-packs/da.ts +46 -0
- package/src/core/ui-packs/de.ts +47 -0
- package/src/core/ui-packs/el.ts +46 -0
- package/src/core/ui-packs/es.ts +47 -0
- package/src/core/ui-packs/fa.ts +46 -0
- package/src/core/ui-packs/fi.ts +46 -0
- package/src/core/ui-packs/fr.ts +47 -0
- package/src/core/ui-packs/he.ts +44 -0
- package/src/core/ui-packs/hi.ts +45 -0
- package/src/core/ui-packs/hr.ts +46 -0
- package/src/core/ui-packs/hu.ts +45 -0
- package/src/core/ui-packs/id.ts +46 -0
- package/src/core/ui-packs/it.ts +46 -0
- package/src/core/ui-packs/ja.ts +46 -0
- package/src/core/ui-packs/ko.ts +45 -0
- package/src/core/ui-packs/nl.ts +47 -0
- package/src/core/ui-packs/no.ts +45 -0
- package/src/core/ui-packs/pl.ts +46 -0
- package/src/core/ui-packs/pt-br.ts +47 -0
- package/src/core/ui-packs/pt.ts +46 -0
- package/src/core/ui-packs/ro.ts +46 -0
- package/src/core/ui-packs/ru.ts +46 -0
- package/src/core/ui-packs/sk.ts +45 -0
- package/src/core/ui-packs/sr.ts +46 -0
- package/src/core/ui-packs/sv.ts +45 -0
- package/src/core/ui-packs/th.ts +44 -0
- package/src/core/ui-packs/tr.ts +46 -0
- package/src/core/ui-packs/uk.ts +46 -0
- package/src/core/ui-packs/vi.ts +46 -0
- package/src/core/ui-packs/zh-tw.ts +44 -0
- package/src/core/ui-packs/zh.ts +44 -0
- package/src/core/variables.ts +103 -0
- package/src/deploy/adapter-output.ts +7 -2
- package/src/deploy/adapters/cloudflare.ts +7 -5
- package/src/deploy/adapters/node.ts +47 -9
- package/src/deploy/adapters/types.ts +7 -3
- package/src/deploy/artifacts.ts +79 -9
- 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 +45 -9
- package/src/deploy/platforms/netlify.ts +35 -2
- package/src/deploy/platforms/node.ts +12 -2
- package/src/deploy/platforms/types.ts +21 -7
- package/src/deploy/platforms/vercel.ts +149 -17
- package/src/deploy/redirects.ts +151 -24
- package/src/deploy/rss.ts +3 -1
- package/src/deploy/sitemap.ts +39 -5
- package/src/deploy/vercel-negotiation.ts +326 -102
- package/src/eval/agents.ts +18 -1
- package/src/eval/prompts.ts +6 -3
- package/src/eval/report.ts +34 -13
- package/src/eval/run.ts +5 -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/relative-links.ts +3 -2
- 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/checks.ts +273 -0
- package/src/openapi/graphql-build.ts +21 -0
- package/src/openapi/graphql.ts +11 -0
- package/src/openapi/model.ts +72 -27
- package/src/openapi/overlay.ts +256 -0
- package/src/openapi/parse.ts +182 -4
- package/src/openapi/references.ts +64 -8
- package/src/openapi/render-mdx.ts +5 -1
- package/src/openapi/scalar.ts +40 -6
- package/src/openapi/source.ts +40 -15
- 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 +247 -0
- package/src/ratelimit/schema.ts +28 -0
- package/src/ratelimit/upstash.ts +60 -0
- package/src/ratelimit/wrangler.ts +70 -0
- package/src/reference/asyncapi.ts +2 -2
- package/src/reference/graphql.ts +20 -3
- package/src/reference/index.ts +1 -0
- package/src/reference/openapi.ts +21 -7
- package/src/reference/options.ts +80 -8
- package/src/reference/scalar.ts +33 -8
- package/src/registry/eject.ts +23 -5
- package/src/registry/registry.ts +7 -0
- package/src/search/adapters/locale-scope.ts +30 -0
- package/src/search/adapters/typesense.ts +14 -4
- package/src/search/build.ts +21 -3
- package/src/search/documents.ts +26 -2
- package/src/search/orama-index.ts +191 -34
- package/src/search/pagefind-ranking.ts +39 -0
- package/src/search/source-pages.ts +29 -0
- package/src/search/sync/algolia.ts +111 -10
- package/src/search/sync/index.ts +52 -13
- package/src/search/sync/orama-cloud.ts +3 -0
- package/src/search/sync/typesense.ts +76 -26
- package/src/seo/metatags.ts +67 -0
- package/src/sources/contentful.ts +12 -0
- package/src/sources/github-releases.ts +7 -1
- package/src/sources/shared.ts +3 -0
- package/src/theme/chrome-icons.ts +1 -0
- package/src/theme/color.ts +500 -0
- package/src/theme/entry.ts +85 -6
- package/src/theme/palette.ts +121 -45
- package/src/translate/prompts.ts +1 -0
- package/src/translate/validate.ts +31 -3
- package/src/upgrade/upgrade.ts +4 -171
- package/dist/cli/chunk-1d7ve1dm.js.map +0 -12
- package/dist/cli/chunk-2hsdwb9n.js.map +0 -10
- package/dist/cli/chunk-35d4wj9f.js.map +0 -15
- 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-8ktnccpt.js.map +0 -10
- package/dist/cli/chunk-9t7a85s3.js.map +0 -10
- package/dist/cli/chunk-b07cmahc.js +0 -145
- package/dist/cli/chunk-b07cmahc.js.map +0 -11
- package/dist/cli/chunk-c8chx29p.js.map +0 -36
- package/dist/cli/chunk-e04dxsz1.js +0 -39
- package/dist/cli/chunk-e04dxsz1.js.map +0 -10
- package/dist/cli/chunk-j85vccga.js.map +0 -36
- package/dist/cli/chunk-p3v96n38.js.map +0 -10
- 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-vg9r4eb9.js.map +0 -14
- 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-tkacnehg.js.map → chunk-3v3ffbrj.js.map} +0 -0
- /package/dist/cli/{chunk-r9rcc4w7.js.map → chunk-8y2kmjv8.js.map} +0 -0
- /package/dist/cli/{chunk-2eytanqx.js.map → chunk-c887fj0s.js.map} +0 -0
- /package/dist/cli/{chunk-akbpwfxc.js.map → chunk-dzwehpy7.js.map} +0 -0
- /package/dist/cli/{chunk-2z928egk.js.map → chunk-ft9vfys6.js.map} +0 -0
- /package/dist/cli/{chunk-a58773jm.js.map → chunk-kma9f6sf.js.map} +0 -0
- /package/dist/cli/{chunk-crgn1q09.js.map → chunk-sds923fa.js.map} +0 -0
- /package/dist/cli/{chunk-acanzt5p.js.map → chunk-tdqhyxfz.js.map} +0 -0
package/src/astro/templates.ts
CHANGED
|
@@ -4,27 +4,44 @@ import { pathToFileURL } from "node:url";
|
|
|
4
4
|
import { dirname, isAbsolute, join, relative } from "pathe";
|
|
5
5
|
|
|
6
6
|
import type { AskRetrievalOptions } from "../ai/ask-context.ts";
|
|
7
|
+
import { ASK_MAX_MESSAGES, ASK_MAX_MESSAGES_CHARS } from "../ai/ask-limits.ts";
|
|
7
8
|
import type { AskBackend } from "../ai/ask.ts";
|
|
8
9
|
import { buildHomeLinkHeader } from "../ai/link-headers.ts";
|
|
10
|
+
import type { CaptchaAdapter } from "../captcha/schema.ts";
|
|
11
|
+
import { CONSENT_CLIENT_MODULES } from "../consent/clients.ts";
|
|
9
12
|
import { normalizeBasePath } from "../core/base-path.ts";
|
|
10
13
|
import { TOC_HIDDEN_KEY } from "../core/heading-markers.ts";
|
|
14
|
+
import {
|
|
15
|
+
compileEveryRedirect,
|
|
16
|
+
isPatternPath,
|
|
17
|
+
} from "../core/redirect-patterns.ts";
|
|
18
|
+
import type { CompiledRedirect } from "../core/redirect-patterns.ts";
|
|
11
19
|
import type { ResolvedConfig } from "../core/schema.ts";
|
|
12
20
|
import { resolveDocsCollection } from "../core/sources/collection.ts";
|
|
13
21
|
import { BLUME_IGNORE_DIRS } from "../core/sources/watch.ts";
|
|
14
22
|
import { trimChar } from "../core/trim.ts";
|
|
15
23
|
import type { ProjectContext } from "../core/types.ts";
|
|
24
|
+
import { hasVariables } from "../core/variables.ts";
|
|
16
25
|
import { deployPassthrough } from "../deploy/adapters/types.ts";
|
|
17
26
|
import { SVG_ASSET_HEADERS } from "../deploy/headers.ts";
|
|
18
27
|
import { deployPlatform } from "../deploy/platforms/index.ts";
|
|
19
28
|
import { adapterRoot, distDir } from "../deploy/platforms/paths.ts";
|
|
20
|
-
import {
|
|
29
|
+
import {
|
|
30
|
+
applyBaseToAstroRedirects,
|
|
31
|
+
withMirrorRedirects,
|
|
32
|
+
} from "../deploy/redirects.ts";
|
|
33
|
+
import { API_RAIL_KEY } from "../markdown/api-rail.ts";
|
|
34
|
+
import { VIEWS_KEY } from "../markdown/views.ts";
|
|
21
35
|
import type { OgCache } from "../og/cache.ts";
|
|
22
36
|
import type { OgFont, OgFontFamilies, OgGoogleFont } from "../og/card.ts";
|
|
37
|
+
import { RATE_LIMIT_BINDING } from "../ratelimit/cloudflare.ts";
|
|
38
|
+
import type { RateLimitAdapter } from "../ratelimit/schema.ts";
|
|
23
39
|
import type { MixedbreadOptions } from "../search/adapters/mixedbread.ts";
|
|
24
40
|
import type {
|
|
25
41
|
ResolvedSearchAdapter,
|
|
26
42
|
SearchAdapterKind,
|
|
27
43
|
} from "../search/adapters/registry.ts";
|
|
44
|
+
import type { SourcePage } from "../search/source-pages.ts";
|
|
28
45
|
import { buildFontEntries, fontLocaleCodes } from "../theme/fonts.ts";
|
|
29
46
|
import { importSpecifier, wrapperPropsType } from "./component-slots.ts";
|
|
30
47
|
import type { ExampleSpec } from "./examples.ts";
|
|
@@ -113,7 +130,7 @@ const renderAstroAdapter = (
|
|
|
113
130
|
const { astro } = platform;
|
|
114
131
|
const args = {
|
|
115
132
|
...astro.options(context),
|
|
116
|
-
...deployPassthrough(deployment.options),
|
|
133
|
+
...deployPassthrough(deployment.options, astro.configOptions),
|
|
117
134
|
};
|
|
118
135
|
const argsLiteral = Object.keys(args).length > 0 ? JSON.stringify(args) : "";
|
|
119
136
|
const construct = `adapter(${argsLiteral})`;
|
|
@@ -124,7 +141,7 @@ const renderAstroAdapter = (
|
|
|
124
141
|
? `withAdapterRoot(${construct}, ${JSON.stringify(adapterRoot(context))})`
|
|
125
142
|
: construct;
|
|
126
143
|
return {
|
|
127
|
-
configEntries: Object.entries(astro.config)
|
|
144
|
+
configEntries: Object.entries(astro.config(deployment.options))
|
|
128
145
|
.map(([key, value]) => `\n ${key}: ${JSON.stringify(value)},`)
|
|
129
146
|
.join(""),
|
|
130
147
|
importLine: `import adapter from "${astro.package}";\n`,
|
|
@@ -188,7 +205,8 @@ export const runtimeDependencies = (options: {
|
|
|
188
205
|
deps.push(
|
|
189
206
|
...new Set(config.reference.flatMap((adapter) => adapter.runtimeDeps)),
|
|
190
207
|
...config.search.provider.runtimeDeps,
|
|
191
|
-
...config.analytics.flatMap((adapter) => adapter.runtimeDeps)
|
|
208
|
+
...config.analytics.flatMap((adapter) => adapter.runtimeDeps),
|
|
209
|
+
...(config.consent?.runtimeDeps ?? [])
|
|
192
210
|
);
|
|
193
211
|
// Each content source adapter declares the SDK its fetch imports (Notion,
|
|
194
212
|
// Sanity); the descriptor is the one place that knows.
|
|
@@ -273,18 +291,14 @@ const renderUserAliases = (
|
|
|
273
291
|
.join("");
|
|
274
292
|
|
|
275
293
|
/**
|
|
276
|
-
* Excludes
|
|
277
|
-
* react()
|
|
278
|
-
*
|
|
279
|
-
*
|
|
280
|
-
* re-
|
|
281
|
-
*
|
|
282
|
-
* resolve under `node_modules/blume/src`, and exclude beats include in the
|
|
283
|
-
* plugin's filter), so only the pre-bundle cache is excluded. The hidden runtime
|
|
284
|
-
* relocates that cache to `<runtime>/.cache/vite` (see `cacheOptions`), so both
|
|
285
|
-
* the default and the relocated path are excluded.
|
|
294
|
+
* Excludes the hidden runtime's pre-bundled dep cache from @vitejs/plugin-react.
|
|
295
|
+
* Astro's react() already excludes `/node_modules/`, which covers Vite's
|
|
296
|
+
* default `node_modules/.vite` cache, but the hidden runtime relocates that
|
|
297
|
+
* cache to `<runtime>/.cache/vite` (see `cacheOptions`). Without this the React
|
|
298
|
+
* Compiler re-transforms every optimized dep chunk served from there — a 500KB+
|
|
299
|
+
* vendor bundle per chunk, re-done on each re-optimization.
|
|
286
300
|
*/
|
|
287
|
-
const REACT_EXCLUDE = String.raw`exclude: [
|
|
301
|
+
const REACT_EXCLUDE = String.raw`exclude: [/\/\.cache\/vite\//]`;
|
|
288
302
|
|
|
289
303
|
/**
|
|
290
304
|
* The `cacheDir` entries for the generated config's top level and its `vite`
|
|
@@ -314,17 +328,14 @@ const runtimeCacheOptions = (
|
|
|
314
328
|
};
|
|
315
329
|
|
|
316
330
|
/**
|
|
317
|
-
* The `react()` integration call.
|
|
318
|
-
*
|
|
319
|
-
*
|
|
320
|
-
*
|
|
321
|
-
* specifier wouldn't resolve in a user project. `target: "19"` matches Blume's
|
|
322
|
-
* React pin. `null`/`undefined` (compiler off or unresolvable) omits the babel
|
|
323
|
-
* block. Both variants carry the pre-bundle exclude above.
|
|
331
|
+
* The `react()` integration call. `compiler` turns on @astrojs/react's React
|
|
332
|
+
* Compiler, which runs on `oxc-transform-react` and targets the installed
|
|
333
|
+
* React major; false/absent (compiler off or unresolvable) leaves it out. Both
|
|
334
|
+
* variants carry the pre-bundle exclude above.
|
|
324
335
|
*/
|
|
325
|
-
const reactIntegration = (
|
|
326
|
-
|
|
327
|
-
? `react({
|
|
336
|
+
const reactIntegration = (compiler: boolean | undefined): string =>
|
|
337
|
+
compiler
|
|
338
|
+
? `react({ compiler: true, ${REACT_EXCLUDE} })`
|
|
328
339
|
: `react({ ${REACT_EXCLUDE} })`;
|
|
329
340
|
|
|
330
341
|
interface IntegrationBridgeOptions {
|
|
@@ -378,11 +389,10 @@ interface OptimizeDepsConfig {
|
|
|
378
389
|
* the Vite root is the generated runtime, so user pages, convention islands,
|
|
379
390
|
* and alias-reachable components all live outside it and are otherwise only
|
|
380
391
|
* crawled when first requested. The compiler runtime rides the include list
|
|
381
|
-
* because
|
|
382
|
-
*
|
|
383
|
-
*
|
|
384
|
-
*
|
|
385
|
-
* the optimizeDeps comment in the generated config for the failure this prevents.
|
|
392
|
+
* because the compiler injects its import and no source scan can see it. The
|
|
393
|
+
* compiler plugin in `@vitejs/plugin-react` lists it too; Blume keeps it
|
|
394
|
+
* explicit so the guard doesn't hinge on that plugin's internals. See the
|
|
395
|
+
* optimizeDeps comment in the generated config for the failure this prevents.
|
|
386
396
|
*/
|
|
387
397
|
/**
|
|
388
398
|
* The client-side libraries a site needs, decided at generation time. A
|
|
@@ -422,7 +432,7 @@ const resolveOptimizeDeps = (options: {
|
|
|
422
432
|
context: ProjectContext;
|
|
423
433
|
features: ClientFeatures;
|
|
424
434
|
needsReact: boolean;
|
|
425
|
-
|
|
435
|
+
reactCompiler: boolean | undefined;
|
|
426
436
|
searchKind: SearchAdapterKind;
|
|
427
437
|
}): OptimizeDepsConfig => {
|
|
428
438
|
const { context, features } = options;
|
|
@@ -446,7 +456,7 @@ const resolveOptimizeDeps = (options: {
|
|
|
446
456
|
// (__PREFETCH_PREFETCH_ALL__ and friends) that a pre-bundled copy loses,
|
|
447
457
|
// throwing ReferenceError on every page. Astro manages their optimization
|
|
448
458
|
// itself, without a mid-session reload.
|
|
449
|
-
...(options.needsReact && options.
|
|
459
|
+
...(options.needsReact && options.reactCompiler
|
|
450
460
|
? ["react/compiler-runtime"]
|
|
451
461
|
: []),
|
|
452
462
|
];
|
|
@@ -555,17 +565,23 @@ const blumeIntegrationOptions = (options: {
|
|
|
555
565
|
contentRoutes: string[];
|
|
556
566
|
ejected: boolean;
|
|
557
567
|
pages: BlumePageRoute[];
|
|
558
|
-
|
|
559
|
-
|
|
568
|
+
redirects: CompiledRedirect[];
|
|
569
|
+
}): BlumeIntegrationOptions => {
|
|
570
|
+
const shared: BlumeIntegrationOptions = { pages: options.pages };
|
|
571
|
+
if (options.redirects.length > 0) {
|
|
572
|
+
shared.redirects = options.redirects;
|
|
573
|
+
}
|
|
574
|
+
return options.ejected
|
|
560
575
|
? {
|
|
576
|
+
...shared,
|
|
561
577
|
buildArtifactsRoot: ".",
|
|
562
578
|
contentRoutes: options.contentRoutes,
|
|
563
579
|
homeLinkHeader:
|
|
564
580
|
buildHomeLinkHeader(options.config, options.contentRoutes, "dev") ??
|
|
565
581
|
undefined,
|
|
566
|
-
pages: options.pages,
|
|
567
582
|
}
|
|
568
|
-
:
|
|
583
|
+
: shared;
|
|
584
|
+
};
|
|
569
585
|
|
|
570
586
|
export const astroConfigTemplate = (options: {
|
|
571
587
|
context: ProjectContext;
|
|
@@ -581,6 +597,8 @@ export const astroConfigTemplate = (options: {
|
|
|
581
597
|
/** The example-preview Tailwind entry (`blume:examples-theme`). */
|
|
582
598
|
examplesThemePath: string;
|
|
583
599
|
themePath: string;
|
|
600
|
+
/** The configured consent adapter's browser module (`blume:consent-client`). */
|
|
601
|
+
consentClientPath: string;
|
|
584
602
|
searchClientPath: string;
|
|
585
603
|
/** The generated client-feature loaders (`blume:features`). */
|
|
586
604
|
featuresPath: string;
|
|
@@ -594,11 +612,10 @@ export const astroConfigTemplate = (options: {
|
|
|
594
612
|
*/
|
|
595
613
|
generatedModulesDir?: string;
|
|
596
614
|
/**
|
|
597
|
-
*
|
|
598
|
-
*
|
|
599
|
-
* disables the compiler and emits a bare `react()`.
|
|
615
|
+
* Turn on the React Compiler (the caller checked that `oxc-transform-react`
|
|
616
|
+
* resolves); false/absent emits `react()` without it.
|
|
600
617
|
*/
|
|
601
|
-
|
|
618
|
+
reactCompiler?: boolean;
|
|
602
619
|
/** Project tsconfig path aliases (`find` -> absolute dir), e.g. `@` -> src. */
|
|
603
620
|
aliases?: Record<string, string>;
|
|
604
621
|
/**
|
|
@@ -617,6 +634,7 @@ export const astroConfigTemplate = (options: {
|
|
|
617
634
|
);
|
|
618
635
|
const {
|
|
619
636
|
askPath,
|
|
637
|
+
consentClientPath,
|
|
620
638
|
contentRoutes,
|
|
621
639
|
examplesPath,
|
|
622
640
|
examplesThemePath,
|
|
@@ -645,7 +663,7 @@ export const astroConfigTemplate = (options: {
|
|
|
645
663
|
context,
|
|
646
664
|
features,
|
|
647
665
|
needsReact,
|
|
648
|
-
|
|
666
|
+
reactCompiler: options.reactCompiler,
|
|
649
667
|
searchKind: config.search.provider.kind,
|
|
650
668
|
});
|
|
651
669
|
|
|
@@ -681,17 +699,29 @@ export const astroConfigTemplate = (options: {
|
|
|
681
699
|
// Base the redirect paths the same way routes are based, so a redirect lands
|
|
682
700
|
// under `basePath` too. Astro layers its own `base` (deployment.base) onto
|
|
683
701
|
// `from` when matching, but never onto `to` — see applyBaseToAstroRedirects.
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
702
|
+
// A moved page's Markdown copies move with it (see withMirrorRedirects).
|
|
703
|
+
const redirectPages = new Set(contentRoutes);
|
|
704
|
+
const basedRedirects = withMirrorRedirects(
|
|
705
|
+
applyBaseToAstroRedirects(
|
|
706
|
+
config.redirects,
|
|
707
|
+
config.basePath,
|
|
708
|
+
deployment.options.base ?? "",
|
|
709
|
+
redirectPages
|
|
710
|
+
),
|
|
711
|
+
redirectPages,
|
|
712
|
+
{ from: "", to: normalizeBasePath(deployment.options.base) }
|
|
713
|
+
);
|
|
714
|
+
// Only exact redirects: Astro can't prerender a pattern's redirect pages
|
|
715
|
+
// (it would need every path the pattern covers), so a pattern reaches the
|
|
716
|
+
// dev server through the integration and each host through its own rules.
|
|
717
|
+
const exactRedirects = basedRedirects.filter(
|
|
718
|
+
(redirect) => !isPatternPath(redirect.from)
|
|
689
719
|
);
|
|
690
720
|
const redirectsOption =
|
|
691
|
-
|
|
721
|
+
exactRedirects.length > 0
|
|
692
722
|
? `\n redirects: ${JSON.stringify(
|
|
693
723
|
Object.fromEntries(
|
|
694
|
-
|
|
724
|
+
exactRedirects.map((redirect) => [
|
|
695
725
|
redirect.from,
|
|
696
726
|
{ destination: redirect.to, status: redirect.status },
|
|
697
727
|
])
|
|
@@ -759,12 +789,19 @@ export const astroConfigTemplate = (options: {
|
|
|
759
789
|
const svelteImport = needsSvelte
|
|
760
790
|
? `import svelte from "@astrojs/svelte";\n`
|
|
761
791
|
: "";
|
|
792
|
+
// Content variables in `.mdx` are replaced before the MDX compiler parses
|
|
793
|
+
// the source, which reads `{{name}}` as a JavaScript expression.
|
|
794
|
+
const substitutesVariables = hasVariables(config.variables);
|
|
795
|
+
const variablesPluginEntry = substitutesVariables
|
|
796
|
+
? `variablesVitePlugin(${JSON.stringify(config.variables)}), `
|
|
797
|
+
: "";
|
|
762
798
|
const blumeImports = [
|
|
763
799
|
"blumeIntegration",
|
|
764
800
|
"includeHmrPlugin",
|
|
765
801
|
"prerenderDepsPlugin",
|
|
766
802
|
...runtimeModuleImports,
|
|
767
803
|
...(adapterOption.includes("withAdapterRoot") ? ["withAdapterRoot"] : []),
|
|
804
|
+
...(substitutesVariables ? ["variablesVitePlugin"] : []),
|
|
768
805
|
];
|
|
769
806
|
const blumeImport = `import { ${blumeImports.join(", ")} } from "blume/astro";\n`;
|
|
770
807
|
|
|
@@ -794,6 +831,7 @@ export const astroConfigTemplate = (options: {
|
|
|
794
831
|
deployBase,
|
|
795
832
|
externalLinks: config.markdown.externalLinks,
|
|
796
833
|
headingAnchors: config.markdown.headingAnchors,
|
|
834
|
+
variables: config.variables,
|
|
797
835
|
});
|
|
798
836
|
const processorOptions =
|
|
799
837
|
options.generatedModulesDir === undefined
|
|
@@ -804,7 +842,7 @@ export const astroConfigTemplate = (options: {
|
|
|
804
842
|
`mdx({ processor: blumeMdxProcessor(${processorOptions}) })`,
|
|
805
843
|
];
|
|
806
844
|
if (needsReact) {
|
|
807
|
-
integrations.push(reactIntegration(options.
|
|
845
|
+
integrations.push(reactIntegration(options.reactCompiler));
|
|
808
846
|
}
|
|
809
847
|
if (needsVue) {
|
|
810
848
|
integrations.push("vue()");
|
|
@@ -822,6 +860,7 @@ export const astroConfigTemplate = (options: {
|
|
|
822
860
|
contentRoutes,
|
|
823
861
|
ejected,
|
|
824
862
|
pages,
|
|
863
|
+
redirects: compileEveryRedirect(basedRedirects),
|
|
825
864
|
})
|
|
826
865
|
)})`
|
|
827
866
|
);
|
|
@@ -885,7 +924,7 @@ ${userConfigSetup}export default defineConfig({
|
|
|
885
924
|
// the overlap only adds memory.
|
|
886
925
|
build: { concurrency: Math.min(8, availableParallelism()) },
|
|
887
926
|
vite: {${viteCacheOption}
|
|
888
|
-
plugins: [${runtimeModulesPluginEntry}tailwindcss(), includeHmrPlugin(${configPath(
|
|
927
|
+
plugins: [${runtimeModulesPluginEntry}${variablesPluginEntry}tailwindcss(), includeHmrPlugin(${configPath(
|
|
889
928
|
`${context.outDir}/src/generated/includes.json`,
|
|
890
929
|
ejected
|
|
891
930
|
)}), prerenderDepsPlugin()],
|
|
@@ -897,8 +936,8 @@ ${userConfigSetup}export default defineConfig({
|
|
|
897
936
|
// Everything hydration can reach must be part of the dev dep optimizer's
|
|
898
937
|
// FIRST run. The Vite root is the generated runtime, so user pages,
|
|
899
938
|
// islands, and aliased components live outside it and are only crawled
|
|
900
|
-
// when first requested — and \`react/compiler-runtime\` is
|
|
901
|
-
// so no source scan can ever see it. A dependency discovered after
|
|
939
|
+
// when first requested — and \`react/compiler-runtime\` is injected by the
|
|
940
|
+
// React Compiler, so no source scan can ever see it. A dependency discovered after
|
|
902
941
|
// hydration begins triggers a mid-session re-optimization whose new
|
|
903
942
|
// generation imports React through new \`?v=\` URLs; the browser then
|
|
904
943
|
// evaluates a second React copy and every island tears down with
|
|
@@ -944,6 +983,7 @@ ${userConfigSetup}export default defineConfig({
|
|
|
944
983
|
resolve: {
|
|
945
984
|
alias: {
|
|
946
985
|
"blume:ask": ${configPath(askPath, ejected)},
|
|
986
|
+
"blume:consent-client": ${configPath(consentClientPath, ejected)},
|
|
947
987
|
"blume:examples": ${configPath(examplesPath, ejected)},
|
|
948
988
|
"blume:examples-theme": ${configPath(examplesThemePath, ejected)},
|
|
949
989
|
"blume:features": ${configPath(featuresPath, ejected)},
|
|
@@ -1095,12 +1135,21 @@ const ASK_FALLBACK_PROMPT =
|
|
|
1095
1135
|
|
|
1096
1136
|
/** The `ai.assistant` values the generated endpoint has to carry with it. */
|
|
1097
1137
|
export interface AskEndpointOptions {
|
|
1138
|
+
/** `ai.assistant.captcha` — the bot check the route verifies first. */
|
|
1139
|
+
captcha?: CaptchaAdapter;
|
|
1098
1140
|
/** `ai.assistant.cors` — origins allowed to call the route from another site. */
|
|
1099
1141
|
cors?: string[];
|
|
1142
|
+
/** `rateLimit` — the limiter the route checks before any work. */
|
|
1143
|
+
rateLimit?: RateLimitAdapter | null;
|
|
1100
1144
|
/** `ai.assistant.instructions` — extra system-prompt text. */
|
|
1101
1145
|
instructions?: string;
|
|
1102
1146
|
/** `ai.assistant.retrieval` — how much documentation each question carries. */
|
|
1103
1147
|
retrieval?: AskRetrievalOptions;
|
|
1148
|
+
/**
|
|
1149
|
+
* `ai.assistant.tools`, resolved against the adapter's default: give the
|
|
1150
|
+
* model the docs search and read-page tools.
|
|
1151
|
+
*/
|
|
1152
|
+
tools?: boolean;
|
|
1104
1153
|
}
|
|
1105
1154
|
|
|
1106
1155
|
/** The pieces `askEndpointTemplate` splices in for `ai.assistant.cors`. */
|
|
@@ -1132,7 +1181,7 @@ const askCorsTemplate = (cors: readonly string[] = []): AskCorsTemplate =>
|
|
|
1132
1181
|
imports: [
|
|
1133
1182
|
'import { preflightResponse, withCors } from "blume/ai/cors.ts";',
|
|
1134
1183
|
],
|
|
1135
|
-
open: "withCors(ALLOWED_ORIGINS, async (
|
|
1184
|
+
open: "withCors(ALLOWED_ORIGINS, async (context) => {",
|
|
1136
1185
|
setup: `
|
|
1137
1186
|
const ALLOWED_ORIGINS = ${JSON.stringify(cors)};
|
|
1138
1187
|
|
|
@@ -1140,12 +1189,94 @@ export const OPTIONS: APIRoute = ({ request }) =>
|
|
|
1140
1189
|
preflightResponse(request, ALLOWED_ORIGINS);
|
|
1141
1190
|
`,
|
|
1142
1191
|
}
|
|
1143
|
-
: { close: "};", imports: [], open: "async (
|
|
1192
|
+
: { close: "};", imports: [], open: "async (context) => {", setup: "" };
|
|
1193
|
+
|
|
1194
|
+
/** The pieces a server route splices in to check `rateLimit` first. */
|
|
1195
|
+
interface RateLimitTemplate {
|
|
1196
|
+
/** The check at the top of the handler; reads `context`. */
|
|
1197
|
+
check: string;
|
|
1198
|
+
/** The runtime import, plus what the adapter's store needs. */
|
|
1199
|
+
imports: string[];
|
|
1200
|
+
/** The route's limiter, built once at module scope. */
|
|
1201
|
+
setup: string;
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
/**
|
|
1205
|
+
* `rateLimit` for one server route: a limiter built at module scope from the
|
|
1206
|
+
* configured adapter, checked against every request, keyed by the reader's
|
|
1207
|
+
* address and `scope` so each route keeps its own budget (see
|
|
1208
|
+
* `ratelimit/runtime.ts`). Upstash reads its secrets through `getSecret`;
|
|
1209
|
+
* Cloudflare's binding comes from the Worker's env. Nothing at all when
|
|
1210
|
+
* rate limiting is off.
|
|
1211
|
+
*/
|
|
1212
|
+
export const rateLimitTemplate = (
|
|
1213
|
+
adapter: RateLimitAdapter | null | undefined,
|
|
1214
|
+
scope: string
|
|
1215
|
+
): RateLimitTemplate => {
|
|
1216
|
+
if (!adapter) {
|
|
1217
|
+
return { check: "", imports: [], setup: "" };
|
|
1218
|
+
}
|
|
1219
|
+
const imports = [
|
|
1220
|
+
'import { createLimiter, rateLimited } from "blume/ratelimit/runtime.ts";',
|
|
1221
|
+
];
|
|
1222
|
+
let runtime = "";
|
|
1223
|
+
if (adapter.kind === "upstash") {
|
|
1224
|
+
imports.push('import { getSecret } from "astro:env/server";');
|
|
1225
|
+
runtime = ", { secret: getSecret }";
|
|
1226
|
+
} else if (adapter.kind === "cloudflare") {
|
|
1227
|
+
imports.push(
|
|
1228
|
+
"// @ts-ignore `cloudflare:workers` is typed once `wrangler types` has run.",
|
|
1229
|
+
'import { env } from "cloudflare:workers";'
|
|
1230
|
+
);
|
|
1231
|
+
runtime = `, { binding: Reflect.get(env, ${JSON.stringify(RATE_LIMIT_BINDING)}) }`;
|
|
1232
|
+
}
|
|
1233
|
+
return {
|
|
1234
|
+
check: ` const limited = await rateLimited(limiter, context, ${JSON.stringify(scope)});
|
|
1235
|
+
if (limited) {
|
|
1236
|
+
return limited;
|
|
1237
|
+
}
|
|
1238
|
+
`,
|
|
1239
|
+
imports,
|
|
1240
|
+
setup: `\nconst limiter = createLimiter(${JSON.stringify(adapter)}${runtime});\n`,
|
|
1241
|
+
};
|
|
1242
|
+
};
|
|
1243
|
+
|
|
1244
|
+
/**
|
|
1245
|
+
* `ai.assistant.captcha` for the ask route: verify the question's token with
|
|
1246
|
+
* the provider before the model runs (see `captcha/verify.ts`). A missing
|
|
1247
|
+
* secret answers with the same "not configured" notice as a missing
|
|
1248
|
+
* provider key, which the panel shows as is; a failed check answers `403`.
|
|
1249
|
+
*/
|
|
1250
|
+
const askCaptchaTemplate = (adapter?: CaptchaAdapter): RateLimitTemplate => {
|
|
1251
|
+
if (!adapter) {
|
|
1252
|
+
return { check: "", imports: [], setup: "" };
|
|
1253
|
+
}
|
|
1254
|
+
const [secret = ""] = adapter.requiredSecrets;
|
|
1255
|
+
return {
|
|
1256
|
+
check: ` const captchaSecret = getSecret(${JSON.stringify(secret)});
|
|
1257
|
+
if (!captchaSecret) {
|
|
1258
|
+
return new Response(
|
|
1259
|
+
${JSON.stringify(`The assistant is not configured: set ${secret}.`)},
|
|
1260
|
+
{ status: 503 }
|
|
1261
|
+
);
|
|
1262
|
+
}
|
|
1263
|
+
const captchaToken =
|
|
1264
|
+
typeof body.captcha === "string" ? body.captcha : undefined;
|
|
1265
|
+
if (!(await verifyCaptcha(CAPTCHA, captchaToken, context, { secret: captchaSecret }))) {
|
|
1266
|
+
return new Response("Verification failed: the bot check didn't pass.", {
|
|
1267
|
+
status: 403,
|
|
1268
|
+
});
|
|
1269
|
+
}
|
|
1270
|
+
`,
|
|
1271
|
+
imports: ['import { verifyCaptcha } from "blume/captcha/verify.ts";'],
|
|
1272
|
+
setup: `\nconst CAPTCHA = ${JSON.stringify(adapter)};\n`,
|
|
1273
|
+
};
|
|
1274
|
+
};
|
|
1144
1275
|
|
|
1145
1276
|
/**
|
|
1146
1277
|
* Largest request body the assistant route reads: 64 KB, well above the
|
|
1147
|
-
*
|
|
1148
|
-
* never meets it.
|
|
1278
|
+
* message budget it validates next (`ai/ask-limits.ts`), so a real
|
|
1279
|
+
* conversation never meets it.
|
|
1149
1280
|
*/
|
|
1150
1281
|
const ASK_BODY_LIMIT_BYTES = 65_536;
|
|
1151
1282
|
|
|
@@ -1174,6 +1305,8 @@ export const askEndpointTemplate = (
|
|
|
1174
1305
|
): string => {
|
|
1175
1306
|
const { instructions, retrieval } = options ?? {};
|
|
1176
1307
|
const { grounded } = backend;
|
|
1308
|
+
// The tools read the grounding snapshot, so an ungrounded backend has none.
|
|
1309
|
+
const tools = grounded && Boolean(options?.tools);
|
|
1177
1310
|
const fallbackPrompt = instructions
|
|
1178
1311
|
? `${ASK_FALLBACK_PROMPT}\n\n${instructions}`
|
|
1179
1312
|
: ASK_FALLBACK_PROMPT;
|
|
@@ -1201,12 +1334,32 @@ export const askEndpointTemplate = (
|
|
|
1201
1334
|
if (retrieval) {
|
|
1202
1335
|
groundFields.push(`retrieval: ${JSON.stringify(retrieval)}`);
|
|
1203
1336
|
}
|
|
1337
|
+
if (tools) {
|
|
1338
|
+
groundFields.push("tools: true");
|
|
1339
|
+
}
|
|
1204
1340
|
const groundOptions =
|
|
1205
1341
|
groundFields.length > 0 ? `, { ${groundFields.join(", ")} }` : "";
|
|
1206
1342
|
setup += `\nconst ground = createAskContext(askData${groundOptions});\n`;
|
|
1343
|
+
if (tools) {
|
|
1344
|
+
imports.push(
|
|
1345
|
+
'import { stepCountIs } from "ai";',
|
|
1346
|
+
'import { ASK_MAX_STEPS, createAskTools } from "blume/ai/ask-tools.ts";'
|
|
1347
|
+
);
|
|
1348
|
+
setup += "const askTools = createAskTools(askData);\n";
|
|
1349
|
+
}
|
|
1207
1350
|
}
|
|
1208
1351
|
const cors = askCorsTemplate(options?.cors);
|
|
1209
1352
|
imports.push(...cors.imports);
|
|
1353
|
+
const limit = rateLimitTemplate(options?.rateLimit, "ask");
|
|
1354
|
+
for (const line of limit.imports) {
|
|
1355
|
+
if (!imports.includes(line)) {
|
|
1356
|
+
imports.push(line);
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
setup += limit.setup;
|
|
1360
|
+
const captcha = askCaptchaTemplate(options?.captcha);
|
|
1361
|
+
imports.push(...captcha.imports);
|
|
1362
|
+
setup += captcha.setup;
|
|
1210
1363
|
// Validate the client-supplied body and cap its size. The endpoint is
|
|
1211
1364
|
// unauthenticated, so bounding message count/length limits how much a caller
|
|
1212
1365
|
// can spend against the model per request, and restricting roles to
|
|
@@ -1215,7 +1368,8 @@ export const askEndpointTemplate = (
|
|
|
1215
1368
|
// limiter (or your provider's limits) for stronger protection. The body is
|
|
1216
1369
|
// read under a 64 KB cap before any parsing, since a self-hosted Node server
|
|
1217
1370
|
// would otherwise buffer an arbitrarily large POST in memory first.
|
|
1218
|
-
const validate = ` const
|
|
1371
|
+
const validate = ` const { request } = context;
|
|
1372
|
+
${limit.check} const text = await readCappedText(request, ${ASK_BODY_LIMIT_BYTES});
|
|
1219
1373
|
if (text === undefined) {
|
|
1220
1374
|
return new Response("Request too large: the body must be at most 64 KB.", {
|
|
1221
1375
|
status: 413,
|
|
@@ -1231,7 +1385,7 @@ export const askEndpointTemplate = (
|
|
|
1231
1385
|
const valid =
|
|
1232
1386
|
Array.isArray(raw) &&
|
|
1233
1387
|
raw.length > 0 &&
|
|
1234
|
-
raw.length <=
|
|
1388
|
+
raw.length <= ${ASK_MAX_MESSAGES} &&
|
|
1235
1389
|
raw.every(
|
|
1236
1390
|
(m: unknown) =>
|
|
1237
1391
|
typeof m === "object" &&
|
|
@@ -1239,10 +1393,10 @@ export const askEndpointTemplate = (
|
|
|
1239
1393
|
("role" in m && (m.role === "user" || m.role === "assistant")) &&
|
|
1240
1394
|
("content" in m && typeof m.content === "string")
|
|
1241
1395
|
) &&
|
|
1242
|
-
JSON.stringify(raw).length <=
|
|
1396
|
+
JSON.stringify(raw).length <= ${ASK_MAX_MESSAGES_CHARS};
|
|
1243
1397
|
if (!valid) {
|
|
1244
1398
|
return new Response(
|
|
1245
|
-
"Invalid request: send 1
|
|
1399
|
+
"Invalid request: send 1-${ASK_MAX_MESSAGES} user/assistant messages with string content.",
|
|
1246
1400
|
{ status: 400 }
|
|
1247
1401
|
);
|
|
1248
1402
|
}
|
|
@@ -1273,6 +1427,11 @@ export const askEndpointTemplate = (
|
|
|
1273
1427
|
? "instructions"
|
|
1274
1428
|
: `instructions:\n ${JSON.stringify(fallbackPrompt)}`,
|
|
1275
1429
|
"messages",
|
|
1430
|
+
// The model may search and read pages, then answer, within one request:
|
|
1431
|
+
// tool calls run here on the server and only the text reaches the reader.
|
|
1432
|
+
...(tools
|
|
1433
|
+
? ["tools: askTools(body.page)", "stopWhen: stepCountIs(ASK_MAX_STEPS)"]
|
|
1434
|
+
: []),
|
|
1276
1435
|
...backend.template.fields,
|
|
1277
1436
|
];
|
|
1278
1437
|
// The request's signal aborts when the reader closes the panel mid-answer
|
|
@@ -1294,7 +1453,7 @@ ${call}`
|
|
|
1294
1453
|
const handler = `export const POST: APIRoute = ${cors.open}
|
|
1295
1454
|
${validate}
|
|
1296
1455
|
${keyCheck}
|
|
1297
|
-
try {
|
|
1456
|
+
${captcha.check} try {
|
|
1298
1457
|
${stream}
|
|
1299
1458
|
return createTextStreamResponse({
|
|
1300
1459
|
stream: toTextStream({ stream: result.stream }),
|
|
@@ -1339,9 +1498,11 @@ const { strings } = Astro.props;
|
|
|
1339
1498
|
---
|
|
1340
1499
|
|
|
1341
1500
|
<Assistant
|
|
1501
|
+
captcha={data.config.assistant?.captcha ?? undefined}
|
|
1342
1502
|
endpoint={data.config.assistant?.endpoint ?? undefined}
|
|
1343
1503
|
strings={strings ?? data.ui.assistant}
|
|
1344
1504
|
suggestions={data.config.assistant?.suggestions ?? []}
|
|
1505
|
+
support={data.config.assistant?.support ?? undefined}
|
|
1345
1506
|
/>
|
|
1346
1507
|
`
|
|
1347
1508
|
: `---
|
|
@@ -1441,6 +1602,36 @@ ${
|
|
|
1441
1602
|
}
|
|
1442
1603
|
`;
|
|
1443
1604
|
|
|
1605
|
+
/**
|
|
1606
|
+
* Generate `.blume/src/generated/consent-client.ts`, behind the
|
|
1607
|
+
* `blume:consent-client` alias: the configured consent adapter's browser
|
|
1608
|
+
* module (see `consent/clients.ts`), started by `ConsentHead.astro` with the
|
|
1609
|
+
* adapter's options baked in. Only that adapter's module is referenced, so a
|
|
1610
|
+
* site bundles no other adapter's code. An adapter without one (a hosted
|
|
1611
|
+
* manager), or no `consent` at all, gets a no-op so the alias always resolves.
|
|
1612
|
+
*/
|
|
1613
|
+
export const consentClientTemplate = (
|
|
1614
|
+
consent: ResolvedConfig["consent"]
|
|
1615
|
+
): string => {
|
|
1616
|
+
const module = consent ? CONSENT_CLIENT_MODULES.get(consent.kind) : undefined;
|
|
1617
|
+
if (!(consent && module)) {
|
|
1618
|
+
return `// Generated by Blume. Do not edit.
|
|
1619
|
+
// The consent adapter has no browser module, or consent is off.
|
|
1620
|
+
export const startConsentClient = (): void => {};
|
|
1621
|
+
`;
|
|
1622
|
+
}
|
|
1623
|
+
return `// Generated by Blume. Do not edit.
|
|
1624
|
+
// The \`${consent.kind}\` consent adapter's browser module, with its options.
|
|
1625
|
+
import { start } from ${JSON.stringify(module)};
|
|
1626
|
+
|
|
1627
|
+
export const startConsentClient = (
|
|
1628
|
+
consent: Parameters<typeof start>[0]
|
|
1629
|
+
): void => {
|
|
1630
|
+
start(consent, ${JSON.stringify(consent.options)});
|
|
1631
|
+
};
|
|
1632
|
+
`;
|
|
1633
|
+
};
|
|
1634
|
+
|
|
1444
1635
|
/**
|
|
1445
1636
|
* Generate `.blume/src/generated/search-client.ts` — the provider-specific
|
|
1446
1637
|
* loader the `<Search>` component lazy-imports via the `blume:search-client`
|
|
@@ -1463,10 +1654,12 @@ export const searchClientTemplate = (config: ResolvedConfig): string => {
|
|
|
1463
1654
|
return hostedSearchClient(provider);
|
|
1464
1655
|
}
|
|
1465
1656
|
case "server": {
|
|
1466
|
-
|
|
1657
|
+
// The dialog passes `typing`, which paces its queries (see endpoint.ts).
|
|
1658
|
+
return `${SEARCH_CLIENT_HEADER}${searchClientImport("endpoint")}${SEARCH_BASE_IMPORT}import type { SearchClientOptions } from "blume/components/layout/search/types.ts";
|
|
1467
1659
|
const api = joinBase(import.meta.env.BASE_URL, "api/search");
|
|
1468
1660
|
|
|
1469
|
-
export const createSearch = (
|
|
1661
|
+
export const createSearch = (options: SearchClientOptions = {}) =>
|
|
1662
|
+
create({ ...options, api });
|
|
1470
1663
|
`;
|
|
1471
1664
|
}
|
|
1472
1665
|
case "pagefind": {
|
|
@@ -1488,28 +1681,55 @@ export const createSearch = () => create({ url });
|
|
|
1488
1681
|
/**
|
|
1489
1682
|
* Generate the Mixedbread search endpoint (`/api/search`). It holds the secret
|
|
1490
1683
|
* key server-side and proxies semantic queries to the configured store. The
|
|
1491
|
-
* adapter's options are inlined as
|
|
1492
|
-
* config.
|
|
1493
|
-
*
|
|
1684
|
+
* adapter's options are inlined as literals so the route never imports the
|
|
1685
|
+
* config. `pages` (see `sourcePages`) maps each source file to its page: a
|
|
1686
|
+
* chunk links to the page whose file `mxbai store sync` uploaded it from,
|
|
1687
|
+
* found by the longest trailing run of the path the CLI recorded.
|
|
1494
1688
|
*/
|
|
1495
1689
|
export const mixedbreadSearchEndpointTemplate = (
|
|
1496
|
-
options: MixedbreadOptions
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1690
|
+
options: MixedbreadOptions,
|
|
1691
|
+
pages: [string, SourcePage][],
|
|
1692
|
+
rateLimit?: RateLimitAdapter | null
|
|
1693
|
+
): string => {
|
|
1694
|
+
const { search_options: tuning, storeId, ...searchOptions } = options;
|
|
1695
|
+
const limit = rateLimitTemplate(rateLimit, "search");
|
|
1696
|
+
const imports = [
|
|
1697
|
+
'import type { APIRoute } from "astro";',
|
|
1698
|
+
'import { getSecret } from "astro:env/server";',
|
|
1699
|
+
'import Mixedbread from "@mixedbread/sdk";',
|
|
1700
|
+
'import { readCappedText } from "blume/core/request-body.ts";',
|
|
1701
|
+
...limit.imports.filter((line) => !line.includes('"astro:env/server"')),
|
|
1702
|
+
];
|
|
1703
|
+
return `// Generated by Blume. Do not edit.
|
|
1704
|
+
${imports.join("\n")}
|
|
1503
1705
|
|
|
1504
1706
|
export const prerender = false;
|
|
1505
1707
|
|
|
1506
1708
|
const client = new Mixedbread({ apiKey: getSecret("MIXEDBREAD_API_KEY") ?? "" });
|
|
1507
|
-
const
|
|
1508
|
-
// Every option
|
|
1509
|
-
const
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1709
|
+
const STORE_ID = ${JSON.stringify(storeId)};
|
|
1710
|
+
// Every other option reaches the search call verbatim.
|
|
1711
|
+
const SEARCH_OPTIONS = ${JSON.stringify(searchOptions)};
|
|
1712
|
+
const SEARCH_TUNING = ${JSON.stringify(tuning ?? {})};
|
|
1713
|
+
// Each page's source file, relative to the project root, and the page it renders.
|
|
1714
|
+
const PAGES = new Map<string, { title: string; url: string }>(${JSON.stringify(pages)});
|
|
1715
|
+
|
|
1716
|
+
// The page a synced file renders. \`mxbai store sync\` records the path
|
|
1717
|
+
// relative to where it ran (the project root, or a monorepo root above it), so
|
|
1718
|
+
// the longest trailing run of that path that names a page's file wins.
|
|
1719
|
+
const pageFor = (path: string) => {
|
|
1720
|
+
const segments = path.replaceAll("\\\\", "/").split("/");
|
|
1721
|
+
for (let start = 0; start < segments.length; start += 1) {
|
|
1722
|
+
const page = PAGES.get(segments.slice(start).join("/"));
|
|
1723
|
+
if (page) {
|
|
1724
|
+
return page;
|
|
1725
|
+
}
|
|
1726
|
+
}
|
|
1727
|
+
return undefined;
|
|
1728
|
+
};
|
|
1729
|
+
${limit.setup}
|
|
1730
|
+
export const POST: APIRoute = async (context) => {
|
|
1731
|
+
const { request } = context;
|
|
1732
|
+
${limit.check} // A search body is one short query: read it under a 16 KB cap, so a
|
|
1513
1733
|
// self-hosted server never buffers an arbitrarily large POST first.
|
|
1514
1734
|
const text = await readCappedText(request, 16_384);
|
|
1515
1735
|
if (text === undefined) {
|
|
@@ -1531,29 +1751,39 @@ export const POST: APIRoute = async ({ request }) => {
|
|
|
1531
1751
|
});
|
|
1532
1752
|
}
|
|
1533
1753
|
// \`top_k\` defaults to 8 unless an option sets it; the query and store
|
|
1534
|
-
// always come from the request and \`storeId\`.
|
|
1754
|
+
// always come from the request and \`storeId\`. File metadata is always
|
|
1755
|
+
// returned: it holds the path \`mxbai store sync\` recorded for the file.
|
|
1535
1756
|
const response = await client.stores.search({
|
|
1536
1757
|
top_k: 8,
|
|
1537
1758
|
...SEARCH_OPTIONS,
|
|
1538
1759
|
query,
|
|
1760
|
+
search_options: { ...SEARCH_TUNING, return_metadata: true },
|
|
1539
1761
|
store_identifiers: [STORE_ID],
|
|
1540
1762
|
});
|
|
1541
|
-
const
|
|
1763
|
+
const seen = new Set<string>();
|
|
1764
|
+
const hits = (response.data ?? []).flatMap((chunk) => {
|
|
1765
|
+
const { file_path: path } = (chunk.metadata ?? {}) as { file_path?: unknown };
|
|
1766
|
+
const page = typeof path === "string" ? pageFor(path) : undefined;
|
|
1767
|
+
// A file no page renders has nowhere to link, and a page's later chunks
|
|
1768
|
+
// would repeat it: one hit per page, at its best chunk.
|
|
1769
|
+
if (!page || seen.has(page.url)) {
|
|
1770
|
+
return [];
|
|
1771
|
+
}
|
|
1772
|
+
seen.add(page.url);
|
|
1542
1773
|
const meta = chunk.generated_metadata ?? {};
|
|
1543
1774
|
// Only a text chunk carries \`text\`; image, audio, and video chunks fall
|
|
1544
1775
|
// back to the excerpt the store generated for them.
|
|
1545
1776
|
const text = "text" in chunk ? chunk.text : undefined;
|
|
1546
|
-
return
|
|
1547
|
-
excerpt: text ?? meta.excerpt ?? "",
|
|
1548
|
-
|
|
1549
|
-
url: meta.url ?? "",
|
|
1550
|
-
};
|
|
1777
|
+
return [
|
|
1778
|
+
{ excerpt: text ?? meta.excerpt ?? "", title: page.title, url: page.url },
|
|
1779
|
+
];
|
|
1551
1780
|
});
|
|
1552
1781
|
return new Response(JSON.stringify(hits), {
|
|
1553
1782
|
headers: { "Content-Type": "application/json" },
|
|
1554
1783
|
});
|
|
1555
1784
|
};
|
|
1556
1785
|
`;
|
|
1786
|
+
};
|
|
1557
1787
|
|
|
1558
1788
|
/**
|
|
1559
1789
|
* Generate the raw-Markdown endpoints (`[...slug].md.ts` and `[...slug].mdx.ts`).
|
|
@@ -1756,17 +1986,26 @@ export const ALL: APIRoute = ({ request }) => handler(request);
|
|
|
1756
1986
|
* client-side data: that is the whole trust boundary keeping the endpoint from
|
|
1757
1987
|
* being an open proxy onto the deployment's own network.
|
|
1758
1988
|
*/
|
|
1759
|
-
export const playgroundProxyTemplate = (
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1989
|
+
export const playgroundProxyTemplate = (
|
|
1990
|
+
origins: string[],
|
|
1991
|
+
rateLimit?: RateLimitAdapter | null
|
|
1992
|
+
): string => {
|
|
1993
|
+
const limit = rateLimitTemplate(rateLimit, "api-proxy");
|
|
1994
|
+
const route = limit.check
|
|
1995
|
+
? `export const ALL: APIRoute = async (context) => {
|
|
1996
|
+
${limit.check} return handler(context.request);
|
|
1997
|
+
};`
|
|
1998
|
+
: "export const ALL: APIRoute = ({ request }) => handler(request);";
|
|
1999
|
+
return `// Generated by Blume. Do not edit.
|
|
2000
|
+
${['import type { APIRoute } from "astro";', 'import { createPlaygroundProxyHandler } from "blume/openapi/proxy.ts";', ...limit.imports].join("\n")}
|
|
1763
2001
|
|
|
1764
2002
|
export const prerender = false;
|
|
1765
2003
|
|
|
1766
2004
|
const handler = createPlaygroundProxyHandler(${JSON.stringify(origins)});
|
|
1767
|
-
|
|
1768
|
-
|
|
2005
|
+
${limit.setup}
|
|
2006
|
+
${route}
|
|
1769
2007
|
`;
|
|
2008
|
+
};
|
|
1770
2009
|
|
|
1771
2010
|
/** Generate a prerendered endpoint that serves a fixed JSON payload. */
|
|
1772
2011
|
export const staticJsonEndpointTemplate = <Payload extends object>(
|
|
@@ -2090,6 +2329,9 @@ const contentComponentsSource = (mathEnabled: boolean) => {
|
|
|
2090
2329
|
return {
|
|
2091
2330
|
imports: `import Accordion from "blume/components/content/Accordion.astro";
|
|
2092
2331
|
import AccordionItem from "blume/components/content/AccordionItem.astro";
|
|
2332
|
+
import ApiEndpoint from "blume/components/content/ApiEndpoint.astro";
|
|
2333
|
+
import ApiPlayground from "blume/components/content/ApiPlayground.astro";
|
|
2334
|
+
import ApiRail from "blume/components/content/ApiRail.astro";
|
|
2093
2335
|
import AutoTypeTable from "blume/components/content/AutoTypeTable.astro";
|
|
2094
2336
|
import Badge from "blume/components/content/Badge.astro";
|
|
2095
2337
|
import Callout from "blume/components/content/Callout.astro";
|
|
@@ -2109,7 +2351,11 @@ import FileTree from "blume/components/content/FileTree.astro";
|
|
|
2109
2351
|
import Frame from "blume/components/content/Frame.astro";
|
|
2110
2352
|
import GithubInfo from "blume/components/content/GithubInfo.astro";
|
|
2111
2353
|
import Panel from "blume/components/content/Panel.astro";
|
|
2354
|
+
import ParamField from "blume/components/content/ParamField.astro";
|
|
2112
2355
|
import Prompt from "blume/components/content/Prompt.astro";
|
|
2356
|
+
import RequestExample from "blume/components/content/RequestExample.astro";
|
|
2357
|
+
import ResponseExample from "blume/components/content/ResponseExample.astro";
|
|
2358
|
+
import ResponseField from "blume/components/content/ResponseField.astro";
|
|
2113
2359
|
import Step from "blume/components/content/Step.astro";
|
|
2114
2360
|
import Steps from "blume/components/content/Steps.astro";
|
|
2115
2361
|
import Tab from "blume/components/content/Tab.astro";
|
|
@@ -2120,10 +2366,13 @@ import TreeRoot from "blume/components/content/Tree.astro";
|
|
|
2120
2366
|
import TreeFile from "blume/components/content/TreeFile.astro";
|
|
2121
2367
|
import TreeFolder from "blume/components/content/TreeFolder.astro";
|
|
2122
2368
|
import TypeTable from "blume/components/content/TypeTable.astro";
|
|
2369
|
+
import View from "blume/components/content/View.astro";
|
|
2123
2370
|
import Visibility from "blume/components/content/Visibility.astro";
|
|
2124
2371
|
import YouTube from "blume/components/content/YouTube.astro";
|
|
2125
2372
|
import Icon from "blume/components/Icon.astro";
|
|
2126
2373
|
import LocaleLinks from "blume/components/layout/LocaleLinks.astro";
|
|
2374
|
+
import NarrationPlayer from "blume/components/layout/NarrationPlayer.astro";
|
|
2375
|
+
import ViewSwitcher from "blume/components/content/ViewSwitcher.astro";
|
|
2127
2376
|
import ApiOverview from "blume/components/openapi/ApiOverview.astro";
|
|
2128
2377
|
import ApiTagOperations from "blume/components/openapi/ApiTagOperations.astro";
|
|
2129
2378
|
import Operation from "blume/components/openapi/Operation.astro";
|
|
@@ -2131,7 +2380,10 @@ ${mathImport}import { mdxComponents as userMdx, layoutOverrides } from "../gener
|
|
|
2131
2380
|
map: `{
|
|
2132
2381
|
Accordion,
|
|
2133
2382
|
AccordionItem,
|
|
2383
|
+
ApiEndpoint,
|
|
2134
2384
|
ApiOverview,
|
|
2385
|
+
ApiPlayground,
|
|
2386
|
+
ApiRail,
|
|
2135
2387
|
ApiTagOperations,
|
|
2136
2388
|
AutoTypeTable,
|
|
2137
2389
|
Badge,
|
|
@@ -2152,7 +2404,11 @@ ${mathImport}import { mdxComponents as userMdx, layoutOverrides } from "../gener
|
|
|
2152
2404
|
Icon,
|
|
2153
2405
|
Operation,
|
|
2154
2406
|
Panel,
|
|
2407
|
+
ParamField,
|
|
2155
2408
|
Prompt,
|
|
2409
|
+
RequestExample,
|
|
2410
|
+
ResponseExample,
|
|
2411
|
+
ResponseField,
|
|
2156
2412
|
Step,
|
|
2157
2413
|
Steps,
|
|
2158
2414
|
Tab,
|
|
@@ -2161,6 +2417,7 @@ ${mathImport}import { mdxComponents as userMdx, layoutOverrides } from "../gener
|
|
|
2161
2417
|
Tooltip,
|
|
2162
2418
|
Tree,
|
|
2163
2419
|
TypeTable,
|
|
2420
|
+
View,
|
|
2164
2421
|
Visibility,
|
|
2165
2422
|
YouTube,
|
|
2166
2423
|
${mathEntry}...userMdx,
|
|
@@ -2191,10 +2448,12 @@ export const catchAllPageTemplate = (options: {
|
|
|
2191
2448
|
return `---
|
|
2192
2449
|
// Generated by Blume. Do not edit.
|
|
2193
2450
|
import { getEntry, render } from "astro:content";
|
|
2451
|
+
import { pageModeLayout } from "blume/core/page-modes.ts";
|
|
2194
2452
|
import type { CollectionKey } from "astro:content";
|
|
2195
2453
|
import RootLayout from "blume/components/layout/RootLayout.astro";
|
|
2196
2454
|
import { withBase, withMountedBase } from "blume/components/islands/base-path.ts";
|
|
2197
2455
|
import { mountBasePath, stripBasePath } from "blume/core/base-path.ts";
|
|
2456
|
+
import { routeSetFor, servesRoute } from "blume/core/locale-links.ts";
|
|
2198
2457
|
import { resolveSlot } from "blume/components/layout/overrides.ts";
|
|
2199
2458
|
${componentImports}
|
|
2200
2459
|
import data from "blume:data";
|
|
@@ -2242,6 +2501,13 @@ const { Content, headings: allHeadings, remarkPluginFrontmatter } = await render
|
|
|
2242
2501
|
// frontmatter (see markdown/heading-anchors.ts). Only the plugin's array
|
|
2243
2502
|
// counts: \`frontmatter.extend\` can declare the same key, and on a page with
|
|
2244
2503
|
// no headings that user-supplied value would pass straight through.
|
|
2504
|
+
// A page written in \`<View>\` blocks gets their picker above the content;
|
|
2505
|
+
// the views plugin lists them through the render's frontmatter (see
|
|
2506
|
+
// markdown/views.ts), and only a list of titled entries counts.
|
|
2507
|
+
const viewsRaw = remarkPluginFrontmatter?.${VIEWS_KEY};
|
|
2508
|
+
const views = Array.isArray(viewsRaw)
|
|
2509
|
+
? viewsRaw.filter((view) => typeof view?.title === "string")
|
|
2510
|
+
: [];
|
|
2245
2511
|
const tocHiddenRaw = remarkPluginFrontmatter?.${TOC_HIDDEN_KEY};
|
|
2246
2512
|
const tocHidden = new Set(Array.isArray(tocHiddenRaw) ? tocHiddenRaw : []);
|
|
2247
2513
|
const headings =
|
|
@@ -2320,6 +2586,14 @@ const htmlLang = i18n ? locale : "en";
|
|
|
2320
2586
|
// follows that language — not the (mirrored) page locale.
|
|
2321
2587
|
const contentLocale =
|
|
2322
2588
|
fallback && i18n?.fallbackLocale ? i18n.fallbackLocale : locale;
|
|
2589
|
+
// "Listen to this page", unless the page opts out. The spoken cues are read
|
|
2590
|
+
// between the content's own sentences, so they come from the content's
|
|
2591
|
+
// language (a fallback page's), while the player's labels follow the page.
|
|
2592
|
+
// A \`custom\` or \`frame\` page brings its own heading: no title, description,
|
|
2593
|
+
// or narration player above its content.
|
|
2594
|
+
const pageChrome = pageModeLayout(frontmatter.mode).chrome;
|
|
2595
|
+
const narration = pageChrome && frontmatter.narration ? data.config.narration : null;
|
|
2596
|
+
const narrationCues = (i18n ? (data.uiByLocale[contentLocale] ?? data.ui) : data.ui).narration;
|
|
2323
2597
|
const contentDir = i18n
|
|
2324
2598
|
? (i18n.locales.find((l) => l.code === contentLocale)?.dir ?? "ltr")
|
|
2325
2599
|
: "ltr";
|
|
@@ -2383,18 +2657,24 @@ const logicalRoute = i18n
|
|
|
2383
2657
|
? stripLocale(stripBasePath(data.config.basePath, route), locale)
|
|
2384
2658
|
: route;
|
|
2385
2659
|
// A page from a one-language source (GitHub Releases) gets no switcher: every
|
|
2386
|
-
// other locale would only repeat the same text.
|
|
2660
|
+
// other locale would only repeat the same text. A locale with no real
|
|
2661
|
+
// translation links the page's fallback copy, which exists only while
|
|
2662
|
+
// fallbacks are on; where nothing is served at that URL (\`fallbackLocale:
|
|
2663
|
+
// null\`), the locale is left out rather than linked to a 404.
|
|
2387
2664
|
const localeSwitch = i18n && !monolingual
|
|
2388
|
-
? i18n.locales.
|
|
2665
|
+
? i18n.locales.flatMap((l) => {
|
|
2389
2666
|
const alt = (alternates ?? []).find((x) => x.locale === l.code);
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2667
|
+
const href = alt ? alt.path : mountLocalized(logicalRoute, l.code);
|
|
2668
|
+
return alt || servesRoute(routeSetFor(data.routes), href)
|
|
2669
|
+
? [{
|
|
2670
|
+
code: l.code,
|
|
2671
|
+
current: l.code === locale,
|
|
2672
|
+
dir: l.dir,
|
|
2673
|
+
href,
|
|
2674
|
+
label: l.label,
|
|
2675
|
+
untranslated: !alt,
|
|
2676
|
+
}]
|
|
2677
|
+
: [];
|
|
2398
2678
|
})
|
|
2399
2679
|
: [];
|
|
2400
2680
|
|
|
@@ -2503,6 +2783,10 @@ const LayoutComponent = resolveSlot(layoutOverrides.Layout, RootLayout);
|
|
|
2503
2783
|
canonical={canonical}
|
|
2504
2784
|
editUrl={editUrl}
|
|
2505
2785
|
feedback={data.config.feedback}
|
|
2786
|
+
feedbackComments={data.config.feedbackComments}
|
|
2787
|
+
pagination={frontmatter.pagination}
|
|
2788
|
+
related={frontmatter.related}
|
|
2789
|
+
search={frontmatter.search}
|
|
2506
2790
|
exportPdf={${options.exportPdf}}
|
|
2507
2791
|
exportEpub={${options.exportEpub}}${
|
|
2508
2792
|
options.navFragments
|
|
@@ -2515,13 +2799,25 @@ const LayoutComponent = resolveSlot(layoutOverrides.Layout, RootLayout);
|
|
|
2515
2799
|
discovery={data.config.discovery}
|
|
2516
2800
|
siteUrl={data.config.site}
|
|
2517
2801
|
pageType={frontmatter.type}
|
|
2802
|
+
apiRail={remarkPluginFrontmatter?.${API_RAIL_KEY} === true}
|
|
2803
|
+
pageMode={frontmatter.mode}
|
|
2518
2804
|
published={frontmatter.date ?? frontmatter.changelog?.date ?? null}
|
|
2519
2805
|
lastModified={lastModified}
|
|
2520
2806
|
noindex={effectiveNoindex}
|
|
2521
2807
|
structuredDataEnabled={data.config.structuredData}
|
|
2522
2808
|
>
|
|
2523
|
-
<h1>{title}</h1>
|
|
2524
|
-
{frontmatter.description && <p class="text-lg text-muted-foreground">{frontmatter.description}</p>}
|
|
2809
|
+
{pageChrome && <h1>{title}</h1>}
|
|
2810
|
+
{pageChrome && frontmatter.description && <p class="text-lg text-muted-foreground">{frontmatter.description}</p>}
|
|
2811
|
+
{narration && (
|
|
2812
|
+
<NarrationPlayer
|
|
2813
|
+
audioBase={narration.generated ? withMountedBase("/blume-narration/audio/") : undefined}
|
|
2814
|
+
cues={narrationCues}
|
|
2815
|
+
lang={i18n ? contentLocale : htmlLang}
|
|
2816
|
+
manifest={narration.generated ? withMountedBase(encodeURI(\`/blume-narration/\${route === "/" ? "index" : route.slice(1)}.json\`)) : undefined}
|
|
2817
|
+
strings={ui.narration}
|
|
2818
|
+
/>
|
|
2819
|
+
)}
|
|
2820
|
+
{views.length > 1 && <ViewSwitcher label={ui.content.selectView} views={views} />}
|
|
2525
2821
|
<LocaleLinks locale={locale}>
|
|
2526
2822
|
<Content components={components} />
|
|
2527
2823
|
</LocaleLinks>
|
|
@@ -2705,13 +3001,15 @@ const canonical = base ? base + basedRoute : null;
|
|
|
2705
3001
|
const ogPath = data.config.og.enabled ? withMountedBase("/og/changelog.png") : null;
|
|
2706
3002
|
const ogImage = ogPath && base ? base + ogPath : ogPath;
|
|
2707
3003
|
|
|
2708
|
-
// The page chrome (h1, title, description) comes from the
|
|
2709
|
-
// \`changelog\` group
|
|
2710
|
-
//
|
|
3004
|
+
// The page chrome (h1, title, description, empty state) comes from the
|
|
3005
|
+
// translatable \`changelog\` group, which carries the \`changelog\` config's title and
|
|
3006
|
+
// description when it sets them; optional chaining tolerates a
|
|
3007
|
+
// not-yet-regenerated data snapshot from before these keys existed.
|
|
2711
3008
|
const changelogTitle = data.ui.changelog?.title ?? "Changelog";
|
|
2712
3009
|
const changelogDescription =
|
|
2713
3010
|
data.ui.changelog?.description ??
|
|
2714
3011
|
"Product updates, new features, and fixes from every release.";
|
|
3012
|
+
const changelogEmpty = data.ui.changelog?.empty ?? "No changelog entries yet.";
|
|
2715
3013
|
// The layout suffixes "- {site title}" itself, so the page title is just the
|
|
2716
3014
|
// changelog's own name — prefixing the site title too would double it
|
|
2717
3015
|
// ("Acme Changelog - Acme").
|
|
@@ -2764,7 +3062,7 @@ const LayoutComponent = resolveSlot(layoutOverrides.Layout, RootLayout);
|
|
|
2764
3062
|
<p class="text-lg text-muted-foreground">{changelogDescription}</p>
|
|
2765
3063
|
{
|
|
2766
3064
|
items.length === 0 ? (
|
|
2767
|
-
<p>
|
|
3065
|
+
<p>{changelogEmpty}</p>
|
|
2768
3066
|
) : (
|
|
2769
3067
|
<div class="not-prose mt-10 divide-y divide-border border-border border-y">
|
|
2770
3068
|
{groups.map((group) => (
|