blume 1.7.3 → 2.0.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 +19 -0
- package/CHANGELOG.md +217 -0
- package/README.md +34 -21
- package/dist/cli/chunk-11j0384y.js +148 -0
- package/dist/cli/chunk-11j0384y.js.map +10 -0
- package/dist/cli/{chunk-rqy0s5wh.js → chunk-1w8dp3qb.js} +17 -16
- package/dist/cli/{chunk-rqy0s5wh.js.map → chunk-1w8dp3qb.js.map} +3 -3
- package/dist/cli/chunk-2q1dwty4.js +75 -0
- package/dist/cli/chunk-2q1dwty4.js.map +11 -0
- package/dist/cli/chunk-41za066z.js +122 -0
- package/dist/cli/chunk-41za066z.js.map +11 -0
- package/dist/cli/chunk-5a2z0198.js +133 -0
- package/dist/cli/chunk-5a2z0198.js.map +10 -0
- package/dist/cli/{chunk-5g0w1e2c.js → chunk-6k8vp3ta.js} +25 -13
- package/dist/cli/{chunk-5g0w1e2c.js.map → chunk-6k8vp3ta.js.map} +4 -4
- package/dist/cli/{chunk-5qk08vmp.js → chunk-79njf86q.js} +133 -54
- package/dist/cli/chunk-79njf86q.js.map +11 -0
- package/dist/cli/{chunk-e7f42gdj.js → chunk-7ez8ny0t.js} +2 -2
- package/dist/cli/{chunk-nn13znc2.js → chunk-88cpgt6h.js} +1 -1
- package/dist/cli/chunk-a9kptbw5.js +361 -0
- package/dist/cli/chunk-a9kptbw5.js.map +14 -0
- package/dist/cli/chunk-abh8yjkn.js +31 -0
- package/dist/cli/chunk-abh8yjkn.js.map +10 -0
- package/dist/cli/chunk-b5aj94ah.js +91 -0
- package/dist/cli/chunk-b5aj94ah.js.map +10 -0
- package/dist/cli/{chunk-b27xqwn9.js → chunk-bctazmbk.js} +9 -5
- package/dist/cli/chunk-bctazmbk.js.map +10 -0
- package/dist/cli/chunk-beat36xx.js +279 -0
- package/dist/cli/chunk-beat36xx.js.map +10 -0
- package/dist/cli/chunk-bnbmcwfb.js +145 -0
- package/dist/cli/chunk-bnbmcwfb.js.map +11 -0
- package/dist/cli/{chunk-0xjyb285.js → chunk-bw22s759.js} +15 -5
- package/dist/cli/{chunk-0xjyb285.js.map → chunk-bw22s759.js.map} +4 -4
- package/dist/cli/chunk-by2290sx.js +39 -0
- package/dist/cli/chunk-by2290sx.js.map +10 -0
- package/dist/cli/chunk-d1tadaw7.js +79 -0
- package/dist/cli/chunk-d1tadaw7.js.map +10 -0
- package/dist/cli/{chunk-ahnw3kxw.js → chunk-d80hr03s.js} +24 -19
- package/dist/cli/chunk-d80hr03s.js.map +15 -0
- package/dist/cli/chunk-ernrthtr.js +97 -0
- package/dist/cli/chunk-ernrthtr.js.map +10 -0
- package/dist/cli/chunk-f2972sbt.js +374 -0
- package/dist/cli/chunk-f2972sbt.js.map +10 -0
- package/dist/cli/{chunk-vacwm2hv.js → chunk-f7t03s3g.js} +2 -2
- package/dist/cli/{chunk-2z47ypj8.js → chunk-fa25z98p.js} +16 -3
- package/dist/cli/chunk-fa25z98p.js.map +11 -0
- package/dist/cli/{chunk-cjtn640a.js → chunk-fh5hj5jt.js} +43 -20
- package/dist/cli/chunk-fh5hj5jt.js.map +10 -0
- package/dist/cli/{chunk-yg63d42r.js → chunk-j8mw0za6.js} +86 -57
- package/dist/cli/chunk-j8mw0za6.js.map +35 -0
- package/dist/cli/{chunk-ct47dqpx.js → chunk-jts8mvcz.js} +67 -7
- package/dist/cli/{chunk-2mzebbbz.js.map → chunk-jts8mvcz.js.map} +6 -4
- package/dist/cli/{chunk-ps4m1xh4.js → chunk-mnqj32sj.js} +505 -542
- package/dist/cli/chunk-mnqj32sj.js.map +12 -0
- package/dist/cli/{chunk-8cjtbafj.js → chunk-mwt1k8n7.js} +100 -374
- package/dist/cli/chunk-mwt1k8n7.js.map +10 -0
- package/dist/cli/chunk-nk3ts2xk.js +51 -0
- package/dist/cli/chunk-nk3ts2xk.js.map +10 -0
- package/dist/cli/{chunk-3r45185y.js → chunk-pat2zzwc.js} +9 -11
- package/dist/cli/{chunk-3r45185y.js.map → chunk-pat2zzwc.js.map} +2 -2
- package/dist/cli/chunk-pnnvybbk.js +176 -0
- package/dist/cli/chunk-pnnvybbk.js.map +11 -0
- package/dist/cli/{chunk-4x36ddpw.js → chunk-sqn5t4q0.js} +81 -87
- package/dist/cli/chunk-sqn5t4q0.js.map +10 -0
- package/dist/cli/{chunk-bvwwhd84.js → chunk-tzne8qfq.js} +15 -15
- package/dist/cli/{chunk-bvwwhd84.js.map → chunk-tzne8qfq.js.map} +1 -1
- package/dist/cli/chunk-xaz13gwg.js +12449 -0
- package/dist/cli/chunk-xaz13gwg.js.map +182 -0
- package/dist/cli/chunk-y3e45rc8.js +102 -0
- package/dist/cli/chunk-y3e45rc8.js.map +10 -0
- package/dist/cli/chunk-z01ze5c1.js +261 -0
- package/dist/cli/chunk-z01ze5c1.js.map +10 -0
- package/dist/cli/{chunk-k79xp7av.js → chunk-z1f5arsg.js} +219 -689
- package/dist/cli/chunk-z1f5arsg.js.map +36 -0
- package/dist/cli/{chunk-1jefwnfs.js → chunk-zg2gtj10.js} +1076 -2592
- package/dist/cli/chunk-zg2gtj10.js.map +35 -0
- package/dist/cli/{chunk-dwgcp5sm.js → chunk-zxccj738.js} +1 -1
- package/dist/cli/index.js +214 -57
- package/dist/cli/index.js.map +8 -7
- package/dist/types/ai/agent-readability.d.ts +52 -0
- package/dist/types/ai/ai-catalog.d.ts +42 -0
- package/dist/types/ai/api/paths.d.ts +17 -0
- package/dist/types/ai/api-catalog.d.ts +18 -0
- package/dist/types/ai/ask.d.ts +368 -0
- package/dist/types/ai/changelog-markdown.d.ts +2 -0
- package/dist/types/ai/component-markdown.d.ts +2 -2
- package/dist/types/ai/index.d.ts +24 -0
- package/dist/types/ai/link-headers.d.ts +24 -0
- package/dist/types/ai/llms.d.ts +25 -0
- package/dist/types/ai/markdown.d.ts +45 -0
- package/dist/types/ai/mcp/discovery.d.ts +68 -0
- package/dist/types/ai/mcp/tools.d.ts +16 -0
- package/dist/types/ai/openapi-components.d.ts +43 -0
- package/dist/types/ai/relative-links.d.ts +26 -0
- package/dist/types/ai/serializers.d.ts +15 -0
- package/dist/types/ai/skills.d.ts +42 -0
- package/dist/types/ai/tar.d.ts +25 -0
- package/dist/types/ai/visibility.d.ts +17 -0
- package/dist/types/ai/web-bot-auth.d.ts +16 -0
- package/dist/types/analytics/adobe.d.ts +35 -0
- package/dist/types/analytics/amplitude.d.ts +50 -0
- package/dist/types/analytics/clarity.d.ts +31 -0
- package/dist/types/analytics/clearbit.d.ts +29 -0
- package/dist/types/analytics/cloudflare.d.ts +41 -0
- package/dist/types/analytics/fathom.d.ts +41 -0
- package/dist/types/analytics/google-analytics.d.ts +44 -0
- package/dist/types/analytics/google-tag-manager.d.ts +40 -0
- package/dist/types/analytics/head.d.ts +30 -0
- package/dist/types/analytics/heap.d.ts +40 -0
- package/dist/types/analytics/hightouch.d.ts +44 -0
- package/dist/types/analytics/hotjar.d.ts +33 -0
- package/dist/types/analytics/index.d.ts +61 -0
- package/dist/types/analytics/inline.d.ts +17 -0
- package/dist/types/analytics/logrocket.d.ts +44 -0
- package/dist/types/analytics/mixpanel.d.ts +67 -0
- package/dist/types/analytics/pirsch.d.ts +42 -0
- package/dist/types/analytics/plausible.d.ts +56 -0
- package/dist/types/analytics/posthog.d.ts +45 -0
- package/dist/types/analytics/schema.d.ts +320 -0
- package/dist/types/analytics/script.d.ts +46 -0
- package/dist/types/analytics/segment.d.ts +50 -0
- package/dist/types/analytics/vercel.d.ts +48 -0
- package/dist/types/astro/integration.d.ts +76 -0
- package/dist/types/astro/markdown-negotiation.d.ts +23 -0
- package/dist/types/astro/module-types.d.ts +14 -0
- package/dist/types/astro/pages.d.ts +44 -0
- package/dist/types/cli/env.d.ts +12 -0
- package/dist/types/cli/init/scaffold.d.ts +154 -0
- package/dist/types/components/layout/nav-utils.d.ts +11 -0
- package/dist/types/core/adapter.d.ts +47 -0
- package/dist/types/core/api-name.d.ts +7 -0
- package/dist/types/core/changelog-index.d.ts +13 -0
- package/dist/types/core/config-input.d.ts +186 -515
- package/dist/types/core/config.d.ts +60 -34
- package/dist/types/core/content-assets.d.ts +76 -0
- package/dist/types/core/custom-pages.d.ts +33 -0
- package/dist/types/core/data.d.ts +27 -12
- package/dist/types/core/define-components.d.ts +12 -9
- package/dist/types/core/deployment-env.d.ts +6 -11
- package/dist/types/core/frontmatter.d.ts +10 -0
- package/dist/types/core/graph.d.ts +18 -0
- package/dist/types/core/heading-markers.d.ts +54 -0
- package/dist/types/core/i18n-ui.d.ts +6 -2
- package/dist/types/core/i18n.d.ts +87 -0
- package/dist/types/core/includes.d.ts +138 -0
- package/dist/types/core/last-modified.d.ts +47 -0
- package/dist/types/core/links.d.ts +95 -0
- package/dist/types/core/locale-links.d.ts +60 -0
- package/dist/types/core/manifest.d.ts +17 -0
- package/dist/types/core/meta.d.ts +38 -0
- package/dist/types/core/nav-diagnostics.d.ts +26 -0
- package/dist/types/core/navigation.d.ts +6 -0
- package/dist/types/core/node-require.d.ts +19 -0
- package/dist/types/core/package-json.d.ts +13 -0
- package/dist/types/core/probe.d.ts +42 -0
- package/dist/types/core/project-graph.d.ts +51 -0
- package/dist/types/core/project.d.ts +2 -0
- package/dist/types/core/safe-href.d.ts +2 -0
- package/dist/types/core/safe-links.d.ts +26 -0
- package/dist/types/core/schema.d.ts +2282 -452
- package/dist/types/core/site-url.d.ts +16 -0
- package/dist/types/core/sources/assets.d.ts +36 -0
- package/dist/types/core/sources/cache.d.ts +37 -0
- package/dist/types/core/sources/collection.d.ts +28 -0
- package/dist/types/core/sources/contentful-rich-text.d.ts +31 -0
- package/dist/types/core/sources/contentful.d.ts +40 -0
- package/dist/types/core/sources/filesystem.d.ts +24 -0
- package/dist/types/core/sources/github-releases.d.ts +31 -0
- package/dist/types/core/sources/json.d.ts +30 -0
- package/dist/types/core/sources/lexical.d.ts +19 -0
- package/dist/types/core/sources/lower.d.ts +75 -0
- package/dist/types/core/sources/mdx-remote.d.ts +28 -0
- package/dist/types/core/sources/normalize.d.ts +147 -0
- package/dist/types/core/sources/notion.d.ts +131 -0
- package/dist/types/core/sources/obsidian.d.ts +46 -0
- package/dist/types/core/sources/payload.d.ts +39 -0
- package/dist/types/core/sources/portable-text.d.ts +42 -0
- package/dist/types/core/sources/read.d.ts +23 -0
- package/dist/types/core/sources/remote.d.ts +74 -0
- package/dist/types/core/sources/resolve.d.ts +19 -0
- package/dist/types/core/sources/sanity.d.ts +43 -0
- package/dist/types/core/sources/strapi-blocks.d.ts +12 -0
- package/dist/types/core/sources/strapi.d.ts +33 -0
- package/dist/types/core/sources/types.d.ts +7 -0
- package/dist/types/core/sources/watch.d.ts +45 -0
- package/dist/types/core/text-width.d.ts +11 -0
- package/dist/types/core/types.d.ts +15 -1
- package/dist/types/core/unrecognized-keys.d.ts +7 -0
- package/dist/types/core/versions.d.ts +72 -0
- package/dist/types/core/yaml.d.ts +9 -0
- package/dist/types/deploy/adapter-output.d.ts +45 -0
- package/dist/types/deploy/adapters/cloudflare.d.ts +40 -0
- package/dist/types/deploy/adapters/index.d.ts +29 -0
- package/dist/types/deploy/adapters/netlify.d.ts +37 -0
- package/dist/types/deploy/adapters/node.d.ts +37 -0
- package/dist/types/deploy/adapters/registry.d.ts +133 -0
- package/dist/types/deploy/adapters/types.d.ts +71 -0
- package/dist/types/deploy/adapters/vercel.d.ts +38 -0
- package/dist/types/deploy/artifacts.d.ts +65 -0
- package/dist/types/deploy/cloudflare-negotiation.d.ts +196 -0
- package/dist/types/deploy/function-bundle.d.ts +80 -0
- package/dist/types/deploy/headers.d.ts +50 -0
- package/dist/types/deploy/node-headers.d.ts +42 -0
- package/dist/types/deploy/platforms/cloudflare.d.ts +40 -0
- package/dist/types/deploy/platforms/index.d.ts +18 -0
- package/dist/types/deploy/platforms/netlify.d.ts +13 -0
- package/dist/types/deploy/platforms/node.d.ts +11 -0
- package/dist/types/deploy/platforms/paths.d.ts +27 -0
- package/dist/types/deploy/platforms/static.d.ts +10 -0
- package/dist/types/deploy/platforms/types.d.ts +104 -0
- package/dist/types/deploy/platforms/vercel.d.ts +32 -0
- package/dist/types/deploy/redirects.d.ts +53 -0
- package/dist/types/deploy/robots.d.ts +8 -0
- package/dist/types/deploy/rss.d.ts +31 -0
- package/dist/types/deploy/sitemap.d.ts +21 -0
- package/dist/types/deploy/vercel-negotiation.d.ts +109 -0
- package/dist/types/markdown/code-title.d.ts +32 -0
- package/dist/types/markdown/fence-meta.d.ts +23 -0
- package/dist/types/markdown/themes.d.ts +3 -3
- package/dist/types/openapi/asyncapi.d.ts +129 -0
- package/dist/types/openapi/graphql-build.d.ts +8 -0
- package/dist/types/openapi/graphql.d.ts +122 -0
- package/dist/types/openapi/model.d.ts +158 -0
- package/dist/types/openapi/parse.d.ts +57 -0
- package/dist/types/openapi/references.d.ts +37 -25
- package/dist/types/openapi/render-mdx.d.ts +33 -0
- package/dist/types/openapi/sentence.d.ts +7 -0
- package/dist/types/openapi/signature.d.ts +10 -0
- package/dist/types/openapi/source.d.ts +22 -0
- package/dist/types/openapi/spec-dependency-error.d.ts +10 -0
- package/dist/types/reference/asyncapi.d.ts +166 -0
- package/dist/types/reference/graphql.d.ts +181 -0
- package/dist/types/reference/index.d.ts +32 -0
- package/dist/types/reference/openapi.d.ts +165 -0
- package/dist/types/reference/options.d.ts +157 -0
- package/dist/types/reference/scalar.d.ts +136 -0
- package/dist/types/reference/schema.d.ts +630 -0
- package/dist/types/search/adapters/algolia.d.ts +32 -0
- package/dist/types/search/adapters/flexsearch.d.ts +12 -0
- package/dist/types/search/adapters/index.d.ts +31 -0
- package/dist/types/search/adapters/mixedbread.d.ts +22 -0
- package/dist/types/search/adapters/orama-cloud.d.ts +33 -0
- package/dist/types/search/adapters/orama.d.ts +13 -0
- package/dist/types/search/adapters/pagefind.d.ts +12 -0
- package/dist/types/search/adapters/registry.d.ts +228 -0
- package/dist/types/search/adapters/types.d.ts +34 -0
- package/dist/types/search/adapters/typesense.d.ts +42 -0
- package/dist/types/search/build.d.ts +23 -0
- package/dist/types/search/documents.d.ts +90 -0
- package/dist/types/search/facets.d.ts +3 -0
- package/dist/types/search/sync/algolia.d.ts +14 -0
- package/dist/types/search/sync/index.d.ts +14 -0
- package/dist/types/search/sync/orama-cloud.d.ts +10 -0
- package/dist/types/search/sync/typesense.d.ts +14 -0
- package/dist/types/sources/contentful.d.ts +68 -0
- package/dist/types/sources/custom.d.ts +20 -0
- package/dist/types/sources/filesystem.d.ts +42 -0
- package/dist/types/sources/github-releases.d.ts +46 -0
- package/dist/types/sources/index.d.ts +48 -0
- package/dist/types/sources/mdx-remote.d.ts +63 -0
- package/dist/types/sources/notion.d.ts +65 -0
- package/dist/types/sources/obsidian.d.ts +34 -0
- package/dist/types/sources/payload.d.ts +68 -0
- package/dist/types/sources/registry.d.ts +1414 -0
- package/dist/types/sources/sanity.d.ts +69 -0
- package/dist/types/sources/shared.d.ts +46 -0
- package/dist/types/sources/strapi.d.ts +66 -0
- package/dist/types/theme/icon-kind.d.ts +11 -0
- package/dist/types/theme/icons.d.ts +20 -0
- package/docs/01-quickstart.mdx +18 -18
- package/docs/02-deployment.mdx +50 -26
- package/docs/03-upgrading.mdx +351 -0
- package/docs/04-migrating.mdx +58 -0
- package/docs/08-faq.mdx +3 -3
- package/docs/advanced/blog.mdx +13 -6
- package/docs/advanced/changelog.mdx +23 -35
- package/docs/advanced/custom-pages.mdx +20 -8
- package/docs/advanced/meta.ts +1 -8
- package/docs/advanced/skills.mdx +8 -0
- package/docs/cli/audit.mdx +646 -0
- package/docs/cli/doctor.mdx +29 -0
- package/docs/{reference/eval.mdx → cli/evals.mdx} +9 -8
- package/docs/cli/index.mdx +105 -0
- package/docs/cli/meta.ts +7 -0
- package/docs/{reference → cli}/translate.mdx +1 -1
- package/docs/cli/validate.mdx +41 -0
- package/docs/cli/version.mdx +40 -0
- package/docs/configuration/analytics.mdx +350 -59
- package/docs/configuration/ask-ai.mdx +162 -61
- package/docs/configuration/customization.mdx +15 -9
- package/docs/configuration/index.mdx +50 -31
- package/docs/configuration/search.mdx +75 -54
- package/docs/configuration/theming.mdx +24 -15
- package/docs/content/components.mdx +21 -5
- package/docs/{reference → content}/frontmatter.mdx +35 -1
- package/docs/content/i18n.mdx +8 -6
- package/docs/content/includes.mdx +2 -4
- package/docs/content/index.mdx +1 -1
- package/docs/content/islands.mdx +10 -5
- package/docs/content/meta.mdx +1 -1
- package/docs/content/meta.ts +1 -0
- package/docs/content/navigation.mdx +9 -7
- package/docs/content/sources.mdx +145 -50
- package/docs/content/syntax.mdx +16 -12
- package/docs/content/versioning.mdx +3 -3
- package/docs/discoverability/agent-discovery.mdx +11 -11
- package/docs/discoverability/index.mdx +4 -4
- package/docs/discoverability/json-api.mdx +4 -4
- package/docs/discoverability/llms-txt.mdx +6 -6
- package/docs/discoverability/markdown.mdx +4 -4
- package/docs/discoverability/mcp.mdx +11 -11
- package/docs/discoverability/sitemap-and-robots.mdx +3 -3
- package/docs/index.mdx +2 -2
- package/docs/references/asyncapi.mdx +59 -0
- package/docs/{advanced → references}/graphql.mdx +45 -32
- package/docs/{reference → references}/meta.ts +2 -2
- package/docs/references/openapi.mdx +171 -0
- package/docs/references/scalar.mdx +64 -0
- package/package.json +42 -8
- package/skills/blume/SKILL.md +21 -8
- package/skills/blume-migrate/SKILL.md +22 -21
- package/skills/blume-migrate/assets/oxfmt@0.67.0.patch +49 -0
- package/skills/blume-migrate/references/docusaurus.md +5 -4
- package/skills/blume-migrate/references/fumadocs.md +4 -4
- package/skills/blume-migrate/references/mintlify.md +23 -8
- package/skills/blume-migrate/references/monorepo.md +6 -6
- package/skills/blume-migrate/references/starlight.md +4 -4
- package/src/ai/agent-readability.ts +21 -22
- package/src/ai/ai-catalog.ts +50 -32
- package/src/ai/api/handlers.ts +46 -11
- package/src/ai/api-catalog.ts +8 -8
- package/src/ai/ask-data.ts +1 -1
- package/src/ai/ask.ts +624 -100
- package/src/ai/changelog-markdown.ts +91 -0
- package/src/ai/component-markdown.ts +328 -12
- package/src/ai/index.ts +45 -0
- package/src/ai/link-headers.ts +7 -6
- package/src/ai/llms.ts +20 -15
- package/src/ai/markdown.ts +35 -5
- package/src/ai/mcp/data.ts +9 -5
- package/src/ai/openapi-components.ts +4 -1
- package/src/ai/relative-links.ts +170 -0
- package/src/ai/serializers.ts +2 -2
- package/src/ai/skills.ts +1 -1
- package/src/ai/web-bot-auth.ts +2 -2
- package/src/analytics/adobe.ts +46 -0
- package/src/analytics/amplitude.ts +79 -0
- package/src/analytics/clarity.ts +46 -0
- package/src/analytics/clearbit.ts +47 -0
- package/src/analytics/cloudflare.ts +67 -0
- package/src/analytics/fathom.ts +64 -0
- package/src/analytics/google-analytics.ts +84 -0
- package/src/analytics/google-tag-manager.ts +61 -0
- package/src/analytics/head.ts +130 -0
- package/src/analytics/heap.ts +65 -0
- package/src/analytics/hightouch.ts +77 -0
- package/src/analytics/hotjar.ts +47 -0
- package/src/analytics/index.ts +71 -0
- package/src/analytics/inline.ts +21 -0
- package/src/analytics/logrocket.ts +71 -0
- package/src/analytics/mixpanel.ts +92 -0
- package/src/analytics/pirsch.ts +66 -0
- package/src/analytics/plausible.ts +83 -0
- package/src/analytics/posthog.ts +78 -0
- package/src/analytics/schema.ts +60 -0
- package/src/analytics/script.ts +60 -0
- package/src/analytics/segment.ts +85 -0
- package/src/analytics/vercel.ts +50 -0
- package/src/astro/adapter-root.ts +7 -9
- package/src/astro/component-slots.ts +131 -91
- package/src/astro/generate.ts +112 -345
- package/src/astro/integration.ts +2 -6
- package/src/astro/pages.ts +13 -101
- package/src/astro/render-deps.ts +379 -0
- package/src/astro/runtime-deps.ts +201 -0
- package/src/astro/templates.ts +554 -541
- package/src/audit/agent.ts +24 -0
- package/src/audit/catalog.ts +2 -2
- package/src/audit/checks/assets.ts +2 -2
- package/src/audit/checks/dns-aid.ts +1 -1
- package/src/audit/checks/duplicates.ts +3 -1
- package/src/audit/checks/i18n.ts +1 -1
- package/src/audit/checks/indexability.ts +7 -7
- package/src/audit/checks/links.ts +2 -2
- package/src/audit/checks/llms.ts +6 -6
- package/src/audit/checks/network.ts +3 -3
- package/src/audit/checks/og-image.ts +2 -2
- package/src/audit/checks/robots.ts +1 -1
- package/src/audit/checks/sitemap.ts +2 -2
- package/src/audit/checks/social.ts +1 -1
- package/src/audit/run.ts +4 -3
- package/src/audit/terms.ts +31 -0
- package/src/audit/url.ts +13 -13
- package/src/cli/command-meta.ts +10 -0
- package/src/cli/commands/audit.ts +39 -20
- package/src/cli/commands/build.ts +71 -316
- package/src/cli/commands/check.ts +1 -0
- package/src/cli/commands/dev.ts +40 -1
- package/src/cli/commands/doctor.ts +90 -14
- package/src/cli/commands/eject.ts +44 -7
- package/src/cli/commands/eval.ts +2 -2
- package/src/cli/commands/init.ts +161 -41
- package/src/cli/commands/migrate.ts +121 -0
- package/src/cli/commands/preview.ts +7 -1
- package/src/cli/commands/translate.ts +2 -2
- package/src/cli/commands/upgrade.ts +141 -0
- package/src/cli/commands/version.ts +57 -44
- package/src/cli/eject-scripts.ts +121 -6
- package/src/cli/index.ts +11 -1
- package/src/cli/init/install.ts +70 -0
- package/src/cli/init/questions.ts +7 -0
- package/src/cli/init/scaffold.ts +459 -75
- package/src/cli/lazy-command.ts +37 -1
- package/src/cli/prepare.ts +40 -6
- package/src/cli/required-secrets.ts +38 -11
- package/src/cli/unknown-flags.ts +266 -0
- package/src/cli/yarn-pnp.ts +52 -0
- package/src/components/content/AccordionItem.astro +7 -1
- package/src/components/content/Card.astro +4 -3
- package/src/components/content/ColorItem.astro +22 -4
- package/src/components/content/GithubInfo.astro +2 -2
- package/src/components/content/Prompt.astro +25 -25
- package/src/components/content/Tabs.astro +3 -1
- package/src/components/content/Tile.astro +2 -3
- package/src/components/content/Tooltip.astro +57 -9
- package/src/components/content/content-strings.ts +34 -0
- package/src/components/content/diff.ts +1 -1
- package/src/components/content/mermaid-element.ts +13 -2
- package/src/components/content/prompt-markdown.ts +292 -0
- package/src/components/content/tooltip-id.ts +41 -0
- package/src/components/islands/hooks.ts +25 -2
- package/src/components/layout/Analytics.astro +21 -79
- package/src/components/layout/Banner.astro +3 -1
- package/src/components/layout/DiscoveryLinks.astro +69 -0
- package/src/components/layout/Header.astro +83 -20
- package/src/components/layout/LanguageSwitcher.astro +4 -1
- package/src/components/layout/Logo.astro +23 -2
- package/src/components/layout/NavSelector.astro +13 -2
- package/src/components/layout/NavTree.astro +12 -3
- package/src/components/layout/NavTreeScript.astro +17 -3
- package/src/components/layout/PageActions.astro +3 -3
- package/src/components/layout/PageFeedback.astro +11 -1
- package/src/components/layout/PageLayout.astro +52 -5
- package/src/components/layout/ReferenceLayout.astro +21 -12
- package/src/components/layout/RootLayout.astro +69 -66
- package/src/components/layout/Search.astro +30 -6
- package/src/components/layout/WebMcp.astro +1 -1
- package/src/components/layout/analytics-client.ts +72 -15
- package/src/components/layout/drawer-inert.ts +113 -15
- package/src/components/layout/dropdown-clamp.ts +105 -0
- package/src/components/layout/head-scripts.ts +15 -6
- package/src/components/layout/nav-utils.ts +17 -0
- package/src/components/layout/search/algolia.ts +8 -8
- package/src/components/layout/search/orama-cloud.ts +9 -7
- package/src/components/layout/search/typesense.ts +11 -17
- package/src/components/openapi/MessageComposer.astro +2 -2
- package/src/components/openapi/Playground.astro +2 -2
- package/src/components/openapi/playground-client.ts +79 -10
- package/src/core/changelog-index.ts +24 -0
- package/src/core/component-overrides.ts +399 -154
- package/src/core/config-input.ts +188 -553
- package/src/core/config.ts +130 -41
- package/src/core/custom-pages.ts +105 -0
- package/src/core/data.ts +31 -12
- package/src/core/define-components.ts +12 -9
- package/src/core/deployment-env.ts +18 -74
- package/src/core/diagnostics.ts +14 -7
- package/src/core/graph.ts +69 -25
- package/src/core/i18n-ui.ts +6 -2
- package/src/core/i18n.ts +17 -0
- package/src/core/includes.ts +156 -38
- package/src/core/last-modified.ts +6 -11
- package/src/core/links.ts +166 -2
- package/src/core/manifest.ts +10 -3
- package/src/core/navigation.ts +115 -16
- package/src/core/new-tab.ts +35 -0
- package/src/core/node-require.ts +21 -0
- package/src/core/project-graph.ts +42 -26
- package/src/core/project.ts +9 -4
- package/src/core/request-body.ts +61 -0
- package/src/core/safe-href.ts +28 -0
- package/src/core/safe-links.ts +68 -0
- package/src/core/schema.ts +517 -739
- package/src/core/server-features.ts +8 -9
- package/src/core/sources/assets.ts +47 -11
- package/src/core/sources/collection.ts +67 -0
- package/src/core/sources/contentful-rich-text.ts +285 -0
- package/src/core/sources/contentful.ts +173 -0
- package/src/core/sources/github-releases.ts +51 -1
- package/src/core/sources/json.ts +71 -0
- package/src/core/sources/lexical.ts +195 -0
- package/src/core/sources/lower.ts +226 -0
- package/src/core/sources/normalize.ts +52 -2
- package/src/core/sources/notion.ts +39 -28
- package/src/core/sources/payload.ts +135 -0
- package/src/core/sources/portable-text.ts +11 -16
- package/src/core/sources/remote.ts +226 -0
- package/src/core/sources/resolve.ts +104 -166
- package/src/core/sources/sanity.ts +16 -49
- package/src/core/sources/strapi-blocks.ts +124 -0
- package/src/core/sources/strapi.ts +191 -0
- package/src/core/sources/types.ts +12 -1
- package/src/core/types.ts +15 -1
- package/src/core/ui-packs/ar.ts +6 -2
- package/src/core/ui-packs/bg.ts +6 -2
- package/src/core/ui-packs/bn.ts +6 -2
- package/src/core/ui-packs/ca.ts +3 -1
- package/src/core/ui-packs/cs.ts +6 -2
- package/src/core/ui-packs/da.ts +6 -2
- package/src/core/ui-packs/de.ts +6 -2
- package/src/core/ui-packs/el.ts +3 -1
- package/src/core/ui-packs/es.ts +3 -1
- package/src/core/ui-packs/fa.ts +6 -2
- package/src/core/ui-packs/fi.ts +6 -2
- package/src/core/ui-packs/fr.ts +3 -1
- package/src/core/ui-packs/he.ts +6 -2
- package/src/core/ui-packs/hi.ts +6 -2
- package/src/core/ui-packs/hr.ts +6 -2
- package/src/core/ui-packs/hu.ts +6 -2
- package/src/core/ui-packs/id.ts +6 -2
- package/src/core/ui-packs/it.ts +3 -1
- package/src/core/ui-packs/ja.ts +3 -1
- package/src/core/ui-packs/ko.ts +3 -1
- package/src/core/ui-packs/nl.ts +6 -2
- package/src/core/ui-packs/no.ts +6 -2
- package/src/core/ui-packs/pl.ts +6 -2
- package/src/core/ui-packs/pt-br.ts +3 -1
- package/src/core/ui-packs/pt.ts +3 -1
- package/src/core/ui-packs/ro.ts +6 -2
- package/src/core/ui-packs/ru.ts +6 -2
- package/src/core/ui-packs/sk.ts +6 -2
- package/src/core/ui-packs/sr.ts +6 -2
- package/src/core/ui-packs/sv.ts +6 -2
- package/src/core/ui-packs/th.ts +3 -1
- package/src/core/ui-packs/tr.ts +6 -2
- package/src/core/ui-packs/uk.ts +6 -2
- package/src/core/ui-packs/vi.ts +3 -1
- package/src/core/ui-packs/zh-tw.ts +3 -1
- package/src/core/ui-packs/zh.ts +3 -1
- package/src/core/unrecognized-keys.ts +10 -0
- package/src/core/version-cut.ts +116 -8
- package/src/deploy/adapter-output.ts +57 -97
- package/src/deploy/adapters/cloudflare.ts +39 -0
- package/src/deploy/adapters/index.ts +41 -0
- package/src/deploy/adapters/netlify.ts +34 -0
- package/src/deploy/adapters/node.ts +34 -0
- package/src/deploy/adapters/registry.ts +127 -0
- package/src/deploy/adapters/types.ts +92 -0
- package/src/deploy/adapters/vercel.ts +35 -0
- package/src/deploy/artifacts.ts +55 -27
- package/src/deploy/cloudflare-negotiation.ts +179 -100
- package/src/deploy/function-bundle.ts +18 -3
- package/src/deploy/headers.ts +124 -23
- package/src/deploy/node-headers.ts +198 -0
- package/src/deploy/platforms/cloudflare.ts +312 -0
- package/src/deploy/platforms/index.ts +67 -0
- package/src/deploy/platforms/netlify.ts +52 -0
- package/src/deploy/platforms/node.ts +40 -0
- package/src/deploy/platforms/paths.ts +42 -0
- package/src/deploy/platforms/static.ts +29 -0
- package/src/deploy/platforms/types.ts +112 -0
- package/src/deploy/platforms/vercel.ts +193 -0
- package/src/deploy/redirects.ts +30 -18
- package/src/deploy/robots.ts +3 -3
- package/src/deploy/rss.ts +2 -2
- package/src/deploy/sitemap.ts +2 -2
- package/src/deploy/vercel-negotiation.ts +2 -2
- package/src/eval/agents.ts +32 -1
- package/src/eval/findings.ts +19 -11
- package/src/eval/report.ts +10 -2
- package/src/markdown/external-links.ts +65 -0
- package/src/markdown/include.ts +45 -27
- package/src/markdown/index.ts +34 -9
- package/src/markdown/inline-code.ts +12 -6
- package/src/markdown/relative-links.ts +324 -0
- package/src/markdown/themes.ts +3 -3
- package/src/migrate/migrate.ts +149 -0
- package/src/openapi/parse.ts +64 -16
- package/src/openapi/proxy.ts +62 -10
- package/src/openapi/references.ts +147 -147
- package/src/openapi/render-mdx.ts +32 -10
- package/src/openapi/scalar.ts +15 -13
- package/src/openapi/sentence.ts +14 -0
- package/src/openapi/source.ts +37 -21
- package/src/openapi/spec-dependency-error.ts +15 -0
- package/src/reference/asyncapi.ts +83 -0
- package/src/reference/graphql.ts +89 -0
- package/src/reference/index.ts +40 -0
- package/src/reference/openapi.ts +83 -0
- package/src/reference/options.ts +201 -0
- package/src/reference/scalar.ts +136 -0
- package/src/reference/schema.ts +88 -0
- package/src/registry/eject.ts +97 -42
- package/src/search/adapters/algolia.ts +46 -0
- package/src/search/adapters/flexsearch.ts +29 -0
- package/src/search/adapters/index.ts +39 -0
- package/src/search/adapters/mixedbread.ts +39 -0
- package/src/search/adapters/orama-cloud.ts +51 -0
- package/src/search/adapters/orama.ts +24 -0
- package/src/search/adapters/pagefind.ts +27 -0
- package/src/search/adapters/registry.ts +131 -0
- package/src/search/adapters/types.ts +46 -0
- package/src/search/adapters/typesense.ts +57 -0
- package/src/search/build.ts +8 -4
- package/src/search/documents.ts +6 -0
- package/src/search/sync/algolia.ts +12 -11
- package/src/search/sync/index.ts +35 -20
- package/src/search/sync/orama-cloud.ts +12 -9
- package/src/search/sync/typesense.ts +15 -13
- package/src/sources/contentful.ts +63 -0
- package/src/sources/custom.ts +38 -0
- package/src/sources/filesystem.ts +51 -0
- package/src/sources/github-releases.ts +52 -0
- package/src/sources/index.ts +60 -0
- package/src/sources/mdx-remote.ts +76 -0
- package/src/sources/notion.ts +63 -0
- package/src/sources/obsidian.ts +38 -0
- package/src/sources/payload.ts +60 -0
- package/src/sources/registry.ts +182 -0
- package/src/sources/sanity.ts +66 -0
- package/src/sources/shared.ts +52 -0
- package/src/sources/strapi.ts +58 -0
- package/src/theme/entry.ts +24 -2
- package/src/translate/report.ts +40 -7
- package/src/upgrade/upgrade.ts +499 -0
- package/dist/cli/chunk-0qymqwzz.js +0 -164
- package/dist/cli/chunk-0qymqwzz.js.map +0 -15
- package/dist/cli/chunk-1jefwnfs.js.map +0 -48
- package/dist/cli/chunk-2mzebbbz.js +0 -69
- package/dist/cli/chunk-2z47ypj8.js.map +0 -11
- package/dist/cli/chunk-4x36ddpw.js.map +0 -11
- package/dist/cli/chunk-5093q3n7.js +0 -68
- package/dist/cli/chunk-5093q3n7.js.map +0 -10
- package/dist/cli/chunk-5qk08vmp.js.map +0 -11
- package/dist/cli/chunk-7s8hm3b6.js +0 -5347
- package/dist/cli/chunk-7s8hm3b6.js.map +0 -58
- package/dist/cli/chunk-8cjtbafj.js.map +0 -13
- package/dist/cli/chunk-97r59kpr.js +0 -381
- package/dist/cli/chunk-97r59kpr.js.map +0 -12
- package/dist/cli/chunk-ahnw3kxw.js.map +0 -15
- package/dist/cli/chunk-b27xqwn9.js.map +0 -10
- package/dist/cli/chunk-bf6bt1xt.js +0 -185
- package/dist/cli/chunk-bf6bt1xt.js.map +0 -11
- package/dist/cli/chunk-cjtn640a.js.map +0 -10
- package/dist/cli/chunk-ct47dqpx.js.map +0 -11
- package/dist/cli/chunk-esphfr8p.js +0 -107
- package/dist/cli/chunk-esphfr8p.js.map +0 -11
- package/dist/cli/chunk-ex56aa81.js +0 -1016
- package/dist/cli/chunk-ex56aa81.js.map +0 -13
- package/dist/cli/chunk-garjf5z9.js +0 -30
- package/dist/cli/chunk-garjf5z9.js.map +0 -10
- package/dist/cli/chunk-js7saxwm.js +0 -1045
- package/dist/cli/chunk-js7saxwm.js.map +0 -22
- package/dist/cli/chunk-k79xp7av.js.map +0 -39
- package/dist/cli/chunk-ps4m1xh4.js.map +0 -15
- package/dist/cli/chunk-q4rae3bg.js +0 -60
- package/dist/cli/chunk-q4rae3bg.js.map +0 -10
- package/dist/cli/chunk-rz9jmfhz.js +0 -108
- package/dist/cli/chunk-rz9jmfhz.js.map +0 -10
- package/dist/cli/chunk-vh9w1sgp.js +0 -73
- package/dist/cli/chunk-vh9w1sgp.js.map +0 -10
- package/dist/cli/chunk-vrfp10qk.js +0 -81
- package/dist/cli/chunk-vrfp10qk.js.map +0 -10
- package/dist/cli/chunk-yg63d42r.js.map +0 -34
- package/docs/advanced/api-reference.mdx +0 -240
- package/docs/reference/cli.mdx +0 -197
- package/skills/blume-migrate/assets/oxfmt@0.55.0.patch +0 -20
- package/src/components/content/changelog-element.ts +0 -69
- package/src/search/providers.ts +0 -91
- /package/dist/cli/{chunk-e7f42gdj.js.map → chunk-7ez8ny0t.js.map} +0 -0
- /package/dist/cli/{chunk-nn13znc2.js.map → chunk-88cpgt6h.js.map} +0 -0
- /package/dist/cli/{chunk-vacwm2hv.js.map → chunk-f7t03s3g.js.map} +0 -0
- /package/dist/cli/{chunk-dwgcp5sm.js.map → chunk-zxccj738.js.map} +0 -0
package/src/astro/templates.ts
CHANGED
|
@@ -4,24 +4,28 @@ 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 { askBackendRuntimeDep } from "../ai/ask.ts";
|
|
8
7
|
import type { AskBackend } from "../ai/ask.ts";
|
|
9
8
|
import { buildHomeLinkHeader } from "../ai/link-headers.ts";
|
|
10
9
|
import { normalizeBasePath } from "../core/base-path.ts";
|
|
11
10
|
import { TOC_HIDDEN_KEY } from "../core/heading-markers.ts";
|
|
12
|
-
import type {
|
|
11
|
+
import type { ResolvedConfig } from "../core/schema.ts";
|
|
12
|
+
import { resolveDocsCollection } from "../core/sources/collection.ts";
|
|
13
13
|
import { BLUME_IGNORE_DIRS } from "../core/sources/watch.ts";
|
|
14
14
|
import { trimChar } from "../core/trim.ts";
|
|
15
15
|
import type { ProjectContext } from "../core/types.ts";
|
|
16
|
+
import { deployPassthrough } from "../deploy/adapters/types.ts";
|
|
17
|
+
import { SVG_ASSET_POLICY } from "../deploy/headers.ts";
|
|
18
|
+
import { deployPlatform } from "../deploy/platforms/index.ts";
|
|
19
|
+
import { adapterRoot, distDir } from "../deploy/platforms/paths.ts";
|
|
16
20
|
import { applyBaseToAstroRedirects } from "../deploy/redirects.ts";
|
|
17
21
|
import type { OgCache } from "../og/cache.ts";
|
|
18
22
|
import type { OgFont, OgFontFamilies } from "../og/card.ts";
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
23
|
+
import type { MixedbreadOptions } from "../search/adapters/mixedbread.ts";
|
|
24
|
+
import type { ResolvedSearchAdapter } from "../search/adapters/registry.ts";
|
|
21
25
|
import { buildFontEntries, fontLocaleCodes } from "../theme/fonts.ts";
|
|
26
|
+
import { importSpecifier, wrapperPropsType } from "./component-slots.ts";
|
|
22
27
|
import type { ExampleSpec } from "./examples.ts";
|
|
23
28
|
import type { BlumeIntegrationOptions, BlumePageRoute } from "./integration.ts";
|
|
24
|
-
import type { IslandSpec } from "./islands.ts";
|
|
25
29
|
import type { OgCustomRoute } from "./pages.ts";
|
|
26
30
|
import { RUNTIME_MODULE_FILES } from "./runtime-modules.ts";
|
|
27
31
|
|
|
@@ -77,67 +81,54 @@ const findWorkspaceRoot = (start: string): string => {
|
|
|
77
81
|
}
|
|
78
82
|
};
|
|
79
83
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
"wrangler.json",
|
|
97
|
-
"wrangler.toml",
|
|
98
|
-
];
|
|
84
|
+
/**
|
|
85
|
+
* The `adapter:` entry of the generated config plus the import that backs it,
|
|
86
|
+
* for a server build. The platform (see `deploy/platforms/*`) names the
|
|
87
|
+
* `@astrojs/*` package and Blume's own constructor options; the descriptor's
|
|
88
|
+
* passthrough options are spread over those, verbatim, so anything the
|
|
89
|
+
* adapter was given reaches the real adapter. A platform that resolves its
|
|
90
|
+
* output against Astro's root is handed the project root instead of the
|
|
91
|
+
* hidden runtime (`withAdapterRoot`).
|
|
92
|
+
*/
|
|
93
|
+
interface AstroAdapterRender {
|
|
94
|
+
/** Extra top-level `defineConfig` entries the adapter needs. */
|
|
95
|
+
configEntries: string;
|
|
96
|
+
importLine: string;
|
|
97
|
+
/** The `adapter:` entry, or empty for a static build. */
|
|
98
|
+
option: string;
|
|
99
|
+
}
|
|
99
100
|
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
];
|
|
109
|
-
const wranglerPath = WRANGLER_CONFIG_FILES.map((file) =>
|
|
110
|
-
join(context.root, file)
|
|
111
|
-
).find((file) => existsSync(file));
|
|
112
|
-
if (wranglerPath) {
|
|
113
|
-
let configPath = relative(context.outDir, wranglerPath);
|
|
114
|
-
// The wrangler config always lives at the project root, above the `.blume`
|
|
115
|
-
// runtime, so `relative` yields a `../…` path; normalize the theoretical
|
|
116
|
-
// sibling case to an explicit `./` so it reads as a relative import.
|
|
117
|
-
if (!configPath.startsWith(".") && !configPath.startsWith("/")) {
|
|
118
|
-
configPath = `./${configPath}`;
|
|
119
|
-
}
|
|
120
|
-
args.push(`configPath: ${JSON.stringify(configPath)}`);
|
|
101
|
+
const renderAstroAdapter = (
|
|
102
|
+
deployment: ResolvedConfig["deployment"],
|
|
103
|
+
context: ProjectContext,
|
|
104
|
+
ejected: boolean
|
|
105
|
+
): AstroAdapterRender => {
|
|
106
|
+
const platform = deployPlatform(deployment);
|
|
107
|
+
if (deployment.options.output !== "server" || !platform.astro) {
|
|
108
|
+
return { configEntries: "", importLine: "", option: "" };
|
|
121
109
|
}
|
|
122
|
-
|
|
110
|
+
const { astro } = platform;
|
|
111
|
+
const args = {
|
|
112
|
+
...astro.options(context),
|
|
113
|
+
...deployPassthrough(deployment.options),
|
|
114
|
+
};
|
|
115
|
+
const argsLiteral = Object.keys(args).length > 0 ? JSON.stringify(args) : "";
|
|
116
|
+
const construct = `adapter(${argsLiteral})`;
|
|
117
|
+
// An ejected app's Astro root is the project root already, so its adapter
|
|
118
|
+
// needs no redirect (and the config no machine-specific path).
|
|
119
|
+
const expression =
|
|
120
|
+
platform.hiddenRuntime.showProjectRoot && !ejected
|
|
121
|
+
? `withAdapterRoot(${construct}, ${JSON.stringify(adapterRoot(context))})`
|
|
122
|
+
: construct;
|
|
123
|
+
return {
|
|
124
|
+
configEntries: Object.entries(astro.config)
|
|
125
|
+
.map(([key, value]) => `\n ${key}: ${JSON.stringify(value)},`)
|
|
126
|
+
.join(""),
|
|
127
|
+
importLine: `import adapter from "${astro.package}";\n`,
|
|
128
|
+
option: `\n adapter: ${expression},`,
|
|
129
|
+
};
|
|
123
130
|
};
|
|
124
131
|
|
|
125
|
-
/**
|
|
126
|
-
* Without a configured driver, `@astrojs/cloudflare` force-enables KV-backed
|
|
127
|
-
* sessions and declares a `SESSION` kv_namespaces entry in the generated
|
|
128
|
-
* wrangler config — which `wrangler deploy` then requires a real KV namespace
|
|
129
|
-
* for, even though Blume never reads `Astro.session`. Astro's `session: false`
|
|
130
|
-
* opts the project out, and the adapter checks for it before adding the
|
|
131
|
-
* binding. (An in-memory driver used to stand in before the opt-out existed.)
|
|
132
|
-
*/
|
|
133
|
-
const resolveSessionOption = (deployment: {
|
|
134
|
-
adapter: string | null;
|
|
135
|
-
output: string;
|
|
136
|
-
}): string =>
|
|
137
|
-
deployment.output === "server" && deployment.adapter === "cloudflare"
|
|
138
|
-
? "\n session: false,"
|
|
139
|
-
: "";
|
|
140
|
-
|
|
141
132
|
/**
|
|
142
133
|
* A font weight as Astro's Fonts API spells it: a variable range is
|
|
143
134
|
* `"100 900"` there, where Blume's config (and Takumi's Google Fonts helper,
|
|
@@ -183,29 +174,36 @@ export const runtimeDependencies = (options: {
|
|
|
183
174
|
if (needsSvelte) {
|
|
184
175
|
deps.push("@astrojs/svelte");
|
|
185
176
|
}
|
|
186
|
-
//
|
|
187
|
-
//
|
|
188
|
-
//
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
//
|
|
193
|
-
//
|
|
194
|
-
deps.push(
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
177
|
+
// Each reference adapter declares what it needs: Blume's own renderer
|
|
178
|
+
// parses at generate time and needs nothing, while `scalar()` declares
|
|
179
|
+
// `@scalar/astro` so the framework crawl bundles the embed (two Scalar
|
|
180
|
+
// adapters declare it twice, hence the set). Only the configured
|
|
181
|
+
// search adapter's SDK is declared, so a project pulls in (and the user
|
|
182
|
+
// installs) exactly the backend it uses — nothing more. Each analytics
|
|
183
|
+
// adapter declares what it needs the same way; the built-ins need nothing
|
|
184
|
+
// beyond Blume's own deps, so their share is usually empty.
|
|
185
|
+
deps.push(
|
|
186
|
+
...new Set(config.reference.flatMap((adapter) => adapter.runtimeDeps)),
|
|
187
|
+
...config.search.provider.runtimeDeps,
|
|
188
|
+
...config.analytics.flatMap((adapter) => adapter.runtimeDeps)
|
|
189
|
+
);
|
|
190
|
+
// Each content source adapter declares the SDK its fetch imports (Notion,
|
|
191
|
+
// Sanity); the descriptor is the one place that knows.
|
|
192
|
+
for (const source of config.content.sources) {
|
|
193
|
+
for (const dep of source.runtimeDeps) {
|
|
194
|
+
if (!deps.includes(dep)) {
|
|
195
|
+
deps.push(dep);
|
|
196
|
+
}
|
|
200
197
|
}
|
|
201
198
|
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
deps.push(adapter);
|
|
207
|
-
}
|
|
199
|
+
// Ask AI's provider SDK, as its adapter declares it (the gateway needs
|
|
200
|
+
// nothing beyond core `ai`, so it declares none).
|
|
201
|
+
if (config.ai.ask?.enabled && !config.ai.ask.endpoint) {
|
|
202
|
+
deps.push(...config.ai.ask.provider.runtimeDeps);
|
|
208
203
|
}
|
|
204
|
+
// The deployment adapter's `@astrojs/*` package, for a server build; the
|
|
205
|
+
// descriptor declares it (and nothing for a static build).
|
|
206
|
+
deps.push(...config.deployment.runtimeDeps);
|
|
209
207
|
return deps;
|
|
210
208
|
};
|
|
211
209
|
|
|
@@ -271,25 +269,6 @@ const renderUserAliases = (
|
|
|
271
269
|
)
|
|
272
270
|
.join("");
|
|
273
271
|
|
|
274
|
-
/** Astro's build output dir: the runtime's own `distDir`, else `<root>/dist`. */
|
|
275
|
-
const astroOutDir = (context: ProjectContext): string =>
|
|
276
|
-
context.distDir ?? `${context.root}/dist`;
|
|
277
|
-
|
|
278
|
-
/**
|
|
279
|
-
* The root a deploy adapter is shown, in place of the `.blume` runtime Astro
|
|
280
|
-
* actually roots at. Adapters assume `outDir` is `<root>/dist` and resolve their
|
|
281
|
-
* own output (and Vercel's dependency trace) against `root`, so the root implied
|
|
282
|
-
* by Blume's `outDir` is the one that keeps that assumption true. See
|
|
283
|
-
* {@link withAdapterRoot}.
|
|
284
|
-
*
|
|
285
|
-
* For a normal build that is the project root (`<project>/dist` -> `<project>`).
|
|
286
|
-
* For a relocated runtime (`blume build --isolated`) it is the runtime dir
|
|
287
|
-
* itself (`<runtime>/dist` -> `<runtime>`), keeping a verify build's adapter
|
|
288
|
-
* output self-contained instead of overwriting the real `.vercel/output`.
|
|
289
|
-
*/
|
|
290
|
-
const adapterRoot = (context: ProjectContext): string =>
|
|
291
|
-
dirname(astroOutDir(context));
|
|
292
|
-
|
|
293
272
|
/**
|
|
294
273
|
* Excludes Vite's pre-bundled dep cache from @vitejs/plugin-react. Astro's
|
|
295
274
|
* react() replaces the plugin's default `/node_modules/` exclude with just
|
|
@@ -451,6 +430,59 @@ const resolveOptimizeDeps = (options: {
|
|
|
451
430
|
return { optimizeDepsEntries, optimizeDepsInclude };
|
|
452
431
|
};
|
|
453
432
|
|
|
433
|
+
/**
|
|
434
|
+
* A path the generated config hands Vite or a plugin. The ejected config
|
|
435
|
+
* resolves it against the config file itself when it loads, so the project
|
|
436
|
+
* builds from any checkout and working directory, and Vite gets the absolute
|
|
437
|
+
* alias target it expects (it warns on every relative one). The hidden
|
|
438
|
+
* runtime's paths are absolute already.
|
|
439
|
+
*/
|
|
440
|
+
const configPath = (path: string, ejected: boolean): string =>
|
|
441
|
+
ejected
|
|
442
|
+
? `fileURLToPath(new URL(${JSON.stringify(path)}, import.meta.url))`
|
|
443
|
+
: JSON.stringify(path);
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* The first line of an ejected `astro.config.mjs`, which also marks the
|
|
447
|
+
* project as ejected for the CLI (`blume dev`/`build` step aside, and a second
|
|
448
|
+
* `blume eject` refuses to overwrite the app).
|
|
449
|
+
*/
|
|
450
|
+
export const EJECTED_CONFIG_HEADER =
|
|
451
|
+
"// Written by `blume eject`. This is your Astro config now: edit it freely.";
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* The generated config's first line. The hidden runtime's config is rewritten
|
|
455
|
+
* on every run; an ejected one belongs to the project from then on.
|
|
456
|
+
*/
|
|
457
|
+
const astroConfigHeader = (ejected: boolean): string =>
|
|
458
|
+
ejected
|
|
459
|
+
? EJECTED_CONFIG_HEADER
|
|
460
|
+
: "// Generated by Blume. Do not edit; this file is recreated on each run.";
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* The client build's chunk-size warning threshold (kB) on a site with Mermaid
|
|
464
|
+
* diagrams. Mermaid's ELK layout engine (about 1.5 MB) and its core (about
|
|
465
|
+
* 650 kB) land in their own chunks, which the `blume:features` loader fetches
|
|
466
|
+
* only on a page with a diagram — so Vite's default 500 kB warning fired on
|
|
467
|
+
* every such build without anything to fix. A chunk past this still warns.
|
|
468
|
+
*/
|
|
469
|
+
const MERMAID_CHUNK_LIMIT = "\n chunkSizeWarningLimit: 2048,";
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Rolldown's log hook for the build. Astro's content-assets plugin opens each
|
|
473
|
+
* page's `?astroPropagatedAssets` module with a `"use astro:head-inject"`
|
|
474
|
+
* directive that nothing reads once bundled (head propagation rides on the
|
|
475
|
+
* module's `__astroPropagation` export), and Rolldown 1.2.10+ warns about it
|
|
476
|
+
* in nine lines per page, on every build. That one warning is dropped; every
|
|
477
|
+
* other log goes to Vite's default handler.
|
|
478
|
+
*/
|
|
479
|
+
const ROLLDOWN_ON_LOG = `onLog(level, log, handler) {
|
|
480
|
+
if (log.code === "MODULE_LEVEL_DIRECTIVE" && String(log.message).includes("use astro:head-inject")) {
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
handler(level, log);
|
|
484
|
+
}`;
|
|
485
|
+
|
|
454
486
|
/**
|
|
455
487
|
* How the generated config reaches the runtime data modules (`blume:data`,
|
|
456
488
|
* the search index, …): served from memory by `runtimeModulesPlugin` in the
|
|
@@ -479,7 +511,7 @@ const renderRuntimeModuleWiring = (
|
|
|
479
511
|
const aliasLines = [...RUNTIME_MODULE_FILES]
|
|
480
512
|
.map(
|
|
481
513
|
([id, file]) =>
|
|
482
|
-
`\n ${JSON.stringify(id)}: ${
|
|
514
|
+
`\n ${JSON.stringify(id)}: ${configPath(`${generatedModulesDir}/${file}`, true)},`
|
|
483
515
|
)
|
|
484
516
|
.join("");
|
|
485
517
|
return { aliasLines, imports: [], pluginEntry: "" };
|
|
@@ -572,6 +604,7 @@ export const astroConfigTemplate = (options: {
|
|
|
572
604
|
needsVue,
|
|
573
605
|
searchClientPath,
|
|
574
606
|
} = options;
|
|
607
|
+
const ejected = generatedModulesDir !== undefined;
|
|
575
608
|
const {
|
|
576
609
|
aliasLines: runtimeModuleAliasLines,
|
|
577
610
|
imports: runtimeModuleImports,
|
|
@@ -579,7 +612,6 @@ export const astroConfigTemplate = (options: {
|
|
|
579
612
|
} = renderRuntimeModuleWiring(generatedModulesDir);
|
|
580
613
|
const { deployment } = config;
|
|
581
614
|
const userAliasLines = renderUserAliases(options.aliases);
|
|
582
|
-
const server = deployment.output === "server";
|
|
583
615
|
|
|
584
616
|
// The project root plus the workspace root, so hoisted dependencies (e.g.
|
|
585
617
|
// KaTeX fonts under a monorepo's root node_modules) stay servable in dev.
|
|
@@ -593,38 +625,17 @@ export const astroConfigTemplate = (options: {
|
|
|
593
625
|
reactCompilerPath: options.reactCompilerPath,
|
|
594
626
|
});
|
|
595
627
|
|
|
596
|
-
const
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
}
|
|
604
|
-
if (deployment.adapter === "cloudflare") {
|
|
605
|
-
return resolveCloudflareAdapterArgs(context);
|
|
606
|
-
}
|
|
607
|
-
return ADAPTER_OPTIONS.get(deployment.adapter) ?? "";
|
|
608
|
-
})();
|
|
609
|
-
// Vercel resolves its Build Output tree and its `@vercel/nft` dependency
|
|
610
|
-
// trace against the Astro root, which for Blume is the hidden `.blume`
|
|
611
|
-
// runtime — leaving the traced function without its chunks or node_modules.
|
|
612
|
-
// The other adapters emit into `outDir` (cloudflare, node) or are surfaced
|
|
613
|
-
// afterwards (netlify), so none of them read `root` this way.
|
|
614
|
-
const adapterExpr =
|
|
615
|
-
deployment.adapter === "vercel"
|
|
616
|
-
? `withAdapterRoot(adapter(${adapterArgs}), ${JSON.stringify(adapterRoot(context))})`
|
|
617
|
-
: `adapter(${adapterArgs})`;
|
|
618
|
-
const adapterOption =
|
|
619
|
-
server && deployment.adapter ? `\n adapter: ${adapterExpr},` : "";
|
|
620
|
-
|
|
621
|
-
const sessionOption = resolveSessionOption(deployment);
|
|
622
|
-
|
|
623
|
-
const siteOption = deployment.site
|
|
624
|
-
? `\n site: ${JSON.stringify(deployment.site)},`
|
|
628
|
+
const {
|
|
629
|
+
configEntries: adapterConfigEntries,
|
|
630
|
+
importLine: adapterImport,
|
|
631
|
+
option: adapterOption,
|
|
632
|
+
} = renderAstroAdapter(deployment, context, ejected);
|
|
633
|
+
|
|
634
|
+
const siteOption = deployment.options.site
|
|
635
|
+
? `\n site: ${JSON.stringify(deployment.options.site)},`
|
|
625
636
|
: "";
|
|
626
|
-
const baseOption = deployment.base
|
|
627
|
-
? `\n base: ${JSON.stringify(deployment.base)},`
|
|
637
|
+
const baseOption = deployment.options.base
|
|
638
|
+
? `\n base: ${JSON.stringify(deployment.options.base)},`
|
|
628
639
|
: "";
|
|
629
640
|
const imageOption = renderImageOption(config);
|
|
630
641
|
|
|
@@ -649,7 +660,7 @@ export const astroConfigTemplate = (options: {
|
|
|
649
660
|
const basedRedirects = applyBaseToAstroRedirects(
|
|
650
661
|
config.redirects,
|
|
651
662
|
config.basePath,
|
|
652
|
-
deployment.base ?? ""
|
|
663
|
+
deployment.options.base ?? ""
|
|
653
664
|
);
|
|
654
665
|
const redirectsOption =
|
|
655
666
|
basedRedirects.length > 0
|
|
@@ -746,16 +757,26 @@ export const astroConfigTemplate = (options: {
|
|
|
746
757
|
// hand-written `basePath` link (`/docs/x`) isn't double-prefixed (see
|
|
747
758
|
// `withComposedBasePath`). The link checker validates the base-less authored
|
|
748
759
|
// path against `basePath` routes separately.
|
|
749
|
-
const deployBase = normalizeBasePath(deployment.base);
|
|
760
|
+
const deployBase = normalizeBasePath(deployment.options.base);
|
|
761
|
+
// Both processors take the same options. An ejected app has no CLI to
|
|
762
|
+
// publish the `blume:data` snapshot that relative page links resolve
|
|
763
|
+
// through, so its processors read the snapshot file instead, resolved
|
|
764
|
+
// against the config file like the module aliases.
|
|
765
|
+
const processorLiteral = JSON.stringify({
|
|
766
|
+
basePath: config.basePath,
|
|
767
|
+
codeThemes: config.markdown.code.theme,
|
|
768
|
+
contentRoot: options.contentRoot,
|
|
769
|
+
deployBase,
|
|
770
|
+
externalLinks: config.markdown.externalLinks,
|
|
771
|
+
headingAnchors: config.markdown.headingAnchors,
|
|
772
|
+
});
|
|
773
|
+
const processorOptions =
|
|
774
|
+
options.generatedModulesDir === undefined
|
|
775
|
+
? processorLiteral
|
|
776
|
+
: `{ ...${processorLiteral}, dataFile: ${configPath(`${options.generatedModulesDir}/data.json`, true)} }`;
|
|
750
777
|
|
|
751
778
|
const integrations = [
|
|
752
|
-
`mdx({ processor: blumeMdxProcessor(${
|
|
753
|
-
basePath: config.basePath,
|
|
754
|
-
codeThemes: config.markdown.codeBlocks.theme,
|
|
755
|
-
contentRoot: options.contentRoot,
|
|
756
|
-
deployBase,
|
|
757
|
-
headingAnchors: config.markdown.headingAnchors,
|
|
758
|
-
})}) })`,
|
|
779
|
+
`mdx({ processor: blumeMdxProcessor(${processorOptions}) })`,
|
|
759
780
|
];
|
|
760
781
|
if (needsReact) {
|
|
761
782
|
integrations.push(reactIntegration(options.reactCompilerPath));
|
|
@@ -774,7 +795,7 @@ export const astroConfigTemplate = (options: {
|
|
|
774
795
|
blumeIntegrationOptions({
|
|
775
796
|
config,
|
|
776
797
|
contentRoutes,
|
|
777
|
-
ejected
|
|
798
|
+
ejected,
|
|
778
799
|
pages,
|
|
779
800
|
})
|
|
780
801
|
)})`
|
|
@@ -787,9 +808,13 @@ export const astroConfigTemplate = (options: {
|
|
|
787
808
|
userIntegrationSpread,
|
|
788
809
|
} = renderIntegrationBridge(options.integrationBridge);
|
|
789
810
|
|
|
790
|
-
|
|
811
|
+
const fileUrlImport = ejected
|
|
812
|
+
? `import { fileURLToPath } from "node:url";\n`
|
|
813
|
+
: "";
|
|
814
|
+
|
|
815
|
+
return `${astroConfigHeader(ejected)}
|
|
791
816
|
${configSourceMarker}${userConfigImports}import { availableParallelism } from "node:os";
|
|
792
|
-
${defineConfigImport}
|
|
817
|
+
${fileUrlImport}${defineConfigImport}
|
|
793
818
|
import mdx from "@astrojs/mdx";
|
|
794
819
|
import tailwindcss from "@tailwindcss/vite";
|
|
795
820
|
import { blumeMarkdownProcessor, blumeMdxProcessor, blumeShikiTransformers, blumeTwoslashTransformer } from "blume/markdown";
|
|
@@ -797,22 +822,16 @@ ${reactImport}${vueImport}${svelteImport}${blumeImport}${adapterImport}
|
|
|
797
822
|
${userConfigSetup}export default defineConfig({
|
|
798
823
|
root: ${JSON.stringify(context.outDir)},
|
|
799
824
|
srcDir: ${JSON.stringify(`${context.outDir}/src`)},
|
|
800
|
-
outDir: ${JSON.stringify(
|
|
825
|
+
outDir: ${JSON.stringify(distDir(context))},
|
|
801
826
|
publicDir: ${JSON.stringify(`${context.root}/public`)},${cacheOptions}
|
|
802
|
-
output: ${JSON.stringify(deployment.output)},${adapterOption}${
|
|
827
|
+
output: ${JSON.stringify(deployment.options.output)},${adapterOption}${adapterConfigEntries}${siteOption}${baseOption}${imageOption}${redirectsOption}${i18nOption}${fontsOption}
|
|
803
828
|
integrations: [${integrations.join(", ")}${userIntegrationSpread}],
|
|
804
829
|
markdown: {
|
|
805
|
-
processor: blumeMarkdownProcessor(${
|
|
806
|
-
basePath: config.basePath,
|
|
807
|
-
codeThemes: config.markdown.codeBlocks.theme,
|
|
808
|
-
contentRoot: options.contentRoot,
|
|
809
|
-
deployBase,
|
|
810
|
-
headingAnchors: config.markdown.headingAnchors,
|
|
811
|
-
})}),
|
|
830
|
+
processor: blumeMarkdownProcessor(${processorOptions}),
|
|
812
831
|
shikiConfig: {
|
|
813
832
|
themes: {
|
|
814
|
-
light: ${JSON.stringify(config.markdown.
|
|
815
|
-
dark: ${JSON.stringify(config.markdown.
|
|
833
|
+
light: ${JSON.stringify(config.markdown.code.theme.light)},
|
|
834
|
+
dark: ${JSON.stringify(config.markdown.code.theme.dark)},
|
|
816
835
|
},
|
|
817
836
|
defaultColor: false,
|
|
818
837
|
transformers: [${twoslashTransformer}...blumeShikiTransformers(${JSON.stringify(
|
|
@@ -841,9 +860,15 @@ ${userConfigSetup}export default defineConfig({
|
|
|
841
860
|
// the overlap only adds memory.
|
|
842
861
|
build: { concurrency: Math.min(8, availableParallelism()) },
|
|
843
862
|
vite: {${viteCacheOption}
|
|
844
|
-
plugins: [${runtimeModulesPluginEntry}tailwindcss(), includeHmrPlugin(${
|
|
845
|
-
`${context.outDir}/src/generated/includes.json
|
|
863
|
+
plugins: [${runtimeModulesPluginEntry}tailwindcss(), includeHmrPlugin(${configPath(
|
|
864
|
+
`${context.outDir}/src/generated/includes.json`,
|
|
865
|
+
ejected
|
|
846
866
|
)}), prerenderDepsPlugin()],
|
|
867
|
+
build: {${features.mermaid ? MERMAID_CHUNK_LIMIT : ""}
|
|
868
|
+
rolldownOptions: {
|
|
869
|
+
${ROLLDOWN_ON_LOG},
|
|
870
|
+
},
|
|
871
|
+
},
|
|
847
872
|
// Everything hydration can reach must be part of the dev dep optimizer's
|
|
848
873
|
// FIRST run. The Vite root is the generated runtime, so user pages,
|
|
849
874
|
// islands, and aliased components live outside it and are only crawled
|
|
@@ -889,12 +914,12 @@ ${userConfigSetup}export default defineConfig({
|
|
|
889
914
|
},
|
|
890
915
|
resolve: {
|
|
891
916
|
alias: {
|
|
892
|
-
"blume:ask": ${
|
|
893
|
-
"blume:examples": ${
|
|
894
|
-
"blume:examples-theme": ${
|
|
895
|
-
"blume:features": ${
|
|
896
|
-
"blume:search-client": ${
|
|
897
|
-
"blume:theme": ${
|
|
917
|
+
"blume:ask": ${configPath(askPath, ejected)},
|
|
918
|
+
"blume:examples": ${configPath(examplesPath, ejected)},
|
|
919
|
+
"blume:examples-theme": ${configPath(examplesThemePath, ejected)},
|
|
920
|
+
"blume:features": ${configPath(featuresPath, ejected)},
|
|
921
|
+
"blume:search-client": ${configPath(searchClientPath, ejected)},
|
|
922
|
+
"blume:theme": ${configPath(themePath, ejected)},${runtimeModuleAliasLines}${userAliasLines}
|
|
898
923
|
},
|
|
899
924
|
},
|
|
900
925
|
server: {
|
|
@@ -943,10 +968,9 @@ export const contentConfigTemplate = (options: {
|
|
|
943
968
|
/** Base dir for the staged collection; defaults to `<outDir>/content`. */
|
|
944
969
|
stagedBase?: string;
|
|
945
970
|
/**
|
|
946
|
-
* The `docs` collection's base + include/exclude globs. Defaults to
|
|
947
|
-
* `
|
|
948
|
-
* roots the
|
|
949
|
-
* `resolveDocsCollection`).
|
|
971
|
+
* The `docs` collection's base + include/exclude globs. Defaults to what
|
|
972
|
+
* `resolveDocsCollection` derives from the filesystem sources: the collection
|
|
973
|
+
* roots at the first one so entry ids resolve.
|
|
950
974
|
*/
|
|
951
975
|
collection?: { base: string; include: string[]; exclude: string[] };
|
|
952
976
|
/**
|
|
@@ -958,9 +982,11 @@ export const contentConfigTemplate = (options: {
|
|
|
958
982
|
}): string => {
|
|
959
983
|
const { context, config } = options;
|
|
960
984
|
const stagedBase = options.stagedBase ?? stagedContentDir(context.outDir);
|
|
961
|
-
const
|
|
962
|
-
|
|
963
|
-
const
|
|
985
|
+
const collection =
|
|
986
|
+
options.collection ?? resolveDocsCollection(config, context.root);
|
|
987
|
+
const collectionBase = collection.base;
|
|
988
|
+
const includeGlobs = collection.include;
|
|
989
|
+
const excludeGlobs = collection.exclude;
|
|
964
990
|
|
|
965
991
|
// Fold the content excludes into the glob as negative patterns so the `docs`
|
|
966
992
|
// collection doesn't ingest ignored trees (`node_modules`, `snippets`, the
|
|
@@ -1044,11 +1070,6 @@ export interface AskEndpointOptions {
|
|
|
1044
1070
|
cors?: string[];
|
|
1045
1071
|
/** `ai.ask.instructions` — extra system-prompt text. */
|
|
1046
1072
|
instructions?: string;
|
|
1047
|
-
/**
|
|
1048
|
-
* `ai.ask.reasoning` — how much the model reasons before answering, sent
|
|
1049
|
-
* as the backend's own reasoning-effort control.
|
|
1050
|
-
*/
|
|
1051
|
-
reasoning?: AskReasoning;
|
|
1052
1073
|
/** `ai.ask.retrieval` — how much documentation each question carries. */
|
|
1053
1074
|
retrieval?: AskRetrievalOptions;
|
|
1054
1075
|
}
|
|
@@ -1092,31 +1113,38 @@ export const OPTIONS: APIRoute = ({ request }) =>
|
|
|
1092
1113
|
}
|
|
1093
1114
|
: { close: "};", imports: [], open: "async ({ request }) => {", setup: "" };
|
|
1094
1115
|
|
|
1116
|
+
/**
|
|
1117
|
+
* Largest request body the Ask AI route reads: 64 KB, well above the
|
|
1118
|
+
* 24,000-character message budget it validates next, so a real conversation
|
|
1119
|
+
* never meets it.
|
|
1120
|
+
*/
|
|
1121
|
+
const ASK_BODY_LIMIT_BYTES = 65_536;
|
|
1122
|
+
|
|
1095
1123
|
/**
|
|
1096
1124
|
* Generate the Ask AI server endpoint (`.blume/src/pages/api/ask.ts`).
|
|
1097
1125
|
*
|
|
1126
|
+
* The provider-specific pieces — imports, the provider factory call, the model
|
|
1127
|
+
* expression, the credential guard, and the adapter's reasoning mapping and
|
|
1128
|
+
* `providerOptions` — come from the resolved `backend` descriptor, inlined as
|
|
1129
|
+
* literals so the route imports the provider SDK by bare name and never
|
|
1130
|
+
* `blume.config.ts`. `backend.grounded` decides whether answers are grounded
|
|
1131
|
+
* in the retrieved docs (every adapter but Inkeep, which retrieves itself).
|
|
1132
|
+
*
|
|
1098
1133
|
* `options.instructions` (the `ai.ask.instructions` config) is appended to the
|
|
1099
1134
|
* built-in prompt on every path: the grounded prompt via `createAskContext`,
|
|
1100
1135
|
* and the plain fallback here. `options.retrieval` (the `ai.ask.retrieval`
|
|
1101
1136
|
* config) is forwarded to `createAskContext` on the grounded path, where it
|
|
1102
|
-
* sizes retrieval. `options.
|
|
1103
|
-
*
|
|
1104
|
-
*
|
|
1105
|
-
*
|
|
1106
|
-
* site can't silently drop one of them.
|
|
1137
|
+
* sizes retrieval. `options.cors` (the `ai.ask.cors` config) adds a preflight
|
|
1138
|
+
* handler and wraps the `POST` so every response names a listed origin. All
|
|
1139
|
+
* three travel in one options object so a new call site can't silently drop
|
|
1140
|
+
* one of them.
|
|
1107
1141
|
*/
|
|
1108
1142
|
export const askEndpointTemplate = (
|
|
1109
1143
|
backend: AskBackend,
|
|
1110
|
-
grounded: boolean,
|
|
1111
1144
|
options?: AskEndpointOptions
|
|
1112
1145
|
): string => {
|
|
1113
|
-
const { instructions,
|
|
1114
|
-
|
|
1115
|
-
// from `streamText`'s top-level `reasoning` (the gateway maps it to the
|
|
1116
|
-
// model's own control, the OpenAI-compatible provider sends it as
|
|
1117
|
-
// `reasoning_effort`). OpenRouter's provider ignores that call option and
|
|
1118
|
-
// only reads its own model setting, so there the level rides on the model
|
|
1119
|
-
// as `reasoning.effort`. Omitted keeps the provider default on every path.
|
|
1146
|
+
const { instructions, retrieval } = options ?? {};
|
|
1147
|
+
const { grounded } = backend;
|
|
1120
1148
|
const fallbackPrompt = instructions
|
|
1121
1149
|
? `${ASK_FALLBACK_PROMPT}\n\n${instructions}`
|
|
1122
1150
|
: ASK_FALLBACK_PROMPT;
|
|
@@ -1126,47 +1154,10 @@ export const askEndpointTemplate = (
|
|
|
1126
1154
|
const imports = [
|
|
1127
1155
|
'import type { APIRoute } from "astro";',
|
|
1128
1156
|
'import { getSecret } from "astro:env/server";',
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
// secret store reach it too.
|
|
1132
|
-
backend.kind === "gateway"
|
|
1133
|
-
? 'import { createGateway, streamText } from "ai";'
|
|
1134
|
-
: 'import { streamText } from "ai";',
|
|
1157
|
+
'import { readCappedText } from "blume/core/request-body.ts";',
|
|
1158
|
+
...backend.template.imports,
|
|
1135
1159
|
];
|
|
1136
|
-
let setup =
|
|
1137
|
-
let modelExpr = JSON.stringify(backend.model);
|
|
1138
|
-
// `ai.ask.headers`, inlined as literals: every provider factory below takes
|
|
1139
|
-
// the same `headers` option, so one line serves all three.
|
|
1140
|
-
const headersField = backend.headers
|
|
1141
|
-
? `\n headers: ${JSON.stringify(backend.headers)},`
|
|
1142
|
-
: "";
|
|
1143
|
-
if (backend.kind === "gateway") {
|
|
1144
|
-
setup = `\nconst gateway = createGateway({
|
|
1145
|
-
apiKey: getSecret("AI_GATEWAY_API_KEY"),${headersField}
|
|
1146
|
-
});\n`;
|
|
1147
|
-
modelExpr = `gateway(${JSON.stringify(backend.model)})`;
|
|
1148
|
-
} else if (backend.kind === "openrouter") {
|
|
1149
|
-
imports.push(
|
|
1150
|
-
'import { createOpenRouter } from "@openrouter/ai-sdk-provider";'
|
|
1151
|
-
);
|
|
1152
|
-
setup = `\nconst openrouter = createOpenRouter({
|
|
1153
|
-
apiKey: getSecret(${JSON.stringify(backend.apiKeyEnv)}),${headersField}
|
|
1154
|
-
});\n`;
|
|
1155
|
-
const settings = reasoning
|
|
1156
|
-
? `, { reasoning: { effort: ${JSON.stringify(reasoning)} } }`
|
|
1157
|
-
: "";
|
|
1158
|
-
modelExpr = `openrouter(${JSON.stringify(backend.model)}${settings})`;
|
|
1159
|
-
} else if (backend.kind === "openai-compatible") {
|
|
1160
|
-
imports.push(
|
|
1161
|
-
'import { createOpenAICompatible } from "@ai-sdk/openai-compatible";'
|
|
1162
|
-
);
|
|
1163
|
-
setup = `\nconst provider = createOpenAICompatible({
|
|
1164
|
-
apiKey: getSecret(${JSON.stringify(backend.apiKeyEnv)}),
|
|
1165
|
-
baseURL: ${JSON.stringify(backend.baseUrl)},${headersField}
|
|
1166
|
-
name: ${JSON.stringify(backend.name)},
|
|
1167
|
-
});\n`;
|
|
1168
|
-
modelExpr = `provider(${JSON.stringify(backend.model)})`;
|
|
1169
|
-
}
|
|
1160
|
+
let { setup } = backend.template;
|
|
1170
1161
|
// Ground the answer in retrieved docs, except for RAG-native backends (Inkeep),
|
|
1171
1162
|
// which run their own retrieval and would conflict with injected context.
|
|
1172
1163
|
if (grounded) {
|
|
@@ -1192,8 +1183,21 @@ export const askEndpointTemplate = (
|
|
|
1192
1183
|
// can spend against the model per request, and restricting roles to
|
|
1193
1184
|
// user/assistant keeps callers from injecting their own system prompt and
|
|
1194
1185
|
// repurposing the endpoint as a general LLM proxy; front it with a rate
|
|
1195
|
-
// limiter (or your provider's limits) for stronger protection.
|
|
1196
|
-
|
|
1186
|
+
// limiter (or your provider's limits) for stronger protection. The body is
|
|
1187
|
+
// read under a 64 KB cap before any parsing, since a self-hosted Node server
|
|
1188
|
+
// would otherwise buffer an arbitrarily large POST in memory first.
|
|
1189
|
+
const validate = ` const text = await readCappedText(request, ${ASK_BODY_LIMIT_BYTES});
|
|
1190
|
+
if (text === undefined) {
|
|
1191
|
+
return new Response("Request too large: the body must be at most 64 KB.", {
|
|
1192
|
+
status: 413,
|
|
1193
|
+
});
|
|
1194
|
+
}
|
|
1195
|
+
let body;
|
|
1196
|
+
try {
|
|
1197
|
+
body = JSON.parse(text);
|
|
1198
|
+
} catch {
|
|
1199
|
+
body = null;
|
|
1200
|
+
}
|
|
1197
1201
|
const raw = body?.messages;
|
|
1198
1202
|
const valid =
|
|
1199
1203
|
Array.isArray(raw) &&
|
|
@@ -1221,23 +1225,10 @@ export const askEndpointTemplate = (
|
|
|
1221
1225
|
// `streamText` returns synchronously and defers provider/auth/network errors
|
|
1222
1226
|
// to stream consumption, so the handler's try/catch never sees them: without
|
|
1223
1227
|
// these the client gets a 200 whose stream aborts mid-flight and nothing is
|
|
1224
|
-
// logged server-side. A missing credential is rejected up front
|
|
1225
|
-
//
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
? ` // The AI Gateway authenticates with an API key or Vercel's OIDC token.
|
|
1229
|
-
if (!(getSecret("AI_GATEWAY_API_KEY") || getSecret("VERCEL_OIDC_TOKEN"))) {
|
|
1230
|
-
return new Response(
|
|
1231
|
-
"Ask AI is not configured: set AI_GATEWAY_API_KEY (or deploy on Vercel with OIDC).",
|
|
1232
|
-
{ status: 500 }
|
|
1233
|
-
);
|
|
1234
|
-
}`
|
|
1235
|
-
: ` if (!getSecret(${JSON.stringify(backend.apiKeyEnv)})) {
|
|
1236
|
-
return new Response(
|
|
1237
|
-
${JSON.stringify(`Ask AI is not configured: set ${backend.apiKeyEnv}.`)},
|
|
1238
|
-
{ status: 500 }
|
|
1239
|
-
);
|
|
1240
|
-
}`;
|
|
1228
|
+
// logged server-side. A missing credential is rejected up front with a 503
|
|
1229
|
+
// (the adapter's `keyCheck`); everything else is at least logged via
|
|
1230
|
+
// `onError`.
|
|
1231
|
+
const { keyCheck } = backend.template;
|
|
1241
1232
|
// Provider errors surface mid-stream, after the 200 is committed; this is
|
|
1242
1233
|
// the only place they can be observed server-side.
|
|
1243
1234
|
const onError = ` onError({ error }) {
|
|
@@ -1245,16 +1236,16 @@ export const askEndpointTemplate = (
|
|
|
1245
1236
|
},`;
|
|
1246
1237
|
// The `streamText` argument list, built once so the grounded and plain
|
|
1247
1238
|
// paths can't drift: they differ only in where the instructions come from.
|
|
1239
|
+
// The adapter appends its own call-level fields (its reasoning mapping when
|
|
1240
|
+
// that is a call option, and the verbatim `providerOptions`).
|
|
1248
1241
|
const streamFields = [
|
|
1249
|
-
`model: ${
|
|
1242
|
+
`model: ${backend.template.model}`,
|
|
1250
1243
|
grounded
|
|
1251
1244
|
? "instructions"
|
|
1252
1245
|
: `instructions:\n ${JSON.stringify(fallbackPrompt)}`,
|
|
1253
1246
|
"messages",
|
|
1247
|
+
...backend.template.fields,
|
|
1254
1248
|
];
|
|
1255
|
-
if (reasoning && backend.kind !== "openrouter") {
|
|
1256
|
-
streamFields.push(`reasoning: ${JSON.stringify(reasoning)}`);
|
|
1257
|
-
}
|
|
1258
1249
|
const call = ` const result = streamText({
|
|
1259
1250
|
${streamFields.join(",\n ")},
|
|
1260
1251
|
${onError}
|
|
@@ -1270,7 +1261,9 @@ ${validate}
|
|
|
1270
1261
|
${keyCheck}
|
|
1271
1262
|
try {
|
|
1272
1263
|
${stream}
|
|
1273
|
-
return
|
|
1264
|
+
return createTextStreamResponse({
|
|
1265
|
+
stream: toTextStream({ stream: result.stream }),
|
|
1266
|
+
});
|
|
1274
1267
|
} catch {
|
|
1275
1268
|
return new Response("Failed to generate a response.", { status: 500 });
|
|
1276
1269
|
}
|
|
@@ -1366,47 +1359,18 @@ export const createSearch = () => create({ indexUrl${
|
|
|
1366
1359
|
`;
|
|
1367
1360
|
|
|
1368
1361
|
/**
|
|
1369
|
-
*
|
|
1370
|
-
*
|
|
1371
|
-
*
|
|
1362
|
+
* A client that passes the adapter's options — public credentials, by
|
|
1363
|
+
* contract — straight to the provider SDK. The options are inlined verbatim
|
|
1364
|
+
* as a literal: Blume maps only the fields it names, and any extra option
|
|
1365
|
+
* the adapter was given rides along untouched.
|
|
1372
1366
|
*/
|
|
1373
|
-
type HostedSearchCredentials = Record<string, string | number | undefined>;
|
|
1374
|
-
|
|
1375
|
-
/** A client that passes public credentials straight to the provider SDK. */
|
|
1376
1367
|
const hostedSearchClient = (
|
|
1377
|
-
|
|
1378
|
-
options: HostedSearchCredentials
|
|
1368
|
+
provider: Extract<ResolvedSearchAdapter, { mode: "hosted" }>
|
|
1379
1369
|
): string =>
|
|
1380
|
-
`${SEARCH_CLIENT_HEADER}${searchClientImport(
|
|
1381
|
-
export const createSearch = () => create(${JSON.stringify(options)});
|
|
1370
|
+
`${SEARCH_CLIENT_HEADER}${searchClientImport(provider.kind)}
|
|
1371
|
+
export const createSearch = () => create(${JSON.stringify(provider.options)});
|
|
1382
1372
|
`;
|
|
1383
1373
|
|
|
1384
|
-
/** Build the per-provider config object the hosted client is created with. */
|
|
1385
|
-
const hostedSearchOptions = (
|
|
1386
|
-
search: ResolvedConfig["search"]
|
|
1387
|
-
): { module: string; options: HostedSearchCredentials } | null => {
|
|
1388
|
-
switch (search.provider) {
|
|
1389
|
-
case "algolia": {
|
|
1390
|
-
return { module: "algolia", options: { ...search.algolia } };
|
|
1391
|
-
}
|
|
1392
|
-
case "orama-cloud": {
|
|
1393
|
-
return {
|
|
1394
|
-
module: "orama-cloud",
|
|
1395
|
-
options: {
|
|
1396
|
-
apiKey: search.oramaCloud?.apiKey,
|
|
1397
|
-
endpoint: search.oramaCloud?.endpoint,
|
|
1398
|
-
},
|
|
1399
|
-
};
|
|
1400
|
-
}
|
|
1401
|
-
case "typesense": {
|
|
1402
|
-
return { module: "typesense", options: { ...search.typesense } };
|
|
1403
|
-
}
|
|
1404
|
-
default: {
|
|
1405
|
-
return null;
|
|
1406
|
-
}
|
|
1407
|
-
}
|
|
1408
|
-
};
|
|
1409
|
-
|
|
1410
1374
|
/**
|
|
1411
1375
|
* Generate `.blume/src/generated/features.ts` — the client-feature loaders
|
|
1412
1376
|
* behind the `blume:features` alias. Each loader is a dynamic import when the
|
|
@@ -1443,78 +1407,102 @@ export const loadEpub:
|
|
|
1443
1407
|
* baked in here; secret keys never reach the client.
|
|
1444
1408
|
*/
|
|
1445
1409
|
export const searchClientTemplate = (config: ResolvedConfig): string => {
|
|
1446
|
-
const {
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
if (search.provider === "mixedbread") {
|
|
1462
|
-
return `${SEARCH_CLIENT_HEADER}${searchClientImport("endpoint")}${SEARCH_BASE_IMPORT}
|
|
1410
|
+
const { provider } = config.search;
|
|
1411
|
+
|
|
1412
|
+
switch (provider.mode) {
|
|
1413
|
+
case "static": {
|
|
1414
|
+
// Only Orama derives a tokenizer from the locale; FlexSearch has no
|
|
1415
|
+
// equivalent hook, so its client keeps the bare index URL.
|
|
1416
|
+
const locale =
|
|
1417
|
+
provider.kind === "orama" ? config.i18n?.defaultLocale : undefined;
|
|
1418
|
+
return staticSearchClient(provider.kind, locale);
|
|
1419
|
+
}
|
|
1420
|
+
case "hosted": {
|
|
1421
|
+
return hostedSearchClient(provider);
|
|
1422
|
+
}
|
|
1423
|
+
case "server": {
|
|
1424
|
+
return `${SEARCH_CLIENT_HEADER}${searchClientImport("endpoint")}${SEARCH_BASE_IMPORT}
|
|
1463
1425
|
const api = joinBase(import.meta.env.BASE_URL, "api/search");
|
|
1464
1426
|
|
|
1465
1427
|
export const createSearch = () => create({ api });
|
|
1466
1428
|
`;
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
return `${SEARCH_CLIENT_HEADER}${searchClientImport("pagefind")}${SEARCH_BASE_IMPORT}
|
|
1429
|
+
}
|
|
1430
|
+
case "pagefind": {
|
|
1431
|
+
return `${SEARCH_CLIENT_HEADER}${searchClientImport("pagefind")}${SEARCH_BASE_IMPORT}
|
|
1471
1432
|
const url = joinBase(import.meta.env.BASE_URL, "pagefind/pagefind.js");
|
|
1472
1433
|
|
|
1473
1434
|
export const createSearch = () => create({ url });
|
|
1474
1435
|
`;
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1436
|
+
}
|
|
1437
|
+
default: {
|
|
1438
|
+
// Search disabled: a no-op client so the alias always resolves.
|
|
1439
|
+
return `${SEARCH_CLIENT_HEADER}export const createSearch = () => () =>
|
|
1479
1440
|
Promise.resolve({ hits: [], sections: [] });
|
|
1480
1441
|
`;
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1481
1444
|
};
|
|
1482
1445
|
|
|
1483
1446
|
/**
|
|
1484
1447
|
* Generate the Mixedbread search endpoint (`/api/search`). It holds the secret
|
|
1485
1448
|
* key server-side and proxies semantic queries to the configured store. The
|
|
1486
|
-
*
|
|
1487
|
-
*
|
|
1449
|
+
* adapter's options are inlined as a literal so the route never imports the
|
|
1450
|
+
* config. The result mapping is best-effort and may need tuning to how your
|
|
1451
|
+
* content was synced (see the Mixedbread sync step / \`mxbai vs sync\`).
|
|
1488
1452
|
*/
|
|
1489
|
-
export const mixedbreadSearchEndpointTemplate = (
|
|
1453
|
+
export const mixedbreadSearchEndpointTemplate = (
|
|
1454
|
+
options: MixedbreadOptions
|
|
1455
|
+
): string =>
|
|
1490
1456
|
`// Generated by Blume. Do not edit.
|
|
1491
1457
|
import type { APIRoute } from "astro";
|
|
1492
1458
|
import { getSecret } from "astro:env/server";
|
|
1493
1459
|
import Mixedbread from "@mixedbread/sdk";
|
|
1460
|
+
import { readCappedText } from "blume/core/request-body.ts";
|
|
1494
1461
|
|
|
1495
1462
|
export const prerender = false;
|
|
1496
1463
|
|
|
1497
1464
|
const client = new Mixedbread({ apiKey: getSecret("MIXEDBREAD_API_KEY") ?? "" });
|
|
1498
|
-
const
|
|
1465
|
+
const OPTIONS = ${JSON.stringify(options)};
|
|
1466
|
+
// Every option besides the store reaches the search call verbatim.
|
|
1467
|
+
const { storeId: STORE_ID, ...SEARCH_OPTIONS } = OPTIONS;
|
|
1499
1468
|
|
|
1500
1469
|
export const POST: APIRoute = async ({ request }) => {
|
|
1470
|
+
// A search body is one short query: read it under a 16 KB cap, so a
|
|
1471
|
+
// self-hosted server never buffers an arbitrarily large POST first.
|
|
1472
|
+
const text = await readCappedText(request, 16_384);
|
|
1473
|
+
if (text === undefined) {
|
|
1474
|
+
return new Response("Request too large: the body must be at most 16 KB.", {
|
|
1475
|
+
status: 413,
|
|
1476
|
+
});
|
|
1477
|
+
}
|
|
1501
1478
|
// The endpoint is public: a malformed body must 200-empty, not 500.
|
|
1502
|
-
|
|
1479
|
+
let body = null;
|
|
1480
|
+
try {
|
|
1481
|
+
body = JSON.parse(text);
|
|
1482
|
+
} catch {
|
|
1483
|
+
body = null;
|
|
1484
|
+
}
|
|
1503
1485
|
const query = body?.query;
|
|
1504
1486
|
if (!query || typeof query !== "string") {
|
|
1505
1487
|
return new Response("[]", {
|
|
1506
1488
|
headers: { "Content-Type": "application/json" },
|
|
1507
1489
|
});
|
|
1508
1490
|
}
|
|
1491
|
+
// \`top_k\` defaults to 8 unless an option sets it; the query and store
|
|
1492
|
+
// always come from the request and \`storeId\`.
|
|
1509
1493
|
const response = await client.stores.search({
|
|
1494
|
+
top_k: 8,
|
|
1495
|
+
...SEARCH_OPTIONS,
|
|
1510
1496
|
query,
|
|
1511
1497
|
store_identifiers: [STORE_ID],
|
|
1512
|
-
top_k: 8,
|
|
1513
1498
|
});
|
|
1514
1499
|
const hits = (response.data ?? []).map((chunk) => {
|
|
1515
1500
|
const meta = chunk.generated_metadata ?? {};
|
|
1501
|
+
// Only a text chunk carries \`text\`; image, audio, and video chunks fall
|
|
1502
|
+
// back to the excerpt the store generated for them.
|
|
1503
|
+
const text = "text" in chunk ? chunk.text : undefined;
|
|
1516
1504
|
return {
|
|
1517
|
-
excerpt:
|
|
1505
|
+
excerpt: text ?? meta.excerpt ?? "",
|
|
1518
1506
|
title: meta.title ?? chunk.filename ?? "",
|
|
1519
1507
|
url: meta.url ?? "",
|
|
1520
1508
|
};
|
|
@@ -1661,9 +1649,15 @@ export const GET: APIRoute = async ({ params }) => {
|
|
|
1661
1649
|
return new Response(null, { status: 404 });
|
|
1662
1650
|
}
|
|
1663
1651
|
const body = await readFile(path);
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1652
|
+
const type = contentType(path);
|
|
1653
|
+
// An SVG opened directly is a document that can run script; the sandbox
|
|
1654
|
+
// keeps an uploaded one inert on the docs origin. Static hosts get the same
|
|
1655
|
+
// header from the build's header rules.
|
|
1656
|
+
const headers: Record<string, string> =
|
|
1657
|
+
type === "image/svg+xml"
|
|
1658
|
+
? { "Content-Security-Policy": ${JSON.stringify(SVG_ASSET_POLICY)}, "Content-Type": type }
|
|
1659
|
+
: { "Content-Type": type };
|
|
1660
|
+
return new Response(new Uint8Array(body), { headers });
|
|
1667
1661
|
};
|
|
1668
1662
|
`;
|
|
1669
1663
|
|
|
@@ -1693,7 +1687,8 @@ export const ALL: APIRoute = ({ request }) => handler(request);
|
|
|
1693
1687
|
/**
|
|
1694
1688
|
* Generate the playground's CORS proxy endpoint
|
|
1695
1689
|
* (`.blume/src/blume-openapi/api-proxy.ts`), behind
|
|
1696
|
-
* `
|
|
1690
|
+
* `playground: { proxy: true }` on an `openapi()` reference. A thin
|
|
1691
|
+
* server-rendered wrapper around the
|
|
1697
1692
|
* shipped `createPlaygroundProxyHandler`; injected at `/_api-proxy` rather
|
|
1698
1693
|
* than written under `pages/` because Astro treats `_`-prefixed page files as
|
|
1699
1694
|
* private.
|
|
@@ -1793,7 +1788,9 @@ export function getStaticPaths() {
|
|
|
1793
1788
|
return navVariants(data, hiddenDefaultLocale(data.config.i18n)).flatMap(
|
|
1794
1789
|
({ locale, navigation, version }) =>
|
|
1795
1790
|
[...navGroupIds(navigation.sidebar)]
|
|
1796
|
-
.filter(
|
|
1791
|
+
.filter(
|
|
1792
|
+
([node]) => node.kind === "group" && (node.display ?? "flat") !== "flat"
|
|
1793
|
+
)
|
|
1797
1794
|
.map(([, id]) => ({ params: { id, locale, version } }))
|
|
1798
1795
|
);
|
|
1799
1796
|
}
|
|
@@ -2067,8 +2064,7 @@ import LocaleLinks from "blume/components/layout/LocaleLinks.astro";
|
|
|
2067
2064
|
import ApiOverview from "blume/components/openapi/ApiOverview.astro";
|
|
2068
2065
|
import ApiTagOperations from "blume/components/openapi/ApiTagOperations.astro";
|
|
2069
2066
|
import Operation from "blume/components/openapi/Operation.astro";
|
|
2070
|
-
${mathImport}import { mdxComponents as userMdx, layoutOverrides } from "../generated/components.ts"
|
|
2071
|
-
import { islandComponents } from "../generated/islands.ts";`,
|
|
2067
|
+
${mathImport}import { mdxComponents as userMdx, layoutOverrides } from "../generated/components.ts";`,
|
|
2072
2068
|
map: `{
|
|
2073
2069
|
Accordion,
|
|
2074
2070
|
AccordionItem,
|
|
@@ -2104,8 +2100,7 @@ import { islandComponents } from "../generated/islands.ts";`,
|
|
|
2104
2100
|
TypeTable,
|
|
2105
2101
|
Visibility,
|
|
2106
2102
|
YouTube,
|
|
2107
|
-
${mathEntry}...
|
|
2108
|
-
...userMdx,
|
|
2103
|
+
${mathEntry}...userMdx,
|
|
2109
2104
|
}`,
|
|
2110
2105
|
};
|
|
2111
2106
|
};
|
|
@@ -2164,6 +2159,7 @@ export function getStaticPaths() {
|
|
|
2164
2159
|
indexable: route.indexable,
|
|
2165
2160
|
lastModified: route.lastModified,
|
|
2166
2161
|
locale: route.locale,
|
|
2162
|
+
monolingual: route.monolingual,
|
|
2167
2163
|
route: route.path,
|
|
2168
2164
|
title: route.title,
|
|
2169
2165
|
version: route.version,
|
|
@@ -2172,7 +2168,7 @@ export function getStaticPaths() {
|
|
|
2172
2168
|
}));
|
|
2173
2169
|
}
|
|
2174
2170
|
|
|
2175
|
-
const { entryId, collection, route, title, indexable, editUrl, lastModified, locale, alternates, fallback, version, versionAlternates } = Astro.props;
|
|
2171
|
+
const { entryId, collection, route, title, indexable, editUrl, lastModified, locale, alternates, fallback, monolingual, version, versionAlternates } = Astro.props;
|
|
2176
2172
|
const entry = await getEntry(collection as CollectionKey, entryId);
|
|
2177
2173
|
if (!entry) {
|
|
2178
2174
|
return new Response(null, { status: 404 });
|
|
@@ -2264,13 +2260,32 @@ const contentDir = i18n
|
|
|
2264
2260
|
// hreflang URLs byte-match the sitemap's <loc> for the home page.
|
|
2265
2261
|
const absolute = (path: string) => base + withBase(path);
|
|
2266
2262
|
|
|
2263
|
+
// A fallback page renders the fallback locale's page at this locale's URL, so
|
|
2264
|
+
// its canonical names the page it copies, and search engines never rank the
|
|
2265
|
+
// copy against the original.
|
|
2266
|
+
const fallbackSource =
|
|
2267
|
+
fallback && i18n?.fallbackLocale
|
|
2268
|
+
? (alternates ?? []).find((alt) => alt.locale === i18n.fallbackLocale)
|
|
2269
|
+
: undefined;
|
|
2270
|
+
// The page it copies, when that page is archived too, points on to the latest
|
|
2271
|
+
// docs; the copy names that final page directly, the fallback locale's
|
|
2272
|
+
// version of the latest one, so the canonical is never a chain.
|
|
2273
|
+
const fallbackLatest =
|
|
2274
|
+
fallbackSource && archived && archived.canonical === "latest" && latestVersionAlt
|
|
2275
|
+
? (data.routes.find((entry) => entry.path === latestVersionAlt.path)?.alternates ?? []).find(
|
|
2276
|
+
(alt) => alt.locale === i18n?.fallbackLocale
|
|
2277
|
+
)
|
|
2278
|
+
: undefined;
|
|
2279
|
+
const fallbackTarget = fallbackLatest ?? fallbackSource;
|
|
2267
2280
|
// An archived page defaults its canonical to the same page in the latest docs
|
|
2268
2281
|
// when that page still exists — search engines treat the live page as
|
|
2269
2282
|
// authoritative without deindexing version-only content. A page's own
|
|
2270
2283
|
// \`seo.canonical\` always wins, and \`canonical: "self"\` keeps the default.
|
|
2271
2284
|
const canonical =
|
|
2272
2285
|
seo.canonical ??
|
|
2273
|
-
(
|
|
2286
|
+
(fallbackTarget && base
|
|
2287
|
+
? absolute(fallbackTarget.path)
|
|
2288
|
+
: archived && archived.canonical === "latest" && latestVersionAlt && base
|
|
2274
2289
|
? absolute(latestVersionAlt.path)
|
|
2275
2290
|
: base
|
|
2276
2291
|
? \`\${base}\${basedRoute === "/" ? "/" : encodeURI(basedRoute)}\`
|
|
@@ -2300,7 +2315,9 @@ const mountLocalized = (logical: string, codeArg: string) =>
|
|
|
2300
2315
|
const logicalRoute = i18n
|
|
2301
2316
|
? stripLocale(stripBasePath(data.config.basePath, route), locale)
|
|
2302
2317
|
: route;
|
|
2303
|
-
|
|
2318
|
+
// A page from a one-language source (GitHub Releases) gets no switcher: every
|
|
2319
|
+
// other locale would only repeat the same text.
|
|
2320
|
+
const localeSwitch = i18n && !monolingual
|
|
2304
2321
|
? i18n.locales.map((l) => {
|
|
2305
2322
|
const alt = (alternates ?? []).find((x) => x.locale === l.code);
|
|
2306
2323
|
return {
|
|
@@ -2397,6 +2414,7 @@ const LayoutComponent = resolveSlot(layoutOverrides.Layout, RootLayout);
|
|
|
2397
2414
|
locale={htmlLang}
|
|
2398
2415
|
dir={dir}
|
|
2399
2416
|
contentDir={contentDir}
|
|
2417
|
+
contentLocale={contentLocale}
|
|
2400
2418
|
ui={ui}
|
|
2401
2419
|
localeAlternates={localeAlternates}
|
|
2402
2420
|
xDefault={xDefault}
|
|
@@ -2446,14 +2464,17 @@ const LayoutComponent = resolveSlot(layoutOverrides.Layout, RootLayout);
|
|
|
2446
2464
|
|
|
2447
2465
|
/**
|
|
2448
2466
|
* Generate `.blume/src/pages/changelog.astro` — the changelog index. Collects
|
|
2449
|
-
* every `type: changelog` entry, sorts newest-first, and
|
|
2450
|
-
*
|
|
2451
|
-
*
|
|
2467
|
+
* every `type: changelog` entry, sorts newest-first, and lists them grouped by
|
|
2468
|
+
* year: one row per release with its title (linked to the entry's own page),
|
|
2469
|
+
* its `category` tag, and its date. Bodies are deliberately not rendered — a
|
|
2470
|
+
* long-lived project's index otherwise grows past what an agent can read in
|
|
2471
|
+
* one context window (and what a reader will scroll), while every entry
|
|
2472
|
+
* already has a page of its own. Only written by
|
|
2473
|
+
* {@link generateAstroProject} when changelog entries exist.
|
|
2452
2474
|
*/
|
|
2453
2475
|
export const changelogIndexTemplate = (options: {
|
|
2454
2476
|
exportEpub: boolean;
|
|
2455
2477
|
exportPdf: boolean;
|
|
2456
|
-
mathEnabled: boolean;
|
|
2457
2478
|
/** Serialize the island-hooks snapshot; only needed when React is enabled. */
|
|
2458
2479
|
needsReact: boolean;
|
|
2459
2480
|
/** Whether a `staged` collection exists (non-filesystem changelog sources). */
|
|
@@ -2467,40 +2488,29 @@ export const changelogIndexTemplate = (options: {
|
|
|
2467
2488
|
const stagedSpread = options.staged
|
|
2468
2489
|
? '\n ...(await getCollection("staged")),'
|
|
2469
2490
|
: "";
|
|
2470
|
-
const { imports: componentImports, map: componentMap } =
|
|
2471
|
-
contentComponentsSource(options.mathEnabled);
|
|
2472
2491
|
|
|
2473
2492
|
return `---
|
|
2474
2493
|
// Generated by Blume. Do not edit.
|
|
2475
|
-
import { getCollection
|
|
2494
|
+
import { getCollection } from "astro:content";
|
|
2476
2495
|
import RootLayout from "blume/components/layout/RootLayout.astro";
|
|
2477
|
-
import Update from "blume/components/content/Update.astro";
|
|
2478
2496
|
import { withBase } from "blume/components/islands/base-path.ts";
|
|
2479
2497
|
import { resolveSlot } from "blume/components/layout/overrides.ts";
|
|
2480
2498
|
import { resolveDateFormatOptions } from "blume/core/date-format.ts";
|
|
2481
|
-
|
|
2499
|
+
import { layoutOverrides } from "../generated/components.ts";
|
|
2482
2500
|
import data from "blume:data";
|
|
2483
2501
|
|
|
2484
|
-
const Color = Object.assign(ColorRoot, { Item: ColorItem, Row: ColorRow });
|
|
2485
|
-
const Tree = Object.assign(TreeRoot, { File: TreeFile, Folder: TreeFolder });
|
|
2486
|
-
|
|
2487
2502
|
export const prerender = true;
|
|
2488
2503
|
|
|
2489
|
-
// Entry bodies are MDX rendered outside the catch-all, so they need the same
|
|
2490
|
-
// component map: a \`:::\` callout or \`<Steps>\` in a release note otherwise
|
|
2491
|
-
// throws "Expected component ... to be defined" at build time.
|
|
2492
|
-
const components = ${componentMap};
|
|
2493
|
-
|
|
2494
2504
|
const entryDate = (entry: {
|
|
2495
2505
|
data: { date?: string | null; changelog?: { date?: string | null } | null };
|
|
2496
2506
|
}) => entry.data.date ?? entry.data.changelog?.date ?? null;
|
|
2497
2507
|
|
|
2498
|
-
const
|
|
2508
|
+
const parseDate = (value: string | null | undefined) => {
|
|
2499
2509
|
if (!value) {
|
|
2500
|
-
return
|
|
2510
|
+
return null;
|
|
2501
2511
|
}
|
|
2502
2512
|
const date = new Date(value);
|
|
2503
|
-
return Number.isNaN(date.getTime()) ?
|
|
2513
|
+
return Number.isNaN(date.getTime()) ? null : date;
|
|
2504
2514
|
};
|
|
2505
2515
|
|
|
2506
2516
|
// The changelog is an unlocalized route, so its chrome renders in the default
|
|
@@ -2515,34 +2525,45 @@ const htmlLang = i18n ? i18n.defaultLocale : "en";
|
|
|
2515
2525
|
|
|
2516
2526
|
// Formatted in the same locale as the chrome, and with the configured
|
|
2517
2527
|
// \`dateFormat\` (UTC by default), to match the per-page "last updated" stamp.
|
|
2528
|
+
// The year heads each group, so a row shows the rest of the date: a preset
|
|
2529
|
+
// style keeps its month wording, a component format simply drops the year.
|
|
2518
2530
|
const dateFormatOptions = resolveDateFormatOptions(data.config.dateFormat);
|
|
2519
|
-
const
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2531
|
+
const { dateStyle, year: _year, ...dateComponents } = dateFormatOptions;
|
|
2532
|
+
const rowDateFormat: Intl.DateTimeFormatOptions = dateStyle
|
|
2533
|
+
? {
|
|
2534
|
+
...dateComponents,
|
|
2535
|
+
day: "numeric",
|
|
2536
|
+
month: dateStyle === "medium" ? "short" : "long",
|
|
2537
|
+
}
|
|
2538
|
+
: dateComponents;
|
|
2539
|
+
const formatRowDate = (date: Date) =>
|
|
2540
|
+
new Intl.DateTimeFormat(htmlLang, rowDateFormat).format(date);
|
|
2541
|
+
const formatYear = (date: Date) =>
|
|
2542
|
+
new Intl.DateTimeFormat(htmlLang, {
|
|
2543
|
+
timeZone: dateFormatOptions.timeZone,
|
|
2544
|
+
year: "numeric",
|
|
2545
|
+
}).format(date);
|
|
2528
2546
|
|
|
2529
2547
|
const slugify = (text: string) =>
|
|
2530
2548
|
text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") ||
|
|
2531
2549
|
"update";
|
|
2532
2550
|
|
|
2533
|
-
//
|
|
2534
|
-
//
|
|
2535
|
-
//
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
//
|
|
2542
|
-
|
|
2543
|
-
const routeByEntry = new Map(
|
|
2544
|
-
|
|
2545
|
-
)
|
|
2551
|
+
// Map each entry to its own generated page so the row can link to it. The
|
|
2552
|
+
// collection entry id matches the route manifest's \`entryId\` — and under
|
|
2553
|
+
// i18n every locale serves a page for it (its translations plus fallback
|
|
2554
|
+
// copies of an untranslated one), all sharing that id, so a map over every
|
|
2555
|
+
// route would keep whichever locale came last: a Portuguese permalink on an
|
|
2556
|
+
// unlocalized index. Only the default locale's routes are kept, and an entry
|
|
2557
|
+
// is listed only when it has one, so a translated changelog file (a distinct
|
|
2558
|
+
// entry that lives under its own locale) does not add a second row for the
|
|
2559
|
+
// same release.
|
|
2560
|
+
const defaultLocale = i18n ? i18n.defaultLocale : null;
|
|
2561
|
+
const routeByEntry = new Map<string, string>();
|
|
2562
|
+
for (const route of data.routes) {
|
|
2563
|
+
if (defaultLocale === null || route.locale === defaultLocale) {
|
|
2564
|
+
routeByEntry.set(route.entryId, route.path);
|
|
2565
|
+
}
|
|
2566
|
+
}
|
|
2546
2567
|
|
|
2547
2568
|
const changelogEntries = [
|
|
2548
2569
|
...(await getCollection("docs")),${stagedSpread}
|
|
@@ -2551,35 +2572,37 @@ const changelogEntries = [
|
|
|
2551
2572
|
(entry) =>
|
|
2552
2573
|
entry.data.type === "changelog" &&
|
|
2553
2574
|
!entry.data.draft &&
|
|
2554
|
-
!entry.data.sidebar?.hidden
|
|
2575
|
+
!entry.data.sidebar?.hidden &&
|
|
2576
|
+
(defaultLocale === null || routeByEntry.has(entry.id))
|
|
2555
2577
|
)
|
|
2556
|
-
.toSorted(
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
)
|
|
2578
|
+
.toSorted(
|
|
2579
|
+
(a, b) =>
|
|
2580
|
+
(parseDate(entryDate(b))?.getTime() ?? 0) -
|
|
2581
|
+
(parseDate(entryDate(a))?.getTime() ?? 0)
|
|
2582
|
+
);
|
|
2583
|
+
|
|
2584
|
+
const items = changelogEntries.map((entry) => {
|
|
2585
|
+
const label =
|
|
2586
|
+
entry.data.title ??
|
|
2587
|
+
(entry.data.changelog?.version
|
|
2588
|
+
? "v" + entry.data.changelog.version
|
|
2589
|
+
: "Update");
|
|
2590
|
+
const date = parseDate(entryDate(entry));
|
|
2591
|
+
const route = routeByEntry.get(entry.id);
|
|
2592
|
+
return {
|
|
2593
|
+
date: date ? formatRowDate(date) : null,
|
|
2594
|
+
dateTime: date ? date.toISOString().slice(0, 10) : null,
|
|
2595
|
+
href: route ? withBase(route) : null,
|
|
2596
|
+
id: slugify(label),
|
|
2597
|
+
label,
|
|
2598
|
+
tag: entry.data.changelog?.category ?? null,
|
|
2599
|
+
year: date ? formatYear(date) : null,
|
|
2600
|
+
};
|
|
2601
|
+
});
|
|
2578
2602
|
|
|
2579
2603
|
// Repeated labels slug to the same id (e.g. two entries with neither a title
|
|
2580
2604
|
// nor a version both falling back to "update"); suffix the later ones -2, -3,
|
|
2581
|
-
// ... so every
|
|
2582
|
-
// slug, and the rendered ids stay in lockstep with the \`headings\` list below.
|
|
2605
|
+
// ... so every row keeps its own hash anchor. The first keeps the plain slug.
|
|
2583
2606
|
const seenIds = new Set();
|
|
2584
2607
|
for (const item of items) {
|
|
2585
2608
|
let uniqueId = item.id;
|
|
@@ -2590,26 +2613,18 @@ for (const item of items) {
|
|
|
2590
2613
|
item.id = uniqueId;
|
|
2591
2614
|
}
|
|
2592
2615
|
|
|
2593
|
-
//
|
|
2594
|
-
//
|
|
2595
|
-
//
|
|
2596
|
-
const
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
major,
|
|
2606
|
-
}));
|
|
2607
|
-
|
|
2608
|
-
const headings = items.map((item) => ({
|
|
2609
|
-
depth: 2,
|
|
2610
|
-
slug: item.id,
|
|
2611
|
-
text: item.label,
|
|
2612
|
-
}));
|
|
2616
|
+
// Consecutive releases from the same year share a group, so the list reads as
|
|
2617
|
+
// a year rail beside the rows; undated entries (sorted last) form a final
|
|
2618
|
+
// group with no year label.
|
|
2619
|
+
const groups: { items: typeof items; year: string | null }[] = [];
|
|
2620
|
+
for (const item of items) {
|
|
2621
|
+
const last = groups[groups.length - 1];
|
|
2622
|
+
if (last && last.year === item.year) {
|
|
2623
|
+
last.items.push(item);
|
|
2624
|
+
} else {
|
|
2625
|
+
groups.push({ items: [item], year: item.year });
|
|
2626
|
+
}
|
|
2627
|
+
}
|
|
2613
2628
|
|
|
2614
2629
|
const base = data.config.site ? data.config.site.replace(/\\/$/, "") : null;
|
|
2615
2630
|
// The canonical URL carries the deployment base (the page is served under it),
|
|
@@ -2623,9 +2638,9 @@ const canonical = base ? base + basedRoute : null;
|
|
|
2623
2638
|
const ogPath = data.config.og.enabled ? withBase("/og/changelog.png") : null;
|
|
2624
2639
|
const ogImage = ogPath && base ? base + ogPath : ogPath;
|
|
2625
2640
|
|
|
2626
|
-
// The page chrome (h1, title, description) comes from the
|
|
2627
|
-
// \`changelog\` group
|
|
2628
|
-
//
|
|
2641
|
+
// The page chrome (h1, title, description) comes from the translatable
|
|
2642
|
+
// \`changelog\` group; optional chaining tolerates a not-yet-regenerated data
|
|
2643
|
+
// snapshot from before these keys existed.
|
|
2629
2644
|
const changelogTitle = data.ui.changelog?.title ?? "Changelog";
|
|
2630
2645
|
const changelogDescription =
|
|
2631
2646
|
data.ui.changelog?.description ??
|
|
@@ -2658,7 +2673,7 @@ const LayoutComponent = resolveSlot(layoutOverrides.Layout, RootLayout);
|
|
|
2658
2673
|
description: changelogDescription,
|
|
2659
2674
|
route: "/changelog",
|
|
2660
2675
|
}}
|
|
2661
|
-
headings={
|
|
2676
|
+
headings={[]}
|
|
2662
2677
|
toc={data.config.toc}
|
|
2663
2678
|
contentLayout="bare"
|
|
2664
2679
|
themeMode={data.config.theme.mode}
|
|
@@ -2679,56 +2694,52 @@ const LayoutComponent = resolveSlot(layoutOverrides.Layout, RootLayout);
|
|
|
2679
2694
|
structuredDataEnabled={data.config.structuredData}
|
|
2680
2695
|
>
|
|
2681
2696
|
<h1>{changelogTitle}</h1>
|
|
2697
|
+
<p class="text-lg text-muted-foreground">{changelogDescription}</p>
|
|
2682
2698
|
{
|
|
2683
2699
|
items.length === 0 ? (
|
|
2684
2700
|
<p>No changelog entries yet.</p>
|
|
2685
|
-
) :
|
|
2686
|
-
<
|
|
2687
|
-
|
|
2688
|
-
data-i18n-more={data.ui.changelog?.showReleases}
|
|
2689
|
-
>
|
|
2690
|
-
{majorGroups[0].items.map(({ Content, href, id, label, date, tags }) => (
|
|
2691
|
-
<Update description={date} href={href} id={id} label={label} tags={tags}>
|
|
2692
|
-
<Content components={components} />
|
|
2693
|
-
</Update>
|
|
2694
|
-
))}
|
|
2695
|
-
{majorGroups.slice(1).map((group) => (
|
|
2701
|
+
) : (
|
|
2702
|
+
<div class="not-prose mt-10 divide-y divide-border border-border border-y">
|
|
2703
|
+
{groups.map((group) => (
|
|
2696
2704
|
<section
|
|
2697
|
-
aria-label={group.
|
|
2698
|
-
|
|
2699
|
-
data-changelog-major={group.major}
|
|
2705
|
+
aria-label={group.year ?? undefined}
|
|
2706
|
+
class="grid md:grid-cols-[6rem_minmax(0,1fr)] md:gap-x-8"
|
|
2700
2707
|
>
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2708
|
+
<h2 class="mt-0! pt-3! font-medium! text-muted-foreground text-sm! leading-5! tabular-nums max-md:pb-1">
|
|
2709
|
+
{group.year}
|
|
2710
|
+
</h2>
|
|
2711
|
+
<ul class="m-0! list-none divide-y divide-border p-0!">
|
|
2712
|
+
{group.items.map((item) => (
|
|
2713
|
+
<li class="m-0! p-0!" id={item.id}>
|
|
2714
|
+
<a
|
|
2715
|
+
class="group/release flex items-baseline gap-4 py-3 no-underline! hover:no-underline!"
|
|
2716
|
+
href={item.href ?? "#" + item.id}
|
|
2717
|
+
>
|
|
2718
|
+
<span class="min-w-0 flex-1 font-medium text-foreground text-sm transition-colors group-hover/release:text-accent">
|
|
2719
|
+
{item.label}
|
|
2720
|
+
</span>
|
|
2721
|
+
{item.tag && (
|
|
2722
|
+
<span class="shrink-0 rounded-full bg-muted px-2 py-0.5 font-medium text-[0.65rem] text-muted-foreground">
|
|
2723
|
+
{item.tag}
|
|
2724
|
+
</span>
|
|
2725
|
+
)}
|
|
2726
|
+
{item.date && (
|
|
2727
|
+
<time
|
|
2728
|
+
class="shrink-0 font-mono text-muted-foreground text-xs tabular-nums"
|
|
2729
|
+
datetime={item.dateTime}
|
|
2730
|
+
>
|
|
2731
|
+
{item.date}
|
|
2732
|
+
</time>
|
|
2733
|
+
)}
|
|
2734
|
+
</a>
|
|
2735
|
+
</li>
|
|
2736
|
+
))}
|
|
2737
|
+
</ul>
|
|
2706
2738
|
</section>
|
|
2707
2739
|
))}
|
|
2708
|
-
<div class="mt-10 flex justify-center">
|
|
2709
|
-
<button
|
|
2710
|
-
class="inline-flex items-center gap-2 rounded-full border border-border bg-background px-4 py-2 font-medium text-muted-foreground text-sm transition-colors hover:bg-muted hover:text-foreground"
|
|
2711
|
-
data-changelog-more
|
|
2712
|
-
hidden
|
|
2713
|
-
type="button"
|
|
2714
|
-
>
|
|
2715
|
-
Show older releases
|
|
2716
|
-
</button>
|
|
2717
|
-
</div>
|
|
2718
|
-
</blume-changelog>
|
|
2719
|
-
) : (
|
|
2720
|
-
<div class="not-prose mt-8">
|
|
2721
|
-
{items.map(({ Content, href, id, label, date, tags }) => (
|
|
2722
|
-
<Update description={date} href={href} id={id} label={label} tags={tags}>
|
|
2723
|
-
<Content components={components} />
|
|
2724
|
-
</Update>
|
|
2725
|
-
))}
|
|
2726
2740
|
</div>
|
|
2727
2741
|
)
|
|
2728
2742
|
}
|
|
2729
|
-
<script>
|
|
2730
|
-
import "blume/components/content/changelog-element.ts";
|
|
2731
|
-
</script>
|
|
2732
2743
|
</LayoutComponent>
|
|
2733
2744
|
`;
|
|
2734
2745
|
};
|
|
@@ -2766,7 +2777,7 @@ const htmlLang = i18n ? i18n.defaultLocale : "en";
|
|
|
2766
2777
|
// Recovery links, so a reader — or an agent that followed a stale URL — can
|
|
2767
2778
|
// get back on track without guessing: every top-level section, then the
|
|
2768
2779
|
// machine-readable indexes the build emits (the sitemap only exists with a
|
|
2769
|
-
// \`deployment.site\`; llms.txt only when \`
|
|
2780
|
+
// \`deployment.site\`; llms.txt only when \`agents.llmsTxt\` is on). Tabs link to
|
|
2770
2781
|
// their resolved target when the section has no index page of its own.
|
|
2771
2782
|
const suggestions = [
|
|
2772
2783
|
...data.navigation.tabs.map((tab) => ({
|
|
@@ -2780,6 +2791,31 @@ const suggestions = [
|
|
|
2780
2791
|
? [{ href: withBase("/llms.txt"), label: nf.llms }]
|
|
2781
2792
|
: []),
|
|
2782
2793
|
];
|
|
2794
|
+
|
|
2795
|
+
// Every host serves this one page for any missing URL, so a reader who
|
|
2796
|
+
// mistyped \`/ar/…\` would get the default locale's message. The other locales'
|
|
2797
|
+
// copy rides along, and the script below swaps it in when the URL's first
|
|
2798
|
+
// segment names one of them — the message, its language and direction, the
|
|
2799
|
+
// tab title, and the home link (to that locale's root).
|
|
2800
|
+
const localized = Object.fromEntries(
|
|
2801
|
+
(i18n?.locales ?? [])
|
|
2802
|
+
.filter((l) => l.code !== i18n?.defaultLocale && data.uiByLocale[l.code])
|
|
2803
|
+
.map((l) => {
|
|
2804
|
+
const strings = data.uiByLocale[l.code].notFound;
|
|
2805
|
+
return [
|
|
2806
|
+
l.code,
|
|
2807
|
+
{
|
|
2808
|
+
description: strings.description,
|
|
2809
|
+
dir: l.dir ?? "ltr",
|
|
2810
|
+
home: strings.home,
|
|
2811
|
+
homeHref: withBase("/" + l.code),
|
|
2812
|
+
suggestions: strings.suggestions,
|
|
2813
|
+
title: strings.title,
|
|
2814
|
+
},
|
|
2815
|
+
];
|
|
2816
|
+
})
|
|
2817
|
+
);
|
|
2818
|
+
const localizedJson = JSON.stringify(localized).replaceAll("<", "\\\\u003c");
|
|
2783
2819
|
---
|
|
2784
2820
|
|
|
2785
2821
|
<PageLayout
|
|
@@ -2801,6 +2837,8 @@ const suggestions = [
|
|
|
2801
2837
|
>
|
|
2802
2838
|
<div
|
|
2803
2839
|
class="mx-auto grid w-full max-w-5xl gap-12 px-6 py-20 sm:py-28 md:grid-cols-[3fr_2fr] md:gap-16 lg:gap-24 lg:py-36"
|
|
2840
|
+
data-base={withBase("/")}
|
|
2841
|
+
data-blume-not-found
|
|
2804
2842
|
>
|
|
2805
2843
|
<div class="flex flex-col items-start">
|
|
2806
2844
|
<p
|
|
@@ -2810,24 +2848,26 @@ const suggestions = [
|
|
|
2810
2848
|
</p>
|
|
2811
2849
|
<h1
|
|
2812
2850
|
class="mt-4 text-balance text-4xl font-semibold tracking-tight text-foreground sm:text-5xl"
|
|
2851
|
+
data-nf="title"
|
|
2813
2852
|
>
|
|
2814
2853
|
{nf.title}
|
|
2815
2854
|
</h1>
|
|
2816
|
-
<p class="mt-4 max-w-md text-pretty text-lg text-muted-foreground">
|
|
2855
|
+
<p class="mt-4 max-w-md text-pretty text-lg text-muted-foreground" data-nf="description">
|
|
2817
2856
|
{nf.description}
|
|
2818
2857
|
</p>
|
|
2819
2858
|
<a
|
|
2820
2859
|
class="mt-8 inline-flex items-center gap-1.5 rounded-full bg-accent py-2 pe-4 ps-3.5 text-sm font-medium text-accent-foreground transition-opacity hover:opacity-90"
|
|
2860
|
+
data-nf-home
|
|
2821
2861
|
href={withBase("/")}
|
|
2822
2862
|
>
|
|
2823
2863
|
<Icon class="rtl:-scale-x-100" name="arrow-left" size={14} />
|
|
2824
|
-
{nf.home}
|
|
2864
|
+
<span data-nf="home">{nf.home}</span>
|
|
2825
2865
|
</a>
|
|
2826
2866
|
</div>
|
|
2827
2867
|
{
|
|
2828
2868
|
suggestions.length > 0 && (
|
|
2829
|
-
<nav aria-label={nf.suggestions} class="md:border-s md:border-border md:ps-12 lg:ps-16">
|
|
2830
|
-
<h2 class="text-xs font-medium uppercase tracking-widest text-muted-foreground">
|
|
2869
|
+
<nav aria-label={nf.suggestions} class="md:border-s md:border-border md:ps-12 lg:ps-16" data-nf-suggestions>
|
|
2870
|
+
<h2 class="text-xs font-medium uppercase tracking-widest text-muted-foreground" data-nf="suggestions">
|
|
2831
2871
|
{nf.suggestions}
|
|
2832
2872
|
</h2>
|
|
2833
2873
|
<ul class="mt-4 divide-y divide-border border-y border-border">
|
|
@@ -2851,6 +2891,37 @@ const suggestions = [
|
|
|
2851
2891
|
)
|
|
2852
2892
|
}
|
|
2853
2893
|
</div>
|
|
2894
|
+
<script id="blume-not-found-locales" is:inline type="application/json" set:html={localizedJson} />
|
|
2895
|
+
<script is:inline>
|
|
2896
|
+
(() => {
|
|
2897
|
+
const apply = () => {
|
|
2898
|
+
const root = document.querySelector("[data-blume-not-found]");
|
|
2899
|
+
const source = document.getElementById("blume-not-found-locales");
|
|
2900
|
+
if (!root || !source) {
|
|
2901
|
+
return;
|
|
2902
|
+
}
|
|
2903
|
+
const base = root.getAttribute("data-base") || "/";
|
|
2904
|
+
const path = location.pathname.startsWith(base)
|
|
2905
|
+
? location.pathname.slice(base.length)
|
|
2906
|
+
: location.pathname.replace(/^\\/+/, "");
|
|
2907
|
+
const code = path.split("/")[0];
|
|
2908
|
+
const entry = JSON.parse(source.textContent || "{}")[code];
|
|
2909
|
+
if (!entry) {
|
|
2910
|
+
return;
|
|
2911
|
+
}
|
|
2912
|
+
root.setAttribute("lang", code);
|
|
2913
|
+
root.setAttribute("dir", entry.dir);
|
|
2914
|
+
for (const el of root.querySelectorAll("[data-nf]")) {
|
|
2915
|
+
el.textContent = entry[el.getAttribute("data-nf")];
|
|
2916
|
+
}
|
|
2917
|
+
root.querySelector("[data-nf-home]")?.setAttribute("href", entry.homeHref);
|
|
2918
|
+
root.querySelector("[data-nf-suggestions]")?.setAttribute("aria-label", entry.suggestions);
|
|
2919
|
+
document.title = entry.title;
|
|
2920
|
+
};
|
|
2921
|
+
apply();
|
|
2922
|
+
document.addEventListener("astro:page-load", apply);
|
|
2923
|
+
})();
|
|
2924
|
+
</script>
|
|
2854
2925
|
</PageLayout>
|
|
2855
2926
|
`;
|
|
2856
2927
|
|
|
@@ -2859,7 +2930,9 @@ const suggestions = [
|
|
|
2859
2930
|
* page, prerendered to `dist/404.md`. An agent that asked for a missing page
|
|
2860
2931
|
* with `Accept: text/markdown` — or fetched a `.md` URL no page backs — gets
|
|
2861
2932
|
* this body with the 404 status instead of the HTML shell; Vercel server
|
|
2862
|
-
* builds wire that into the routing config (`deploy/vercel-negotiation.ts`)
|
|
2933
|
+
* builds wire that into the routing config (`deploy/vercel-negotiation.ts`)
|
|
2934
|
+
* and Cloudflare server builds into the wrapper Worker
|
|
2935
|
+
* (`deploy/cloudflare-negotiation.ts`).
|
|
2863
2936
|
* Same recovery links as the HTML page, absolute when the site URL is known:
|
|
2864
2937
|
* the body is read out of context, so a relative link would leave the reader
|
|
2865
2938
|
* guessing the host. Written alongside `404.astro` and skipped under the same
|
|
@@ -2929,8 +3002,8 @@ export function GET() {
|
|
|
2929
3002
|
* Generate `.blume/src/pages/404.json.ts`: the JSON twin of the default 404
|
|
2930
3003
|
* page, prerendered to `dist/404.json` as RFC 9457 problem details. An agent
|
|
2931
3004
|
* that asked for a missing page with `Accept: application/json` gets this body
|
|
2932
|
-
* with the 404 status instead of the HTML shell (Vercel
|
|
2933
|
-
* that into the
|
|
3005
|
+
* with the 404 status instead of the HTML shell (Vercel and Cloudflare server
|
|
3006
|
+
* builds wire that into the deploy, like the Markdown twin). Same recovery links
|
|
2934
3007
|
* as the other variants, carried as `links` and spelled out in `resolution`.
|
|
2935
3008
|
* Written alongside `404.astro` and skipped under the same rule.
|
|
2936
3009
|
*/
|
|
@@ -3079,71 +3152,6 @@ const context = ${JSON.stringify(context)};
|
|
|
3079
3152
|
export const ALL: APIRoute = ({ request }) => apiNotFoundResponse(request, context);
|
|
3080
3153
|
`;
|
|
3081
3154
|
|
|
3082
|
-
/** The literal Astro hydration directive for an island's client mode. */
|
|
3083
|
-
const islandDirective = (spec: IslandSpec): string =>
|
|
3084
|
-
spec.client === "only"
|
|
3085
|
-
? `client:only="${spec.framework}"`
|
|
3086
|
-
: `client:${spec.client}`;
|
|
3087
|
-
|
|
3088
|
-
/**
|
|
3089
|
-
* Frontmatter `Props` alias mirroring the wrapped component's own props, so
|
|
3090
|
-
* `{...Astro.props}` satisfies required props under `astro check` (the spread
|
|
3091
|
-
* of an untyped `Astro.props` contributes nothing to the JSX props type).
|
|
3092
|
-
* `infer P extends object` rather than `Record<string, unknown>` because
|
|
3093
|
-
* interfaces have no implicit index signature and would miss the narrower
|
|
3094
|
-
* constraint. Non-function component types (Vue/Svelte ambient modules) fall
|
|
3095
|
-
* back to an open record, keeping the untyped permissiveness they had.
|
|
3096
|
-
*/
|
|
3097
|
-
const wrapperPropsType = (name: string): string =>
|
|
3098
|
-
`type Props = typeof ${name} extends (
|
|
3099
|
-
props: infer P extends object,
|
|
3100
|
-
...rest: never[]
|
|
3101
|
-
) => unknown
|
|
3102
|
-
? P
|
|
3103
|
-
: Record<string, unknown>;`;
|
|
3104
|
-
|
|
3105
|
-
/**
|
|
3106
|
-
* Generate `.blume/src/generated/islands/<Name>.astro` — a wrapper that renders
|
|
3107
|
-
* a convention island with its hydration directive applied. Astro client
|
|
3108
|
-
* directives must be written statically, so one wrapper is emitted per island;
|
|
3109
|
-
* props and the default slot (MDX children) forward through.
|
|
3110
|
-
*/
|
|
3111
|
-
export const islandWrapperTemplate = (spec: IslandSpec): string =>
|
|
3112
|
-
`---
|
|
3113
|
-
// Generated by Blume. Do not edit.
|
|
3114
|
-
import Island from ${JSON.stringify(spec.file)};
|
|
3115
|
-
${wrapperPropsType("Island")}
|
|
3116
|
-
---
|
|
3117
|
-
<Island ${islandDirective(spec)} {...Astro.props}><slot /></Island>
|
|
3118
|
-
`;
|
|
3119
|
-
|
|
3120
|
-
/**
|
|
3121
|
-
* Generate `.blume/src/generated/islands.ts` — the map of island names to their
|
|
3122
|
-
* wrappers, spread into the MDX component scope by the catch-all page. Always
|
|
3123
|
-
* written (an empty map when there are no islands) so the import resolves.
|
|
3124
|
-
*/
|
|
3125
|
-
export const islandMapTemplate = (specs: IslandSpec[]): string => {
|
|
3126
|
-
if (specs.length === 0) {
|
|
3127
|
-
return `// Generated by Blume. Do not edit.
|
|
3128
|
-
export const islandComponents = {};
|
|
3129
|
-
`;
|
|
3130
|
-
}
|
|
3131
|
-
const imports = specs
|
|
3132
|
-
.map(
|
|
3133
|
-
(spec, index) => `import I${index} from "./islands/${spec.name}.astro";`
|
|
3134
|
-
)
|
|
3135
|
-
.join("\n");
|
|
3136
|
-
const entries = specs
|
|
3137
|
-
.map((spec, index) => ` ${spec.name}: I${index},`)
|
|
3138
|
-
.join("\n");
|
|
3139
|
-
return `// Generated by Blume. Do not edit.
|
|
3140
|
-
${imports}
|
|
3141
|
-
export const islandComponents = {
|
|
3142
|
-
${entries}
|
|
3143
|
-
};
|
|
3144
|
-
`;
|
|
3145
|
-
};
|
|
3146
|
-
|
|
3147
3155
|
/** The literal Astro hydration directive for an example's framework/client. */
|
|
3148
3156
|
const exampleDirective = (spec: ExampleSpec): string => {
|
|
3149
3157
|
if (spec.framework === "astro" || !spec.client) {
|
|
@@ -3169,12 +3177,17 @@ export const exampleSlug = (path: string): string =>
|
|
|
3169
3177
|
/**
|
|
3170
3178
|
* Generate `.blume/src/generated/examples/<slug>.astro` — a wrapper that renders
|
|
3171
3179
|
* one example live, with its hydration directive applied (none for `.astro`).
|
|
3172
|
-
* Mirrors
|
|
3180
|
+
* Mirrors the component-slot wrappers (`component-slots.ts`); `<Component>`
|
|
3181
|
+
* resolves these by path.
|
|
3173
3182
|
*/
|
|
3174
|
-
export const exampleWrapperTemplate = (
|
|
3183
|
+
export const exampleWrapperTemplate = (
|
|
3184
|
+
spec: ExampleSpec,
|
|
3185
|
+
/** The wrapper's directory, when its import must be relative (eject). */
|
|
3186
|
+
fromDir?: string
|
|
3187
|
+
): string =>
|
|
3175
3188
|
`---
|
|
3176
3189
|
// Generated by Blume. Do not edit.
|
|
3177
|
-
import Example from ${JSON.stringify(spec.file)};
|
|
3190
|
+
import Example from ${JSON.stringify(importSpecifier(spec.file, fromDir))};
|
|
3178
3191
|
${wrapperPropsType("Example")}
|
|
3179
3192
|
---
|
|
3180
3193
|
<Example ${exampleDirective(spec)}{...Astro.props}><slot /></Example>
|