blume 1.7.2 → 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 +227 -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-xhtpx3ff.js → chunk-1w8dp3qb.js} +17 -16
- package/dist/cli/{chunk-xhtpx3ff.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-9he6crym.js → chunk-6k8vp3ta.js} +25 -13
- package/dist/cli/{chunk-9he6crym.js.map → chunk-6k8vp3ta.js.map} +4 -4
- package/dist/cli/{chunk-mt76t7dj.js → chunk-79njf86q.js} +133 -54
- package/dist/cli/chunk-79njf86q.js.map +11 -0
- package/dist/cli/{chunk-3w7b2vcx.js → chunk-7ez8ny0t.js} +2 -2
- package/dist/cli/{chunk-688e0dde.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-hs3gbh8p.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-9bkjd11x.js → chunk-bw22s759.js} +15 -5
- package/dist/cli/{chunk-9bkjd11x.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-t3tj0dgr.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-exeeb35e.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-12dxjqk7.js → chunk-fh5hj5jt.js} +44 -21
- package/dist/cli/chunk-fh5hj5jt.js.map +10 -0
- package/dist/cli/{chunk-8cd8tj54.js → chunk-j8mw0za6.js} +86 -57
- package/dist/cli/chunk-j8mw0za6.js.map +35 -0
- package/dist/cli/chunk-jts8mvcz.js +106 -0
- package/dist/cli/{chunk-2mzebbbz.js.map → chunk-jts8mvcz.js.map} +6 -4
- package/dist/cli/{chunk-ejjx8znq.js → chunk-mnqj32sj.js} +505 -536
- package/dist/cli/chunk-mnqj32sj.js.map +12 -0
- package/dist/cli/{chunk-196vjxp9.js → chunk-mwt1k8n7.js} +100 -372
- 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-n9sra6sy.js → chunk-pat2zzwc.js} +10 -14
- package/dist/cli/{chunk-n9sra6sy.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-cvky9gb2.js → chunk-sqn5t4q0.js} +81 -87
- package/dist/cli/chunk-sqn5t4q0.js.map +10 -0
- package/dist/cli/{chunk-eevwt1sc.js → chunk-tzne8qfq.js} +15 -15
- package/dist/cli/{chunk-eevwt1sc.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-5n7t497w.js → chunk-z1f5arsg.js} +247 -755
- package/dist/cli/chunk-z1f5arsg.js.map +36 -0
- package/dist/cli/{chunk-hdm2dkd2.js → chunk-zg2gtj10.js} +1086 -2596
- package/dist/cli/chunk-zg2gtj10.js.map +35 -0
- package/dist/cli/{chunk-ppfvdcd4.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 +209 -505
- 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 +28 -11
- 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 +2291 -441
- 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 +20 -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 +176 -61
- package/docs/configuration/customization.mdx +15 -9
- package/docs/configuration/index.mdx +52 -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 +37 -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 +12 -7
- package/docs/content/sources.mdx +145 -50
- package/docs/content/syntax.mdx +26 -12
- package/docs/content/versioning.mdx +3 -3
- package/docs/discoverability/agent-discovery.mdx +91 -10
- package/docs/discoverability/index.mdx +5 -5
- 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 +26 -22
- package/src/ai/ai-catalog.ts +259 -0
- 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 +19 -6
- package/src/ai/llms.ts +26 -15
- package/src/ai/markdown.ts +35 -5
- package/src/ai/mcp/data.ts +9 -5
- package/src/ai/mcp/discovery.ts +1 -1
- 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 +114 -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 -314
- 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 +75 -3
- 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 -43
- package/src/components/layout/Search.astro +30 -6
- package/src/components/layout/WebMcp.astro +1 -1
- package/src/components/layout/analytics-client.ts +101 -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 +212 -543
- package/src/core/config.ts +130 -41
- package/src/core/custom-pages.ts +105 -0
- package/src/core/data.ts +32 -11
- 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 +137 -19
- 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 +531 -721
- 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 +20 -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 +67 -28
- package/src/deploy/cloudflare-negotiation.ts +179 -100
- package/src/deploy/function-bundle.ts +18 -3
- package/src/deploy/headers.ts +129 -22
- 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 +27 -4
- 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 +33 -7
- 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 +47 -2
- package/src/translate/report.ts +40 -7
- package/src/upgrade/upgrade.ts +499 -0
- package/dist/cli/chunk-12dxjqk7.js.map +0 -10
- package/dist/cli/chunk-196vjxp9.js.map +0 -13
- package/dist/cli/chunk-2mzebbbz.js +0 -69
- package/dist/cli/chunk-2z47ypj8.js.map +0 -11
- package/dist/cli/chunk-30e87n55.js +0 -108
- package/dist/cli/chunk-30e87n55.js.map +0 -10
- package/dist/cli/chunk-450a7rcr.js +0 -185
- package/dist/cli/chunk-450a7rcr.js.map +0 -11
- package/dist/cli/chunk-5n7t497w.js.map +0 -40
- package/dist/cli/chunk-61j18dwk.js +0 -5342
- package/dist/cli/chunk-61j18dwk.js.map +0 -58
- package/dist/cli/chunk-88by27n5.js +0 -17
- package/dist/cli/chunk-88by27n5.js.map +0 -10
- package/dist/cli/chunk-8cd8tj54.js.map +0 -34
- package/dist/cli/chunk-aztttvb3.js +0 -381
- package/dist/cli/chunk-aztttvb3.js.map +0 -12
- package/dist/cli/chunk-cvky9gb2.js.map +0 -11
- package/dist/cli/chunk-ejjx8znq.js.map +0 -15
- package/dist/cli/chunk-fmceyezb.js +0 -1007
- package/dist/cli/chunk-fmceyezb.js.map +0 -13
- package/dist/cli/chunk-hdm2dkd2.js.map +0 -48
- package/dist/cli/chunk-hs3gbh8p.js.map +0 -10
- package/dist/cli/chunk-jbj4qhfw.js +0 -30
- package/dist/cli/chunk-jbj4qhfw.js.map +0 -10
- package/dist/cli/chunk-mqb2ka8m.js +0 -68
- package/dist/cli/chunk-mqb2ka8m.js.map +0 -10
- package/dist/cli/chunk-mt76t7dj.js.map +0 -11
- package/dist/cli/chunk-q4rae3bg.js +0 -60
- package/dist/cli/chunk-q4rae3bg.js.map +0 -10
- package/dist/cli/chunk-ra1v2nc2.js +0 -35
- package/dist/cli/chunk-ra1v2nc2.js.map +0 -10
- package/dist/cli/chunk-t3tj0dgr.js.map +0 -15
- package/dist/cli/chunk-tqa1s0k8.js +0 -69
- package/dist/cli/chunk-tqa1s0k8.js.map +0 -11
- package/dist/cli/chunk-vh9w1sgp.js +0 -73
- package/dist/cli/chunk-vh9w1sgp.js.map +0 -10
- package/dist/cli/chunk-vkrsvbr5.js +0 -107
- package/dist/cli/chunk-vkrsvbr5.js.map +0 -11
- package/dist/cli/chunk-vrfp10qk.js +0 -81
- package/dist/cli/chunk-vrfp10qk.js.map +0 -10
- package/dist/cli/chunk-wjt80jps.js +0 -1049
- package/dist/cli/chunk-wjt80jps.js.map +0 -24
- 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-3w7b2vcx.js.map → chunk-7ez8ny0t.js.map} +0 -0
- /package/dist/cli/{chunk-688e0dde.js.map → chunk-88cpgt6h.js.map} +0 -0
- /package/dist/cli/{chunk-exeeb35e.js.map → chunk-f7t03s3g.js.map} +0 -0
- /package/dist/cli/{chunk-ppfvdcd4.js.map → chunk-zxccj738.js.map} +0 -0
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../src/ai/markdown.ts", "../src/ai/component-markdown.ts", "../src/markdown/features.ts", "../src/deploy/rss.ts", "../src/ai/openapi-components.ts", "../src/ai/serializers.ts", "../src/ai/skills.ts", "../src/ai/tar.ts", "../src/ai/visibility.ts", "../src/ai/llms.ts", "../src/search/documents.ts", "../src/markdown/fence-meta.ts", "../src/markdown/code-title.ts", "../src/search/facets.ts", "../src/ai/mcp/data.ts"],
|
|
4
|
-
"sourcesContent": [
|
|
5
|
-
"import { readFile } from \"node:fs/promises\";\n\nimport { rewriteRelativeImages } from \"../core/content-assets.ts\";\nimport matter from \"../core/frontmatter.ts\";\nimport type { BlumeProject } from \"../core/project-graph.ts\";\nimport { readExpandedEntryText } from \"../core/sources/read.ts\";\nimport type { RouteManifestEntry } from \"../core/types.ts\";\nimport { downlevelComponents } from \"./component-markdown.ts\";\nimport { buildLlmsIndex } from \"./llms.ts\";\nimport { projectComponentSerializers } from \"./serializers.ts\";\nimport { applyAgentVisibility } from \"./visibility.ts\";\n\n/** One route's raw-Markdown variants. */\nexport interface RawMarkdownEntry {\n /**\n * The agent-facing Markdown served at `/<route>.md`: supported components\n * downleveled to plain Markdown (`<TypeTable>` → table, `<Callout>` →\n * blockquote, …). Present only when downleveling changed something, so\n * component-free pages aren't stored twice.\n */\n md?: string;\n /** The original source, served verbatim at `/<route>.mdx`. */\n mdx: string;\n}\n\n/** The Markdown an agent should read for a route. */\nexport const agentMarkdown = (entry: RawMarkdownEntry): string =>\n entry.md ?? entry.mdx;\n\n/**\n * Estimated token count of a Markdown document, for the `x-markdown-tokens`\n * response header (the convention Cloudflare's Markdown for Agents ships; the\n * tokenizer is unspecified there too, so this is the common ~4-characters-\n * per-token estimate). Kept in sync with the inline computation in\n * `rawMarkdownEndpointTemplate`, which can't import build-time code.\n */\nexport const markdownTokenCount = (text: string): number =>\n Math.ceil(text.length / 4);\n\n/**\n * Map every route to its raw source Markdown. Powers the `<route>.md` and\n * `<route>.mdx` endpoints: `.mdx` serves the original source so tools can see\n * exactly what the author wrote, while `.md` downlevels supported components\n * to plain Markdown for consumers that can't interpret JSX. `<Visibility>`\n * audiences are resolved for agents in both variants: web-only content is\n * removed, agents-only unwrapped. Relative image references are rewritten to\n * their served `/blume-assets/content/…` URLs in both variants too — an agent\n * fetches these endpoints by URL, where a colocated `./diagram.png` resolves\n * to nothing.\n */\nexport const buildRawMarkdown = async (\n project: BlumeProject\n): Promise<Record<string, RawMarkdownEntry>> => {\n const pageById = new Map(project.graph.pages.map((page) => [page.id, page]));\n\n const components = projectComponentSerializers(project);\n\n const readRoute = async (route: RouteManifestEntry): Promise<string> => {\n const page = pageById.get(route.id);\n if (page) {\n return await readExpandedEntryText(project, page);\n }\n return route.sourcePath ? await readFile(route.sourcePath, \"utf-8\") : \"\";\n };\n\n const entries = await Promise.all(\n project.manifest.routes.map(async (route) => {\n let text = await readRoute(route);\n if (route.sourcePath) {\n text = rewriteRelativeImages({\n deployBase: project.config.deployment.base,\n projectRoot: project.context.root,\n source: text,\n sourcePath: route.sourcePath,\n });\n }\n const source = applyAgentVisibility(text);\n // The `.md` variant keeps the front-matter block in the output, but its\n // data must also be in scope for `prop={frontmatter.*}` expressions.\n const md = downlevelComponents(source, components, matter(source).data);\n const entry: RawMarkdownEntry =\n md === source ? { mdx: source } : { md, mdx: source };\n return [route.path, entry] as const;\n })\n );\n const map = Object.fromEntries(entries);\n // A landing-page homepage (user `.astro` page, or no home route at all) has\n // no Markdown source, but agents negotiating `Accept: text/markdown` on `/`\n // still expect a Markdown answer. The llms.txt index — the machine-readable\n // representation of the site a landing page fronts — becomes its mirror, so\n // `/index.md` always exists (see `markdownRoutePaths`).\n if (!map[\"/\"]) {\n map[\"/\"] = { mdx: buildLlmsIndex(project) };\n }\n return map;\n};\n\n/**\n * Every route path with a raw-Markdown mirror: the manifest routes, plus the\n * homepage when its mirror is the synthesized llms.txt fallback (see\n * `buildRawMarkdown`). This is the route list the negotiation surfaces (dev\n * middleware, Vercel routing config) and the homepage `Link` header build\n * from, so `Accept: text/markdown` on `/` resolves even when the homepage is\n * a landing page.\n */\nexport const markdownRoutePaths = (project: BlumeProject): string[] => {\n const paths = project.manifest.routes.map((route) => route.path);\n return paths.includes(\"/\") ? paths : [...paths, \"/\"];\n};\n",
|
|
6
|
-
"import { markdownTable } from \"markdown-table\";\nimport { mdxToMdast } from \"satteri\";\n\nimport { parseYouTubeId } from \"../components/content/youtube.ts\";\nimport type { ExampleLookup } from \"../core/types.ts\";\nimport { MDX_FEATURES } from \"../markdown/features.ts\";\n\n/**\n * Downlevel Blume's MDX components to plain Markdown for agent-facing output\n * (the `/<route>.md` mirror, llms-full.txt, MCP `get_page`). Each supported\n * component has a serializer — its \"toString\" — that renders the same\n * information as the Astro component, but as portable Markdown: `<TypeTable>`\n * becomes a GFM table, `<Callout>` a blockquote, `<Steps>` an ordered list,\n * `<Tabs>` labeled sections, `<YouTube>` a link.\n *\n * The transform is a position splice, not a re-stringify: the MDX source is\n * parsed to MDAST (via Satteri, which stamps byte offsets on every node) and\n * only the matched component ranges are replaced, so all surrounding Markdown\n * stays byte-identical to what the author wrote. Anything the serializers\n * can't faithfully convert — an unknown component, a prop bound to an import —\n * is left as JSX, and fenced code that merely *shows* component markup is\n * naturally untouched because it parses as a `code` node.\n */\n\n/** Minimal structural MDAST types — we model only what this module reads. */\ninterface Offset {\n offset: number;\n}\n\n/** The structural slice of an mdast node the downlevel walk reads. */\nexport interface MdastNode {\n attributes?: MdxAttribute[];\n children?: MdastNode[];\n name?: string;\n position?: { end: Offset; start: Offset };\n type: string;\n}\n\ninterface MdxAttribute {\n name?: string;\n type: string;\n value?: string | { type: string; value: string } | null;\n}\n\n/** A single source replacement: `[start, end)` byte range → `text`. */\ninterface Splice {\n end: number;\n start: number;\n text: string;\n}\n\n/**\n * A statically-recovered data value. Parsed front matter and evaluated\n * attribute literals are both plain data — scalars, dates, arrays, and\n * nested maps — never functions or class instances.\n */\nexport type EvaluatedValue =\n | string\n | number\n | boolean\n | null\n | undefined\n | Date\n | EvaluatedValue[]\n | { [key: string]: EvaluatedValue };\n\nexport const isString = <Value>(value: Value): value is Value & string =>\n typeof value === \"string\";\n\nconst isNumber = <Value>(value: Value): value is Value & number =>\n typeof value === \"number\";\n\n/** Evaluated props plus whether any attribute resisted static evaluation. */\ninterface EvaluatedProps {\n lossy: boolean;\n props: Record<string, EvaluatedValue>;\n}\n\n/** A child component extracted by name (e.g. each `<Step>` under `<Steps>`). */\nexport interface ComponentMarkdownChild extends EvaluatedProps {\n /** The child's body, downleveled and dedented. */\n children: string;\n}\n\n/** One direct child of a component — a child component or prose — as Markdown. */\nexport interface ComponentMarkdownBlock {\n /**\n * The child downleveled: a serializable component's rendering (through the\n * registry, so a user override of that component applies), or — for prose,\n * a component with no serializer, or one that declined — its source with\n * any serializable descendants downleveled in place.\n */\n markdown: string;\n /** The JSX name of a child component; `undefined` for prose. */\n name?: string;\n}\n\n/** What a serializer receives for one component usage. */\nexport interface ComponentMarkdownContext extends EvaluatedProps {\n /**\n * Every direct child of the element in document order, components and\n * prose alike, each as a block of Markdown. For a container that is nothing\n * but its contents — `<CardGroup>` — joining these with blank lines is the\n * whole serializer.\n */\n childBlocks: () => ComponentMarkdownBlock[];\n /** Direct child components of `name`, each with evaluated props and body. */\n childComponents: (name: string) => ComponentMarkdownChild[];\n /** The element's body, downleveled and dedented (empty if self-closing). */\n children: string;\n /**\n * The page's parsed front-matter (empty when the caller has none). Lets a\n * serializer read page metadata directly, even when a prop expression is\n * not statically evaluable.\n */\n frontmatter: Record<string, EvaluatedValue>;\n}\n\n/**\n * A component's Markdown serializer. Return the replacement Markdown, or\n * `null` to leave the component's JSX in the output verbatim (the safe\n * fallback when the props can't be recovered statically).\n */\nexport type ComponentMarkdown = (\n context: ComponentMarkdownContext\n) => string | null;\n\n/**\n * Statically evaluate an MDX attribute expression (`prop={...}`). Component\n * data props are object/array/number literals in practice; evaluation runs at\n * build time over the author's own content — the same trust level as the MDX\n * itself, which Astro compiles and executes. The page's `frontmatter` is in\n * scope, mirroring what Astro provides an MDX body at render time, so\n * `prop={frontmatter.status}` resolves; expressions that reference imports or\n * other scope throw and report as not evaluable.\n */\nconst evaluateExpression = (\n raw: string,\n frontmatter: Record<string, EvaluatedValue> | undefined\n) => {\n try {\n // Build-time eval of the author's own attribute literals; a throw falls\n // back to leaving the JSX verbatim.\n // oxlint-disable-next-line no-new-func\n const value: EvaluatedValue = new Function(\n \"frontmatter\",\n `\"use strict\"; return (${raw});`\n )(frontmatter);\n return { ok: true, value };\n } catch {\n return { ok: false, value: undefined };\n }\n};\n\n/** Evaluate an element's attributes into a plain props object. */\nconst readProps = (\n node: MdastNode,\n frontmatter: Record<string, EvaluatedValue> | undefined\n): EvaluatedProps => {\n const props: Record<string, EvaluatedValue> = {};\n let lossy = false;\n for (const attribute of node.attributes ?? []) {\n // A spread ({...props}) can't be recovered statically.\n if (attribute.type !== \"mdxJsxAttribute\" || !attribute.name) {\n lossy = true;\n continue;\n }\n if (attribute.value === null || attribute.value === undefined) {\n // Boolean shorthand: `<Steps compact>`.\n props[attribute.name] = true;\n } else if (isString(attribute.value)) {\n props[attribute.name] = attribute.value;\n } else {\n const result = evaluateExpression(attribute.value.value, frontmatter);\n if (result.ok) {\n props[attribute.name] = result.value;\n } else {\n lossy = true;\n }\n }\n }\n return { lossy, props };\n};\n\n/** A node Satteri stamped with byte offsets. */\ntype Positioned = MdastNode & { position: { end: Offset; start: Offset } };\n\nconst hasOffsets = (node: MdastNode): node is Positioned =>\n typeof node.position?.start?.offset === \"number\" &&\n typeof node.position?.end?.offset === \"number\";\n\n/** Apply non-overlapping splices to `text` (offsets relative to `text`). */\nconst applySplices = (text: string, splices: Splice[]): string => {\n let result = text;\n for (const splice of [...splices].toSorted((a, b) => b.start - a.start)) {\n // An element indented under a parent starts mid-line after whitespace;\n // repeat that indent on the replacement's continuation lines so the block\n // stays uniformly indented (and dedents cleanly with its siblings).\n const lineStart = result.lastIndexOf(\"\\n\", splice.start - 1) + 1;\n const prefix = result.slice(lineStart, splice.start);\n const indent = /^[\\t ]+$/u.test(prefix) ? prefix : \"\";\n const replacement = indent\n ? splice.text\n .split(\"\\n\")\n .map((line, index) =>\n index === 0 || line === \"\" ? line : `${indent}${line}`\n )\n .join(\"\\n\")\n : splice.text;\n result =\n result.slice(0, splice.start) + replacement + result.slice(splice.end);\n }\n return result;\n};\n\n/**\n * Strip the common indentation JSX children carry in source (`<Step>` bodies\n * are typically indented two spaces under their tag). The first line starts\n * mid-line at the slice boundary, so the common prefix is measured on the\n * following lines only.\n */\nconst dedent = (text: string): string => {\n const lines = text.split(\"\\n\");\n const rest = lines.slice(1).filter((line) => line.trim() !== \"\");\n if (rest.length === 0) {\n return text;\n }\n const indent = Math.min(\n ...rest.map((line) => line.length - line.trimStart().length)\n );\n if (indent === 0) {\n return text;\n }\n return [\n lines[0],\n ...lines\n .slice(1)\n .map((line) => (line.trim() === \"\" ? \"\" : line.slice(indent))),\n ].join(\"\\n\");\n};\n\nconst isJsxElement = (node: MdastNode): boolean =>\n node.type === \"mdxJsxFlowElement\" || node.type === \"mdxJsxTextElement\";\n\n/** Flatten a value to a single Markdown table cell (pipes escaped). */\nconst cellText = (value: EvaluatedValue): string =>\n String(value ?? \"\")\n .replaceAll(/\\s*\\n\\s*/gu, \" \")\n .replaceAll(\"|\", \"\\\\|\")\n .trim();\n\n/** A cell rendered as inline code, unless the value itself uses backticks. */\nconst cellCode = (value: EvaluatedValue): string => {\n const text = cellText(value);\n return text && !text.includes(\"`\") ? `\\`${text}\\`` : text;\n};\n\n/**\n * One `<TypeTable type={{...}}>` entry, matching the component's props. The\n * index signature keeps the interface interchangeable with the evaluated\n * data-value maps it is narrowed from.\n */\ninterface TypeEntry {\n [field: string]: EvaluatedValue;\n default?: EvaluatedValue;\n description?: EvaluatedValue;\n required?: EvaluatedValue;\n type?: EvaluatedValue;\n typeDescription?: EvaluatedValue;\n typeDescriptionLink?: EvaluatedValue;\n}\n\n/**\n * The `type` data prop's entry map. Structural only: each entry's fields are\n * rendered through cellText/cellCode, which stringify any value.\n */\nconst isTypeEntryMap = (\n value: EvaluatedValue\n): value is Record<string, TypeEntry> =>\n typeof value === \"object\" && value !== null;\n\nconst typeTable: ComponentMarkdown = ({ children, props }) => {\n const { type } = props;\n if (!isTypeEntryMap(type)) {\n // The data prop is missing or wasn't statically evaluable.\n return null;\n }\n const entries = Object.entries(type);\n const rows = entries.map(([name, info]) => {\n const prop = cellCode(`${name}${info.required ? \"\" : \"?\"}`);\n const typeCell = info.typeDescriptionLink\n ? `[${cellCode(info.type)}](${cellText(info.typeDescriptionLink)})`\n : cellCode(info.type);\n const defaultCell =\n info.default === undefined ? \"-\" : cellCode(info.default);\n const description = cellText(\n [info.description, info.typeDescription]\n .filter((part) => isString(part) && part !== \"\")\n .join(\" \")\n );\n return [prop, typeCell, defaultCell, description];\n });\n const table =\n rows.length > 0\n ? markdownTable([[\"Prop\", \"Type\", \"Default\", \"Description\"], ...rows], {\n // Unpadded columns: cells hold prose and the output is for model\n // consumption, so aligned delimiter rows are wasted tokens.\n alignDelimiters: false,\n })\n : \"\";\n // The component renders its slot after the table.\n return [table, children].filter(Boolean).join(\"\\n\\n\");\n};\n\nconst callout: ComponentMarkdown = ({ children, props }) => {\n const type = isString(props.type) ? props.type : \"info\";\n const label =\n isString(props.title) && props.title !== \"\"\n ? props.title\n : type.charAt(0).toUpperCase() + type.slice(1);\n if (!children) {\n return `> **${label}**`;\n }\n const body = children\n .split(\"\\n\")\n .map((line) => (line.trim() === \"\" ? \">\" : `> ${line}`))\n .join(\"\\n\");\n return `> **${label}**\\n>\\n${body}`;\n};\n\n/** Format `content` as one ordered-list item, continuation lines indented. */\nconst listItem = (index: number, content: string): string => {\n const marker = `${index}. `;\n return content\n .split(\"\\n\")\n .map((line, lineIndex) => {\n if (lineIndex === 0) {\n return `${marker}${line}`;\n }\n return line === \"\" ? \"\" : ` ${line}`;\n })\n .join(\"\\n\");\n};\n\nconst steps: ComponentMarkdown = ({ childComponents, children }) => {\n const items = childComponents(\"Step\");\n if (items.length === 0) {\n return children;\n }\n return items\n .map((step, index) => {\n const title =\n isString(step.props.title) && step.props.title !== \"\"\n ? `**${step.props.title}**`\n : \"\";\n const content = [title, step.children].filter(Boolean).join(\"\\n\\n\");\n return listItem(index + 1, content);\n })\n .join(\"\\n\\n\");\n};\n\nconst tabs: ComponentMarkdown = ({ childComponents, children }) => {\n const items = childComponents(\"Tab\");\n if (items.length === 0) {\n return children;\n }\n return items\n .map((tab, index) => {\n const title =\n isString(tab.props.title) && tab.props.title !== \"\"\n ? tab.props.title\n : `Tab ${index + 1}`;\n return tab.children ? `**${title}**\\n\\n${tab.children}` : `**${title}**`;\n })\n .join(\"\\n\\n\");\n};\n\n/** Escape the brackets that would end a link's text early. */\nconst linkText = (value: string): string =>\n value.replaceAll(/[[\\]]/gu, String.raw`\\$&`);\n\n/**\n * A link destination. Whitespace ends one, and a `)` ends one unless it is\n * part of a balanced pair — so an href carrying either goes in the angle\n * bracket form, where only `<` and `>` are special.\n */\nexport const linkDestination = (href: string): string =>\n /[\\s()<>]/u.test(href)\n ? `<${href.replaceAll(/[<>]/gu, String.raw`\\$&`)}>`\n : href;\n\n/** A text prop: a string, or a number stringified — `title={2024}` is a title. */\nconst textProp = (value: EvaluatedValue): string => {\n if (isString(value)) {\n return value.trim();\n }\n return isNumber(value) ? String(value) : \"\";\n};\n\n/**\n * A card is a link with a blurb, so that is what it becomes: the title as the\n * link text, the body under it, and the call to action last — the order the\n * card renders in. Shaped like {@link tabs}, a bold label over the body,\n * rather than a heading: a card sits inside a page whose outline its author\n * wrote, and a heading would add a level to it.\n *\n * The icon and image are presentation and drop out. A card with no title\n * falls back to its `href`, and one with neither is just its body. `lossy`\n * declines: a title or href recovered from an expression that would not\n * evaluate is a link pointing somewhere wrong, which is worse than the\n * visible JSX.\n */\nconst card: ComponentMarkdown = ({ children, lossy, props }) => {\n if (lossy) {\n return null;\n }\n const title = textProp(props.title);\n const href = isString(props.href) ? props.href.trim() : \"\";\n const body = children.trim();\n const cta = textProp(props.cta);\n const label = title || href;\n if (label === \"\") {\n // Nothing to head the card with, so it is whatever text it carries.\n const rest = [body, cta].filter(Boolean).join(\"\\n\\n\");\n // Nothing but presentation left — keep the JSX rather than delete a card.\n return rest === \"\" ? null : rest;\n }\n const head =\n href === \"\"\n ? `**${label}**`\n : `**[${linkText(label)}](${linkDestination(href)})**`;\n return [head, body, cta].filter(Boolean).join(\"\\n\\n\");\n};\n\n/**\n * `CardGroup` is the grid its cards sit in and carries no meaning of its own,\n * so it becomes its contents: every direct child in order, each a block, a\n * blank line between them. Built on `childBlocks` rather than extracting the\n * cards so nothing the group holds is lost — a `Card` renders through the\n * registry (a user override of `Card` applies in here too), a nested group\n * recurses, prose between the cards stays, and a card that declines keeps its\n * JSX as a block of its own instead of vanishing beside a rendered sibling.\n * Passing the body slice through instead would leave each card's source\n * indentation in the output and run one card's title straight on from the\n * previous card's body as the same paragraph.\n */\nconst cardGroup: ComponentMarkdown = ({ childBlocks }) =>\n childBlocks()\n .map((block) => block.markdown)\n .join(\"\\n\\n\");\n\nconst youtube: ComponentMarkdown = ({ props }) => {\n let input = \"\";\n if (isString(props.id)) {\n input = props.id;\n } else if (isString(props.url)) {\n input = props.url;\n }\n const videoId = parseYouTubeId(input);\n if (!videoId) {\n return null;\n }\n const start =\n isNumber(props.start) && props.start > 0\n ? `&t=${Math.floor(props.start)}s`\n : \"\";\n const title =\n isString(props.title) && props.title !== \"\"\n ? props.title\n : \"Watch on YouTube\";\n return `[${title}](https://www.youtube.com/watch?v=${videoId}${start})`;\n};\n\n/**\n * `text` as an inline code span whose delimiter outlengths any backtick run\n * inside it, padded with a space when the content starts or ends with a\n * backtick (CommonMark strips one such pair) — so a spec path or address\n * carrying a backtick cannot close the span early.\n */\nexport const inlineCode = (text: string): string => {\n const runs = text.match(/`+/gu);\n const longest = runs ? Math.max(...runs.map((run) => run.length)) : 0;\n const delimiter = \"`\".repeat(longest + 1);\n const padded =\n text.startsWith(\"`\") || text.endsWith(\"`\") ? ` ${text} ` : text;\n return `${delimiter}${padded}${delimiter}`;\n};\n\n/** Fence `code` so its opening/closing run outlengths any backticks inside. */\nconst fencedBlock = (lang: string, code: string): string => {\n const trimmed = code.replace(/(?<!\\n)\\n+$/u, \"\");\n const runs = trimmed.match(/`+/gu);\n const longest = runs ? Math.max(...runs.map((run) => run.length)) : 0;\n const fence = \"`\".repeat(Math.max(3, longest + 1));\n return `${fence}${lang}\\n${trimmed}\\n${fence}`;\n};\n\n/**\n * Build the `<Component>` serializer for a project's discovered examples. The\n * live preview can't survive the trip to Markdown, so the agent-facing output\n * carries the example's source — the same code the \"Code\" tab shows — as a\n * fenced block. An unknown `path` (or a missing `path` prop) declines, leaving\n * the JSX verbatim, mirroring the \"no example found\" note the component renders\n * on the page.\n */\nexport const exampleComponentSerializers = (examples: ExampleLookup) =>\n ({\n Component: ({ props }) => {\n const path = isString(props.path) ? props.path : undefined;\n const example = path === undefined ? undefined : examples[path];\n return example ? fencedBlock(example.lang, example.source) : null;\n },\n }) satisfies Record<string, ComponentMarkdown>;\n\n/**\n * The built-in serializer registry, keyed by JSX name. `Step` and `Tab` are\n * intentionally absent: they only carry meaning inside their containers,\n * which extract them via `childComponents`; a stray one stays verbatim.\n */\nconst SERIALIZERS = {\n Callout: callout,\n Card: card,\n CardGroup: cardGroup,\n Steps: steps,\n Tabs: tabs,\n TypeTable: typeTable,\n YouTube: youtube,\n} satisfies Record<string, ComponentMarkdown>;\n\nconst escapeRegExp = (value: string): string =>\n value.replaceAll(/[$()*+.?[\\\\\\]^{|}]/gu, String.raw`\\$&`);\n\n// Skip the MDX parse when no serializable component name appears at all —\n// the common case for prose pages, and it keeps plain-Markdown sources (where\n// `<`/`{` may be literal text MDX would reject) out of the parser entirely.\nconst componentHint = (registry: Record<string, ComponentMarkdown>): RegExp =>\n new RegExp(\n `<(?:${Object.keys(registry).map(escapeRegExp).join(\"|\")})[\\\\s/>]`,\n \"u\"\n );\n\nconst BUILT_IN_HINT = componentHint(SERIALIZERS);\n\n/** One downlevel pass's inputs: the source, registry, and page metadata. */\n/** What a downlevel walk needs: the serializers, the page's front matter, and the source its nodes were parsed from. */\nexport interface DownlevelWalk {\n frontmatter: Record<string, EvaluatedValue> | undefined;\n registry: Record<string, ComponentMarkdown>;\n source: string;\n}\ntype Walk = DownlevelWalk;\n\n/** The serializer registry for a site: built-ins under a user's `ai.markdownComponents`. */\nexport const componentRegistry = (\n components?: Record<string, ComponentMarkdown>\n): Record<string, ComponentMarkdown> =>\n components && Object.keys(components).length > 0\n ? { ...SERIALIZERS, ...components }\n : SERIALIZERS;\n\n/**\n * A source slice as Markdown: the verbatim `[start, end)` range with any\n * serializable component under `nodes` downleveled in place, dedented and\n * trimmed. Mutually recursive with {@link collectSplices} (the nodes may hold\n * further serializable components), hence the forward reference.\n */\nconst renderSlice = (\n walk: Walk,\n start: number,\n end: number,\n nodes: MdastNode[]\n): string => {\n const splices: Splice[] = [];\n // oxlint-disable-next-line no-use-before-define\n collectSplices(walk, nodes, splices);\n const spliced = applySplices(\n walk.source.slice(start, end),\n splices.map((splice) => ({\n ...splice,\n end: splice.end - start,\n start: splice.start - start,\n }))\n );\n return dedent(spliced).trim();\n};\n\n/** The element's body as Markdown: the slice covering all of its children. */\nconst renderChildren = (walk: Walk, node: MdastNode): string => {\n const children = (node.children ?? []).filter(hasOffsets);\n const [first] = children;\n if (!first) {\n return \"\";\n }\n const start = first.position.start.offset;\n return renderSlice(\n walk,\n start,\n children.at(-1)?.position.end.offset ?? start,\n children\n );\n};\n\n/**\n * One direct child as a block of Markdown: a registered component's own\n * rendering, else — prose, an unknown component, or one that declined — its\n * source slice, the same choice {@link collectSplices} makes at the top level.\n */\nconst renderBlock = (walk: Walk, node: Positioned): string => {\n const serializer =\n isJsxElement(node) && node.name ? walk.registry[node.name] : undefined;\n const text = serializer\n ? // oxlint-disable-next-line no-use-before-define\n serializeElement(serializer, walk, node)\n : null;\n return (\n text ??\n renderSlice(\n walk,\n node.position.start.offset,\n node.position.end.offset,\n node.children ?? []\n )\n );\n};\n\n/** Serialize one component usage, or `null` to keep its JSX verbatim. */\nconst serializeElement = (\n serializer: ComponentMarkdown,\n walk: Walk,\n node: MdastNode\n): string | null =>\n serializer({\n ...readProps(node, walk.frontmatter),\n childBlocks: () =>\n (node.children ?? []).filter(hasOffsets).map((child) => ({\n markdown: renderBlock(walk, child),\n name: isJsxElement(child) ? child.name : undefined,\n })),\n childComponents: (name) =>\n (node.children ?? [])\n .filter((child) => isJsxElement(child) && child.name === name)\n .map((child) => ({\n ...readProps(child, walk.frontmatter),\n children: renderChildren(walk, child),\n })),\n children: renderChildren(walk, node),\n frontmatter: walk.frontmatter ?? {},\n });\n\n/**\n * Walk the tree collecting replacements. A replaced element's subtree is\n * owned by its serializer (which downlevels its own children), so the walk\n * doesn't descend into it; when a serializer declines, the walk continues\n * inside so nested serializable components still convert.\n */\n/**\n * Downlevel one parsed component to the Markdown its serializer emits, or\n * `null` when the node isn't a component, has no serializer, or its\n * serializer declines. `walk.source` must be the text `node` was parsed from.\n */\nexport const downlevelComponentNode = (\n node: MdastNode,\n walk: DownlevelWalk\n): string | null => {\n const serializer =\n node.type === \"mdxJsxFlowElement\" && node.name\n ? walk.registry[node.name]\n : undefined;\n return serializer && hasOffsets(node)\n ? serializeElement(serializer, walk, node)\n : null;\n};\n\nconst collectSplices = (\n walk: Walk,\n nodes: MdastNode[],\n out: Splice[]\n): void => {\n for (const node of nodes) {\n const text = hasOffsets(node) ? downlevelComponentNode(node, walk) : null;\n if (text !== null && hasOffsets(node)) {\n out.push({\n end: node.position.end.offset,\n start: node.position.start.offset,\n text,\n });\n continue;\n }\n collectSplices(walk, node.children ?? [], out);\n }\n};\n\n/**\n * Downlevel supported components in an MDX source to plain Markdown. Sources\n * with no supported components — and sources Satteri can't parse as MDX, e.g.\n * plain `.md` with literal `<`/`{` — are returned byte-identical.\n *\n * `components` adds user serializers from `ai.markdownComponents`, layered\n * over the built-ins: a same-name entry replaces the built-in serializer, and\n * one that always returns `null` effectively opts that component out.\n *\n * `frontmatter` is the page's parsed front-matter data. It is put in scope\n * when evaluating attribute expressions — so `prop={frontmatter.status}`\n * resolves the way it does when Astro renders the page — and handed to\n * serializers on their context.\n */\nexport const downlevelComponents = (\n source: string,\n components?: Record<string, ComponentMarkdown>,\n frontmatter?: Record<string, EvaluatedValue>\n): string => {\n const registry = componentRegistry(components);\n const hint =\n registry === SERIALIZERS ? BUILT_IN_HINT : componentHint(registry);\n if (!hint.test(source)) {\n return source;\n }\n let tree: MdastNode;\n try {\n // SAFETY: MdastNode is a structural subset of Satteri's mdast output —\n // every node carries `type`, and the walk reads only optional fields.\n tree = mdxToMdast(source, { features: MDX_FEATURES }) as MdastNode;\n } catch {\n return source;\n }\n const splices: Splice[] = [];\n collectSplices(\n { frontmatter, registry, source },\n tree.children ?? [],\n splices\n );\n return splices.length > 0 ? applySplices(source, splices) : source;\n};\n",
|
|
7
|
-
"import type { Features } from \"satteri\";\n\n/**\n * Sätteri's feature set for plain `.md` pages. Shared by the renderer and the\n * search extractor so the index reads the same grammar the page renders.\n */\nexport const MARKDOWN_FEATURES = {\n subscript: true,\n superscript: true,\n} satisfies Features;\n\n/**\n * The `.mdx` feature set: Markdown's plus `:::` directives (→ `<Callout>`) and\n * block-only math — `singleDollarTextMath: false` keeps a bare `$` (currency,\n * shell, code) as literal text and only parses `$$…$$`.\n */\nexport const MDX_FEATURES = {\n ...MARKDOWN_FEATURES,\n directive: true,\n math: { singleDollarTextMath: false },\n} satisfies Features;\n",
|
|
8
|
-
"// html-escaper's five-entity table is XML-safe: `'` becomes the numeric\n// `'` reference rather than `'`, which XML accepts equally.\nimport { escape as escapeXml } from \"html-escaper\";\n\nimport { normalizeBasePath, withBasePath } from \"../core/base-path.ts\";\nimport type { BlumeProject } from \"../core/project-graph.ts\";\nimport { siteRoot } from \"../core/site-url.ts\";\nimport type { PageRecord } from \"../core/types.ts\";\n\n/** A single feed entry derived from a content page. */\nexport interface RssItem {\n title: string;\n /** Absolute item URL. */\n link: string;\n description?: string;\n /** Publish date, when the page declares one. */\n date?: Date;\n}\n\n/** A resolved RSS feed for one content type. */\nexport interface RssFeed {\n /** Content type the feed covers, e.g. `blog`. */\n type: string;\n /** Feed URL path, e.g. `/blog/rss.xml`. */\n path: string;\n /** Channel title, e.g. `Blume — Blog`. */\n title: string;\n /** Absolute site link. */\n link: string;\n description?: string;\n items: RssItem[];\n}\n\nconst capitalize = (value: string): string =>\n value.charAt(0).toUpperCase() + value.slice(1);\n\n/** Publish date for a page: top-level `date`, else `changelog.date`. */\nconst pageDate = (page: PageRecord): Date | undefined => {\n const raw = page.meta.date ?? page.meta.changelog?.date;\n if (!raw) {\n return;\n }\n const date = new Date(raw);\n return Number.isNaN(date.getTime()) ? undefined : date;\n};\n\n/**\n * Build an RSS feed per configured content type that has publishable pages.\n * Returns an empty list when RSS is disabled or no `deployment.site` is set —\n * absolute URLs are required for a valid feed, mirroring `buildSitemap`.\n */\nexport const buildRssFeeds = (project: BlumeProject): RssFeed[] => {\n const { config } = project;\n const { rss } = config.seo;\n const { site } = config.deployment;\n if (!(rss.enabled && site)) {\n return [];\n }\n const base = siteRoot(site);\n // Routes carry `basePath`; a `deployment.base` subdirectory is layered on top.\n // The feed's own `link`/self URL points at the docs root under that base, while\n // `path` stays base-less (it's also the on-disk output location).\n const deployBase = normalizeBasePath(config.deployment.base);\n const rootLink = `${base}${deployBase}`;\n\n const feeds: RssFeed[] = [];\n for (const type of rss.types) {\n const pages = project.graph.pages.filter(\n (page) =>\n page.contentType === type &&\n !(page.meta.draft || page.meta.sidebar.hidden)\n );\n if (pages.length === 0) {\n continue;\n }\n\n const items: RssItem[] = pages\n .map((page) => ({\n date: pageDate(page),\n description: page.description,\n // Encode like the sitemap does: a route with spaces or non-ASCII\n // must still yield a valid <link>/<guid> URL after XML decoding.\n link: encodeURI(`${base}${withBasePath(deployBase, page.route)}`),\n title: page.title,\n }))\n .toSorted((a, b) => (b.date?.getTime() ?? 0) - (a.date?.getTime() ?? 0))\n .slice(0, rss.limit);\n\n feeds.push({\n description: config.description,\n items,\n link: rootLink,\n path: `/${type}/rss.xml`,\n title: `${config.title} — ${capitalize(type)}`,\n type,\n });\n }\n return feeds;\n};\n\nconst renderItem = (item: RssItem): string => {\n const parts = [\n ` <title>${escapeXml(item.title)}</title>`,\n ` <link>${escapeXml(item.link)}</link>`,\n ` <guid isPermaLink=\"true\">${escapeXml(item.link)}</guid>`,\n ];\n if (item.description) {\n parts.push(` <description>${escapeXml(item.description)}</description>`);\n }\n if (item.date) {\n parts.push(` <pubDate>${item.date.toUTCString()}</pubDate>`);\n }\n return ` <item>\\n${parts.join(\"\\n\")}\\n </item>`;\n};\n\n/** Serialize a resolved feed into an RSS 2.0 XML document. */\nexport const renderRssFeed = (feed: RssFeed): string => {\n const feedSelfHref = `${feed.link}${feed.path}`;\n const channel = [\n ` <title>${escapeXml(feed.title)}</title>`,\n ` <link>${escapeXml(feed.link)}</link>`,\n ` <description>${escapeXml(feed.description ?? feed.title)}</description>`,\n ` <atom:link href=\"${escapeXml(feedSelfHref)}\" rel=\"self\" type=\"application/rss+xml\" />`,\n ];\n const items = feed.items.map(renderItem).join(\"\\n\");\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<rss version=\"2.0\" xmlns:atom=\"http://www.w3.org/2005/Atom\">\n<channel>\n${channel.join(\"\\n\")}\n${items}\n</channel>\n</rss>\n`;\n};\n",
|
|
9
|
-
"import type { ApiOperationRef, OpenApiData } from \"../openapi/model.ts\";\nimport { operationOf, specAddresses, specOf } from \"../openapi/model.ts\";\nimport { operationSignature } from \"../openapi/signature.ts\";\nimport type {\n ComponentMarkdown,\n EvaluatedValue,\n} from \"./component-markdown.ts\";\nimport { inlineCode, isString, linkDestination } from \"./component-markdown.ts\";\n\n/**\n * Spec-authored prose as one line of inline Markdown. Unlike the props the\n * other serializers pass through, a summary is written by whoever wrote the\n * spec, not by the docs author, so the characters CommonMark would read as\n * markup — `<user>` as inline HTML, `*only*` as emphasis — are escaped.\n */\nconst inlineText = (text: string): string =>\n text\n .trim()\n .replaceAll(/\\s+/gu, \" \")\n .replaceAll(/[\\\\`*_[\\]<>~]/gu, String.raw`\\$&`);\n\n/** One operation as a link, for the tag listings. */\nconst listItem = (\n signature: string,\n operation: Pick<ApiOperationRef, \"deprecated\" | \"route\" | \"summary\">\n): string => {\n const tail = [\n inlineText(operation.summary),\n operation.deprecated ? \"Deprecated.\" : \"\",\n ]\n .filter(Boolean)\n .join(\" \");\n return `- [${inlineCode(signature)}](${linkDestination(operation.route)})${tail ? ` — ${tail}` : \"\"}`;\n};\n\n/**\n * Agent-facing Markdown for the components a generated reference page is made\n * of: `<Operation>`, `<ApiTagOperations>` and `<ApiOverview>`.\n *\n * `render-mdx.ts` builds each reference page as the operation's description in\n * the body plus one of these components, deliberately: the structured UI is the\n * component's job, and the prose stays Markdown so it indexes. That split is\n * right for the rendered page and lossy everywhere else — `<route>.md`,\n * llms-full.txt, MCP `get_page` and the Ask AI corpus all downlevel components\n * to Markdown, and these three had no serializer, so an operation page reached\n * an agent as its description followed by a bare tag. On a site whose reference\n * is most of the corpus, that is most of the corpus: measured on one 449-page\n * site, 266 pages and 266 raw `<Operation>` in llms-full.txt, so \"which\n * endpoint do I call?\" had no answer anywhere in the agent surface.\n *\n * Each serializer emits what its component renders and no more: the endpoint\n * in the spec kind's own notation ({@link operationSignature}), the version and\n * addresses the overview shows ({@link specAddresses}), the linked list a tag\n * section shows. Parameters, schemas and responses are deliberately left out:\n * those are `operation-model.ts` plus each component's own preparation, and a\n * second implementation here would be free to disagree with the page. The\n * endpoint and what it does is the part that was missing altogether.\n *\n * `specs` is the parsed `blume:openapi` data — empty when the project has no\n * API reference, in which case every serializer declines.\n */\nexport const openapiComponentSerializers = (specs: OpenApiData) => {\n const spec = (source: EvaluatedValue) =>\n isString(source) ? specOf(specs, source) : undefined;\n\n return {\n /**\n * The spec-level block at the top of the overview page: its version and\n * where the API lives. The tag sections that follow are real Markdown\n * headings over `<ApiTagOperations>`, so they downlevel on their own.\n */\n ApiOverview: ({ props }) => {\n const data = spec(props.source);\n if (!data) {\n return null;\n }\n const { addresses, label } = specAddresses(data);\n const lines = [\n data.version ? `Version ${inlineText(data.version)}` : \"\",\n addresses.length > 0\n ? `${label}: ${addresses.map(inlineCode).join(\", \")}`\n : \"\",\n ].filter(Boolean);\n return lines.length > 0 ? lines.join(\"\\n\\n\") : null;\n },\n\n /** One tag's operations, mirroring the list the rendered page shows. */\n ApiTagOperations: ({ props }) => {\n const { tag } = props;\n const data = spec(props.source);\n if (!(data && isString(tag))) {\n return null;\n }\n const items = Object.values(data.operations)\n .filter((operation) => operation.tagSlug === tag)\n .map((operation) =>\n listItem(operationSignature(data, operation), operation)\n );\n return items.length > 0 ? items.join(\"\\n\") : null;\n },\n\n /**\n * One operation. Declines when the spec carries no such key — Blume's own\n * fallback, which leaves the JSX visible rather than publishing a page that\n * silently lost its endpoint.\n */\n Operation: ({ props }) => {\n const { id } = props;\n const data = spec(props.source);\n if (!(data && isString(id))) {\n return null;\n }\n const operation = operationOf(data, id);\n if (!operation) {\n return null;\n }\n const signature = inlineCode(operationSignature(data, operation));\n return operation.deprecated\n ? `${signature}\\n\\n**Deprecated.**`\n : signature;\n },\n } satisfies Record<string, ComponentMarkdown>;\n};\n",
|
|
10
|
-
"import type { BlumeProject } from \"../core/project-graph.ts\";\nimport { isOpenApiSource } from \"../openapi/source.ts\";\nimport type { ComponentMarkdown } from \"./component-markdown.ts\";\nimport { exampleComponentSerializers } from \"./component-markdown.ts\";\nimport { openapiComponentSerializers } from \"./openapi-components.ts\";\n\n/**\n * Every serializer a project brings to a downlevel pass, layered once for all\n * the agent surfaces (`<route>.md`, llms-full.txt, the search and Ask AI\n * corpora): the built-in families that read project data — examples, then the\n * API reference — under the user's `ai.markdownComponents`, which is spread\n * last so an entry of the same name still wins. One layering, so a new family\n * or a new surface cannot leave one consumer printing raw JSX.\n */\nexport const projectComponentSerializers = (\n project: Pick<BlumeProject, \"config\" | \"examples\" | \"sources\">\n) =>\n ({\n ...exampleComponentSerializers(project.examples ?? {}),\n ...openapiComponentSerializers(\n project.sources.find(isOpenApiSource)?.openApiData() ?? {}\n ),\n ...project.config.ai.markdownComponents,\n }) satisfies Record<string, ComponentMarkdown>;\n",
|
|
11
|
-
"import { createHash } from \"node:crypto\";\nimport { readdir, readFile, stat } from \"node:fs/promises\";\n\nimport { join } from \"pathe\";\n\nimport { normalizeBasePath } from \"../core/base-path.ts\";\nimport matter from \"../core/frontmatter.ts\";\nimport type { ResolvedConfig } from \"../core/schema.ts\";\nimport { buildTarGz } from \"./tar.ts\";\nimport type { TarEntry } from \"./tar.ts\";\n\n/**\n * Agent Skills discovery (Cloudflare's Agent Skills Discovery RFC v0.2.0):\n * the skills a site publishes are enumerated in an index at\n * `/.well-known/agent-skills/index.json`, each entry pointing at its artifact\n * with a SHA-256 digest. A skill that is only a `SKILL.md` publishes the file\n * verbatim (`type: \"skill-md\"`); a skill with supporting resources (scripts,\n * references, assets) is bundled into a deterministic `.tar.gz`\n * (`type: \"archive\"`) so its relative references resolve after unpacking.\n */\n\nexport const AGENT_SKILLS_DIR = \"/.well-known/agent-skills\";\nexport const AGENT_SKILLS_INDEX_PATH = \"/.well-known/agent-skills/index.json\";\n\nconst AGENT_SKILLS_SCHEMA =\n \"https://schemas.agentskills.io/discovery/0.2.0/schema.json\";\n\n/** Skill naming rule from the Agent Skills spec (1-64 chars enforced apart). */\nconst SKILL_NAME = /^[a-z0-9]+(?:-[a-z0-9]+)*$/u;\nconst SKILL_NAME_MAX = 64;\n/** The Agent Skills spec caps `description` at 1024 characters. */\nconst DESCRIPTION_MAX = 1024;\n\n/** One publishable skill artifact plus its index entry. */\nexport interface SkillArtifact {\n /** Raw bytes to publish (the digest is computed over exactly these). */\n content: Uint8Array;\n description: string;\n /** `sha256:{hex}` digest of `content`. */\n digest: string;\n name: string;\n /** Path under the agent-skills dir: `{name}/SKILL.md` or `{name}.tar.gz`. */\n path: string;\n type: \"archive\" | \"skill-md\";\n}\n\nexport interface CollectedSkills {\n skills: SkillArtifact[];\n /** Human-readable reasons for anything skipped. */\n warnings: string[];\n}\n\nconst sha256 = (content: Uint8Array): string =>\n `sha256:${createHash(\"sha256\").update(content).digest(\"hex\")}`;\n\n/**\n * Every regular file under a skill directory, as archive entries with\n * `/`-separated relative paths, dotfiles (`.DS_Store`, `.git`) excluded,\n * sorted for deterministic archives. The owner-execute bit is preserved so a\n * skill's scripts stay runnable after unpacking.\n */\nconst collectEntries = async (\n dir: string,\n prefix = \"\"\n): Promise<TarEntry[]> => {\n const entries: TarEntry[] = [];\n const items = await readdir(dir, { withFileTypes: true });\n for (const item of items.toSorted((a, b) => (a.name < b.name ? -1 : 1))) {\n if (item.name.startsWith(\".\")) {\n continue;\n }\n const path = join(dir, item.name);\n const relative = prefix ? `${prefix}/${item.name}` : item.name;\n if (item.isDirectory()) {\n // Sequential recursion keeps ordering deterministic.\n // oxlint-disable-next-line no-await-in-loop\n entries.push(...(await collectEntries(path, relative)));\n } else if (item.isFile()) {\n // oxlint-disable-next-line no-await-in-loop\n const [content, info] = await Promise.all([readFile(path), stat(path)]);\n entries.push({\n content: new Uint8Array(content),\n // oxlint-disable-next-line no-bitwise -- testing the owner-execute mode bit\n executable: (info.mode & 0o100) !== 0,\n path: relative,\n });\n }\n }\n return entries;\n};\n\n/** The two SKILL.md frontmatter fields the discovery index publishes. */\ninterface SkillMeta {\n description: string;\n name: string;\n}\n\ninterface SkillMetaResult {\n meta: SkillMeta | null;\n warning?: string;\n}\n\n/**\n * What js-yaml can put in a SKILL.md frontmatter field. Rich scalars (Dates)\n * ride along as the object arm; only strings are accepted below anyway.\n */\ntype FrontmatterField =\n | string\n | number\n | boolean\n | null\n | undefined\n | FrontmatterField[]\n | { [key: string]: FrontmatterField };\n\nconst isString = (value: FrontmatterField): value is string =>\n typeof value === \"string\";\n\n/** Frontmatter of a SKILL.md, or null with a warning when unusable. */\nconst skillMeta = (raw: string, dirName: string): SkillMetaResult => {\n let data: { description?: FrontmatterField; name?: FrontmatterField };\n try {\n ({ data } = matter(raw));\n } catch {\n return {\n meta: null,\n warning: `Skill \"${dirName}\" has unparsable SKILL.md frontmatter; skipped.`,\n };\n }\n const name = isString(data.name) ? data.name : \"\";\n const description = isString(data.description) ? data.description : \"\";\n if (!(name && description)) {\n return {\n meta: null,\n warning: `Skill \"${dirName}\" is missing the required \"name\"/\"description\" frontmatter; skipped.`,\n };\n }\n if (!SKILL_NAME.test(name) || name.length > SKILL_NAME_MAX) {\n return {\n meta: null,\n warning: `Skill \"${dirName}\" has an invalid name \"${name}\" (lowercase alphanumerics and single hyphens, max 64 chars); skipped.`,\n };\n }\n return {\n meta: { description: description.slice(0, DESCRIPTION_MAX), name },\n };\n};\n\n/**\n * Collect the publishable skills from a directory whose subdirectories each\n * hold a `SKILL.md` (the layout `npx skills add` consumes). Subdirectories\n * without one are ignored silently — the directory may hold other assets —\n * while a present-but-invalid skill earns a warning so it isn't dropped\n * behind the publisher's back.\n */\nexport const collectSkills = async (dir: string): Promise<CollectedSkills> => {\n const skills: SkillArtifact[] = [];\n const warnings: string[] = [];\n const items = await readdir(dir, { withFileTypes: true });\n for (const item of items.toSorted((a, b) => (a.name < b.name ? -1 : 1))) {\n if (!item.isDirectory() || item.name.startsWith(\".\")) {\n continue;\n }\n // oxlint-disable-next-line no-await-in-loop\n const entries = await collectEntries(join(dir, item.name));\n const skillMd = entries.find((entry) => entry.path === \"SKILL.md\");\n if (!skillMd) {\n continue;\n }\n const { meta, warning } = skillMeta(\n new TextDecoder().decode(skillMd.content),\n item.name\n );\n if (!meta) {\n if (warning) {\n warnings.push(warning);\n }\n continue;\n }\n // A lone SKILL.md ships verbatim; supporting resources ship as an\n // archive so the skill's relative references resolve after unpacking.\n const single = entries.length === 1;\n const content = single ? skillMd.content : buildTarGz(entries);\n skills.push({\n content,\n description: meta.description,\n digest: sha256(content),\n name: meta.name,\n path: single ? `${meta.name}/SKILL.md` : `${meta.name}.tar.gz`,\n type: single ? \"skill-md\" : \"archive\",\n });\n }\n return { skills, warnings };\n};\n\n/**\n * The discovery index (v0.2.0 schema). Artifact URLs are path-absolute under\n * `deployment.base` — the RFC resolves them against the index origin.\n */\nexport const buildSkillsIndex = (\n skills: readonly SkillArtifact[],\n config: ResolvedConfig\n): string => {\n const deployBase = normalizeBasePath(config.deployment.base);\n const index = {\n $schema: AGENT_SKILLS_SCHEMA,\n skills: skills.map((skill) => ({\n description: skill.description,\n digest: skill.digest,\n name: skill.name,\n type: skill.type,\n url: `${deployBase}${AGENT_SKILLS_DIR}/${skill.path}`,\n })),\n };\n return `${JSON.stringify(index, null, 2)}\\n`;\n};\n",
|
|
12
|
-
"import { gzipSync } from \"node:zlib\";\n\nimport { createTar } from \"nanotar\";\n\n/**\n * `.tar.gz` writer for agent-skill archives, on nanotar's ustar writer.\n * Deterministic by construction — fixed mtime/uid/gid/owner attrs,\n * caller-ordered entries, and Node's gzip header carries no timestamp — so a\n * skill's archive digest only changes when its content does. That holds per\n * machine: the tar bytes are portable, but zlib's compressed stream differs\n * across architectures, so gzip-layer digests are not comparable across\n * platforms.\n */\n\n/** One regular file to archive. Paths are `/`-separated, relative, no `..`. */\nexport interface TarEntry {\n /** Raw file bytes. */\n content: Uint8Array;\n /** Preserve the owner-execute bit (e.g. a skill's scripts). */\n executable?: boolean;\n /** Archive-relative path, e.g. `SKILL.md` or `references/FORMS.md`. */\n path: string;\n}\n\n/** ustar `name` field capacity; skill layouts are shallow, so no `prefix`. */\nconst NAME_MAX = 100;\n\nconst encoder = new TextEncoder();\n\n/**\n * Build a gzipped ustar archive of the given files, in the given order. Paths\n * longer than the ustar `name` field or escaping the archive root throw to\n * surface a programming error — nanotar would silently truncate an oversized\n * name into a corrupt archive, so the validation stays here.\n */\nexport const buildTarGz = (entries: readonly TarEntry[]): Uint8Array => {\n for (const entry of entries) {\n if (encoder.encode(entry.path).byteLength > NAME_MAX) {\n throw new Error(`tar path exceeds ${NAME_MAX} bytes: ${entry.path}`);\n }\n if (entry.path.startsWith(\"/\") || entry.path.split(\"/\").includes(\"..\")) {\n throw new Error(`tar path must be archive-relative: ${entry.path}`);\n }\n }\n const tar = createTar(\n entries.map((entry) => ({\n // Root-owned, epoch-mtime, empty owner names: every field a rebuild\n // could vary is pinned so the archive bytes are a function of content.\n attrs: {\n gid: 0,\n group: \"\",\n mode: entry.executable ? \"755\" : \"644\",\n mtime: 0,\n uid: 0,\n user: \"\",\n },\n data: entry.content,\n name: entry.path,\n }))\n );\n // Sync gzip with a pinned level; Node writes no timestamp into the header.\n return new Uint8Array(gzipSync(tar, { level: 9 }));\n};\n",
|
|
13
|
-
"// Mirrors the fence masking in `core/sources/assets.ts`: a fenced code sample\n// that *shows* `<Visibility>` markup must keep showing what the author wrote.\nconst CODE_FENCE_BLOCK =\n /^(?<fence>`{3,}|~{3,})[^\\n]*\\n[\\s\\S]*?^\\k<fence>[^\\n]*(?=\\n|$)/gmu;\n// NUL delimiters cannot appear in authored markdown, so tokens never collide.\n// oxlint-disable-next-line no-control-regex -- the NUL is the collision guard.\nconst FENCE_TOKEN = /\\u0000blume-fence-(?<index>\\d+)\\u0000/gu;\n\n/** The audience an output surface serves — the component's two `for` values. */\nexport type VisibilityAudience = \"agents\" | \"web\";\n\n// `<Visibility for=\"…\">…</Visibility>` in either quote style, tolerant of\n// whitespace around the attribute and inside the tags. Non-greedy bodies stop\n// at the first close tag, so nesting is not supported: a nested block closes\n// the outer match early and any remainder passes through verbatim.\nconst visibilityBlock = (audience: VisibilityAudience): RegExp =>\n new RegExp(\n `<Visibility\\\\s+for\\\\s*=\\\\s*(?:\"${audience}\"|'${audience}')\\\\s*>(?<inner>[\\\\s\\\\S]*?)</Visibility\\\\s*>`,\n \"gu\"\n );\n\nconst BLOCKS = {\n agents: visibilityBlock(\"agents\"),\n web: visibilityBlock(\"web\"),\n} satisfies Record<VisibilityAudience, RegExp>;\n\n/**\n * Resolve `<Visibility>` blocks for one audience: blocks addressed to the\n * other audience are removed entirely and blocks addressed to `audience` are\n * unwrapped (tags dropped, body kept), matching what the Astro component\n * renders on the web. Other `for` values (the component's default) are left\n * untouched, and markdown with no matching blocks is returned byte-identical,\n * so raw sources stay raw.\n */\nexport const applyAudienceVisibility = (\n markdown: string,\n audience: VisibilityAudience\n): string => {\n // Mask fenced code blocks so documentation *about* Visibility survives.\n const fences: string[] = [];\n const masked = markdown.replace(CODE_FENCE_BLOCK, (block) => {\n fences.push(block);\n return `\\u0000blume-fence-${fences.length - 1}\\u0000`;\n });\n\n let touched = false;\n const filtered = masked\n .replaceAll(BLOCKS[audience === \"agents\" ? \"web\" : \"agents\"], () => {\n touched = true;\n return \"\";\n })\n .replaceAll(BLOCKS[audience], (_match, inner: string) => {\n touched = true;\n return inner;\n });\n\n // Removing/unwrapping block-level tags leaves runs of blank lines behind;\n // collapse them only when something matched so untouched files round-trip\n // exactly. Fences are masked as single-line tokens, so they are unaffected.\n const tidied = touched ? filtered.replaceAll(/\\n{3,}/gu, \"\\n\\n\") : filtered;\n\n return tidied.replaceAll(\n FENCE_TOKEN,\n (token, index) => fences[Number(index)] ?? token\n );\n};\n\n/**\n * Resolve `<Visibility>` blocks for agent-facing Markdown (llms-full.txt, the\n * `.md`/`.mdx` mirrors, MCP tools, Ask AI grounding): `for=\"web\"` content is\n * removed and `for=\"agents\"` content is unwrapped.\n */\nexport const applyAgentVisibility = (markdown: string): string =>\n applyAudienceVisibility(markdown, \"agents\");\n",
|
|
14
|
-
"import { normalizeBasePath, withBasePath } from \"../core/base-path.ts\";\nimport { rewriteRelativeImages } from \"../core/content-assets.ts\";\nimport matter from \"../core/frontmatter.ts\";\nimport type { BlumeProject } from \"../core/project-graph.ts\";\nimport { absoluteUrl } from \"../core/site-url.ts\";\nimport { readExpandedEntryText } from \"../core/sources/read.ts\";\nimport type { NavNode, Navigation, PageRecord } from \"../core/types.ts\";\nimport { buildRssFeeds } from \"../deploy/rss.ts\";\nimport { API_CATALOG_PATH, hasApiCatalog } from \"./api-catalog.ts\";\nimport { API_PAGES_PATH, OPENAPI_PATH } from \"./api/paths.ts\";\nimport { downlevelComponents } from \"./component-markdown.ts\";\nimport { projectComponentSerializers } from \"./serializers.ts\";\nimport { AGENT_SKILLS_DIR, AGENT_SKILLS_INDEX_PATH } from \"./skills.ts\";\nimport type { SkillArtifact } from \"./skills.ts\";\nimport { applyAgentVisibility } from \"./visibility.ts\";\n\n// Routes carry `basePath`; a `deployment.base` subdirectory is layered on top —\n// with or without a `site` (the mcp.json convention) — so the emitted URL\n// matches where the page is served. Encoded like the sitemap: a route with\n// spaces or non-ASCII must still yield a valid Markdown link.\nconst pageUrl = (route: string, site?: string, base = \"\"): string => {\n const path = withBasePath(base, route);\n return encodeURI(site ? absoluteUrl(site, path) : path);\n};\n\n/** Inputs only the build has: the published skills, collected once per build. */\nexport interface LlmsIndexOptions {\n /**\n * The Agent Skills the build publishes under `/.well-known/agent-skills/`,\n * listed in their own section with each skill's description — which is\n * where a skill says when to use it. Absent (empty) for the homepage\n * Markdown mirror, which is synthesized before the skills are collected.\n */\n skills?: readonly SkillArtifact[];\n}\n\n/** A skill description on one line — SKILL.md frontmatter may wrap it. */\nconst oneLine = (text: string): string => text.replaceAll(/\\s+/gu, \" \").trim();\n\n/**\n * The \"Agent resources\" section: every machine-readable artifact the site\n * publishes, so an agent that only reads llms.txt still finds the full\n * Markdown dump, the per-page Markdown mirrors, the JSON docs API, the MCP\n * server, the skills index, the API catalog, the readability manifest, and\n * the sitemap. The\n * same artifact set `agent-readability.json` indexes, in prose an agent can\n * act on without a second fetch.\n */\nconst agentResourceLines = (project: BlumeProject): string[] => {\n const { config } = project;\n const { site } = config.deployment;\n const url = (path: string): string =>\n pageUrl(path, site, normalizeBasePath(config.deployment.base));\n const lines = [\n `- [llms-full.txt](${url(\"/llms-full.txt\")}): The full Markdown of every page in one file.`,\n `- [Page Markdown](${url(\"/index.md\")}): Append \\`.md\\` to any page URL to fetch that page as raw Markdown.`,\n ];\n if (config.ai.api) {\n lines.push(\n `- [JSON API](${url(API_PAGES_PATH)}): Page index of the JSON docs API; each entry links the page's JSON and Markdown forms. Described by the OpenAPI document at ${url(OPENAPI_PATH)}.`\n );\n }\n if (config.ai.mcp.enabled) {\n lines.push(\n `- [MCP server](${url(config.ai.mcp.route)}): Streamable HTTP Model Context Protocol server with search_docs, get_page, list_pages, and get_navigation tools, plus every page as a resource. Discovery document: ${url(\"/.well-known/mcp.json\")}`\n );\n }\n if (config.ai.skills) {\n lines.push(\n `- [Agent skills](${url(AGENT_SKILLS_INDEX_PATH)}): Agent Skills discovery index of the skills this site publishes.`\n );\n }\n if (hasApiCatalog(config)) {\n lines.push(\n `- [API catalog](${url(API_CATALOG_PATH)}): RFC 9727 linkset of the APIs documented here.`\n );\n }\n if (config.seo.agentReadability) {\n lines.push(\n `- [agent-readability.json](${url(\"/agent-readability.json\")}): Manifest of every agent-facing artifact on this site.`\n );\n }\n if (site && config.seo.sitemap) {\n lines.push(\n `- [Sitemap](${url(\"/sitemap.xml\")}): Every indexable page URL with its last-modified date.`\n );\n }\n return lines;\n};\n\n// Drafts, hidden, and ordinary `noindex` pages are excluded. Generated API\n// references keep crawler visibility (`noindex`) separate from LLM visibility\n// (`ai.exclude`), and are excluded wholesale when `ai.llmsTxt.openapi` is off.\n// `versions: \"current\"` additionally drops archived-snapshot pages — the flat\n// llms-full.txt dump serves agents the live docs, not every frozen copy.\nconst eligiblePages = (\n project: BlumeProject,\n options: { versions?: \"all\" | \"current\" } = {}\n): PageRecord[] =>\n project.graph.pages.filter(\n (page) =>\n !(\n page.meta.ai.exclude ||\n page.meta.draft ||\n page.meta.sidebar.hidden ||\n (page.meta.seo.noindex && page.source.name !== \"openapi\") ||\n (options.versions === \"current\" && page.version !== \"\")\n ) &&\n (project.config.ai.llmsTxt.openapi || page.source.name !== \"openapi\")\n );\n\n/**\n * The navigation trees the index mirrors: the site tree, or one per locale\n * under i18n (each labeled with the locale except the default, so sections\n * don't repeat ambiguously). On a versioned site each archived snapshot's\n * trees follow the current ones, labeled with the version (and locale) so an\n * agent reading the index knows which docs are frozen.\n */\nconst indexedNavigations = (\n project: BlumeProject\n): { label?: string; nav: Navigation }[] => {\n const { i18n, versions } = project.config;\n const current: { label?: string; nav: Navigation }[] = i18n\n ? i18n.locales.flatMap(({ code, label }) => {\n const nav = project.graph.navigationByLocale[code];\n if (!nav) {\n return [];\n }\n return [\n { label: code === i18n.defaultLocale ? undefined : label, nav },\n ];\n })\n : [{ nav: project.graph.navigation }];\n\n const archived = (versions?.archived ?? []).flatMap((version) => {\n const byLocale = project.graph.navigationByVersion[version.id] ?? {};\n const versionLabel = `${version.label ?? version.id} (archived)`;\n return Object.entries(byLocale).flatMap(([code, nav]) => {\n const locale = i18n?.locales.find((entry) => entry.code === code);\n const label =\n locale && code !== i18n?.defaultLocale\n ? `${locale.label} — ${versionLabel}`\n : versionLabel;\n return [{ label, nav }];\n });\n });\n\n return [...current, ...archived];\n};\n\n/**\n * Build the compact `llms.txt` index: title and summary, then the sidebar tree\n * rendered as sections — group labels become headings, pages become link lists —\n * so the file mirrors how the docs are organized rather than one flat blob.\n * Also serves as the homepage's synthesized Markdown mirror when the home\n * route is a landing page (see `buildRawMarkdown`).\n */\nexport const buildLlmsIndex = (\n project: BlumeProject,\n options: LlmsIndexOptions = {}\n): string => {\n const { config } = project;\n const { site } = config.deployment;\n const base = normalizeBasePath(config.deployment.base);\n const eligible = eligiblePages(project);\n const byRoute = new Map(eligible.map((page) => [page.route, page]));\n const seen = new Set<string>();\n\n const line = (page: PageRecord): string => {\n seen.add(page.route);\n const summary = page.description ? `: ${page.description}` : \"\";\n return `- [${page.title}](${pageUrl(page.route, site, base)})${summary}`;\n };\n\n // One nav level -> Markdown blocks: the level's loose pages as a link list,\n // then each group as a heading (depth-capped at h6) followed by its own\n // blocks. Nav entries whose route has no eligible page — external links,\n // padded i18n fallbacks, `noindex` pages, excluded API references — are\n // skipped, and a group left with nothing emits no heading at all.\n const renderLevel = (nodes: NavNode[], depth: number): string[] => {\n const list: string[] = [];\n const groupBlocks: string[] = [];\n for (const node of nodes) {\n if (node.kind === \"page\") {\n const page = byRoute.get(node.route);\n if (page && !seen.has(page.route)) {\n list.push(line(page));\n }\n continue;\n }\n // An explicit-config group may link its index page on the group itself\n // (`root`) rather than as a child; keep it at the top of the section.\n const rootPage = node.route ? byRoute.get(node.route) : undefined;\n const blocks = renderLevel(node.children, depth + 1);\n if (rootPage && !seen.has(rootPage.route)) {\n blocks.unshift(line(rootPage));\n }\n if (blocks.length > 0) {\n groupBlocks.push(\n `${\"#\".repeat(Math.min(depth, 6))} ${node.label}`,\n ...blocks\n );\n }\n }\n return list.length > 0 ? [list.join(\"\\n\"), ...groupBlocks] : groupBlocks;\n };\n\n // Loose pages at a tree's root get a \"Docs\" section of their own, so every\n // link sits under an h2 as llms.txt consumers expect.\n const renderNav = (nav: Navigation, depth: number): string[] => {\n const loose = nav.sidebar.filter((node) => node.kind === \"page\");\n const groups = nav.sidebar.filter((node) => node.kind === \"group\");\n const looseBlocks = renderLevel(loose, depth + 1);\n return [\n ...(looseBlocks.length > 0\n ? [`${\"#\".repeat(depth)} Docs`, ...looseBlocks]\n : []),\n ...renderLevel(groups, depth),\n ];\n };\n\n const blocks: string[] = [];\n for (const { label, nav } of indexedNavigations(project)) {\n if (label) {\n const localized = renderNav(nav, 3);\n if (localized.length > 0) {\n blocks.push(`## ${label}`, ...localized);\n }\n continue;\n }\n blocks.push(...renderNav(nav, 2));\n }\n\n // Pages the navigation doesn't reach (an explicit sidebar that omits them,\n // or a hand-rolled tree) still belong in the index.\n const leftover = eligible\n .filter((page) => !seen.has(page.route))\n .toSorted((a, b) => a.route.localeCompare(b.route));\n if (leftover.length > 0) {\n blocks.push(\n blocks.length > 0 ? \"## Other\" : \"## Docs\",\n leftover.map(line).join(\"\\n\")\n );\n }\n\n // Surface the RSS feeds so agents can find fresh content (new blog posts,\n // changelog entries) without re-crawling the index. `buildRssFeeds` is empty\n // unless RSS is enabled and an absolute `site` is set — the same condition\n // under which agent-readability.json lists `artifacts.feeds`.\n const feeds = buildRssFeeds(project);\n if (feeds.length > 0) {\n blocks.push(\n \"## RSS Feeds\",\n feeds\n .map((feed) => `- [${feed.title}](${pageUrl(feed.path, site, base)})`)\n .join(\"\\n\")\n );\n }\n\n // The published skills, each with its description — the one place a skill\n // states when an agent should reach for it.\n const skills = options.skills ?? [];\n if (skills.length > 0) {\n blocks.push(\n \"## Agent skills\",\n skills\n .map(\n (skill) =>\n `- [${skill.name}](${pageUrl(`${AGENT_SKILLS_DIR}/${skill.path}`, site, base)}): ${oneLine(skill.description)}`\n )\n .join(\"\\n\")\n );\n }\n\n blocks.push(\"## Agent resources\", agentResourceLines(project).join(\"\\n\"));\n\n const header = config.description\n ? `# ${config.title}\\n\\n> ${config.description}`\n : `# ${config.title}`;\n // `details` is the llms.txt spec's free-form block between the summary and\n // the file sections — \"when to use this\" guidance in the site's own words.\n const { details } = config.ai.llmsTxt;\n const lead = details ? `${header}\\n\\n${details}` : header;\n return `${[lead, ...blocks].join(\"\\n\\n\")}\\n`;\n};\n\n/** Build `llms-full.txt`: the full Markdown body of every current-docs page. */\nconst buildFull = async (project: BlumeProject): Promise<string> => {\n const { config } = project;\n const pages = eligiblePages(project, { versions: \"current\" }).toSorted(\n (a, b) => a.route.localeCompare(b.route)\n );\n const components = projectComponentSerializers(project);\n\n const sections = await Promise.all(\n pages.map(async (page) => {\n let raw = await readExpandedEntryText(project, page);\n // Colocated `./image.png` references resolve to nothing for a reader of\n // llms-full.txt; point them at the served originals instead.\n if (page.sourcePath) {\n raw = rewriteRelativeImages({\n deployBase: config.deployment.base,\n projectRoot: project.context.root,\n source: raw,\n sourcePath: page.sourcePath,\n });\n }\n // Resolve `<Visibility>` audiences (web-only content omitted from the\n // agent-facing output, agents-only unwrapped), then downlevel supported\n // components to plain Markdown.\n const parsed = matter(raw);\n const body = downlevelComponents(\n applyAgentVisibility(parsed.content),\n components,\n parsed.data\n ).trim();\n const url = pageUrl(\n page.route,\n config.deployment.site,\n normalizeBasePath(config.deployment.base)\n );\n return [`# ${page.title}`, `Source: ${url}`, \"\", body].join(\"\\n\");\n })\n );\n\n const header = config.description\n ? `# ${config.title}\\n\\n> ${config.description}\\n`\n : `# ${config.title}\\n`;\n\n return `${header}\\n${sections.join(\"\\n\\n---\\n\\n\")}\\n`;\n};\n\n/** Build both LLM text artifacts for a project. */\nexport const buildLlmsFiles = async (\n project: BlumeProject,\n options: LlmsIndexOptions = {}\n): Promise<{ index: string; full: string }> => ({\n full: await buildFull(project),\n index: buildLlmsIndex(project, options),\n});\n",
|
|
15
|
-
"import type { Nodes } from \"mdast\";\nimport { markdownToMdast, mdxToMdast } from \"satteri\";\n\nimport {\n componentRegistry,\n downlevelComponentNode,\n downlevelComponents,\n} from \"../ai/component-markdown.ts\";\nimport type {\n ComponentMarkdown,\n DownlevelWalk,\n MdastNode as DownlevelNode,\n} from \"../ai/component-markdown.ts\";\nimport { projectComponentSerializers } from \"../ai/serializers.ts\";\nimport { applyAudienceVisibility } from \"../ai/visibility.ts\";\nimport type { VisibilityAudience } from \"../ai/visibility.ts\";\nimport matter from \"../core/frontmatter.ts\";\nimport { parseHeadingMarkers } from \"../core/heading-markers.ts\";\nimport { contentIndexable } from \"../core/manifest.ts\";\nimport type { BlumeProject } from \"../core/project-graph.ts\";\nimport { HTML_COMMENT } from \"../core/sources/normalize.ts\";\nimport { readExpandedEntryText } from \"../core/sources/read.ts\";\nimport type { NavNode, PageRecord } from \"../core/types.ts\";\nimport { parseCodeTitle } from \"../markdown/code-title.ts\";\nimport { MARKDOWN_FEATURES, MDX_FEATURES } from \"../markdown/features.ts\";\nimport { pageFacets } from \"./facets.ts\";\n\n/** A document indexed by the client-side search providers (Orama, FlexSearch). */\nexport interface SearchDocument {\n route: string;\n title: string;\n description: string;\n content: string;\n /** Ancestor section labels for the result breadcrumb, e.g. `[\"Guides\"]`. */\n breadcrumb: string[];\n /** Top-level section label, used by the search filter pills. */\n section: string;\n /** Locale code, so the dialog can filter results to the active language. */\n locale: string;\n /** Resolved page `type` (`doc`, `blog`, a custom `rfc`…), for type filters. */\n contentType: string;\n /** Docs version (`\"\"` for the current docs), so results scope to the viewed version. */\n version: string;\n /** Frontmatter `search.tags`, surfaced for hosted-provider faceting. */\n tags?: string[];\n /**\n * Declared facet values (`content.types.<type>.facets`), key → value.\n * Filterable through the MCP tools' `filters` input.\n */\n facets?: Record<string, string>;\n}\n\n/**\n * A record uploaded to a hosted search backend (Algolia, Orama Cloud,\n * Typesense, Mixedbread). `_id` is the stable per-page key; each sync adapts it\n * to the backend's own id field (`objectID`, `id`, …).\n */\nexport interface SearchRecord {\n _id: string;\n url: string;\n title: string;\n description: string;\n content: string;\n /** Locale code, carried as a facet for per-language filtering. */\n locale: string;\n /**\n * Docs version, carried as a facet for per-version filtering. The current\n * docs upload as `\"current\"` — hosted backends treat an empty facet value\n * unreliably, so the sentinel stands in for the empty version id.\n */\n version: string;\n /** Single faceting tag (the first frontmatter tag, when present). */\n tag?: string;\n}\n\n// Tag-shaped only: a name (or closing slash/fragment) right after `<`, and no\n// newline inside. Applied *within* html/JSX nodes so their inner prose is\n// kept; the surrounding Markdown is walked as a tree, so a bare `<` in prose\n// (\"costs < 5 credits\") is ordinary text and never at risk.\nconst HTML_OR_JSX = /<\\/?[a-zA-Z][^\\n<>]*>|<\\/?>/gu;\nconst WHITESPACE = /\\s+/gu;\n\n// Parents whose children are inline: no separator is inserted after them, or\n// `re*ally*` would index as `re ally`. Every other parent is block-shaped and\n// ends with a space so adjacent paragraphs/headings/cells don't fuse.\nconst INLINE_PARENTS = new Set([\n \"delete\",\n \"emphasis\",\n \"footnoteReference\",\n \"link\",\n \"linkReference\",\n \"mdxJsxTextElement\",\n \"strong\",\n \"subscript\",\n \"superscript\",\n \"textDirective\",\n]);\n\n// Nodes that never render as prose: image alt text was never indexed, MDX\n// expressions (`{props.x}`) and ESM (`export const meta`) are code, and math\n// is LaTeX source rendered by KaTeX, not searchable words.\nconst NON_PROSE = new Set<Nodes[\"type\"]>([\n \"image\",\n \"imageReference\",\n \"inlineMath\",\n \"math\",\n \"mdxFlowExpression\",\n \"mdxTextExpression\",\n \"mdxjsEsm\",\n]);\n\ntype PageFormat = PageRecord[\"format\"];\n\n/** What the plain-text walk carries: the code-block policy and the downlevel context. */\ninterface Walk {\n includeCodeBlocks: boolean;\n downlevel: DownlevelWalk;\n}\n\n// Front matter is already off (core/frontmatter.ts) by the time a body gets\n// here, so a body that opens with a `---` divider must read as a thematic\n// break rather than a second front matter block.\nconst MD_PARSE = { features: { ...MARKDOWN_FEATURES, frontmatter: false } };\nconst MDX_PARSE = { features: { ...MDX_FEATURES, frontmatter: false } };\n\nconst parseMdx = (markdown: string): Nodes | undefined => {\n try {\n return mdxToMdast(markdown, MDX_PARSE);\n } catch {\n return undefined;\n }\n};\n\n/**\n * Parse a page body the way the renderer reads it. `.mdx` pages go through\n * the MDX grammar with the renderer's feature set, so components become\n * `mdxJsx*` nodes whose children — prose and fences, however they're indented\n * — walk like top-level content, and `:::` directives and `$$` math parse as\n * such; Markdown would instead fold a tight component into one html node and\n * read its indented children as an indented code block. A page the MDX\n * grammar rejects (an HTML comment, an unclosed tag) fails to render too, so\n * it has no body to index: reading it as Markdown instead would put its ESM,\n * expressions, and raw tags in the index as prose.\n */\nconst parseMarkdown = (\n markdown: string,\n format: PageFormat\n): Nodes | undefined =>\n format === \"mdx\" ? parseMdx(markdown) : markdownToMdast(markdown, MD_PARSE);\n\n// Fenced code is excluded from the plain index by default (ranking noise) —\n// the \"markdown\" extraction keeps it for Ask AI grounding. Code-heavy docs opt\n// in via `search.indexing.includeCodeBlocks`, which indexes the fence body and\n// its rendered title (```ts blume.config.ts) but never the fence markers,\n// language, or other meta keywords.\nconst collectCode = (\n node: Extract<Nodes, { type: \"code\" }>,\n out: string[],\n walk: Walk\n): void => {\n if (!walk.includeCodeBlocks) {\n return;\n }\n const title = parseCodeTitle(node.meta ?? undefined);\n if (title) {\n out.push(title, \" \");\n }\n // The trailing space keeps the fence apart from what follows it.\n out.push(node.value, \" \");\n};\n\n// Block boundaries separate words; so does an empty inline element (`<br />`,\n// a self-closing icon), which otherwise fuses its neighbors.\nconst separatesWords = (node: Extract<Nodes, { children: unknown }>): boolean =>\n !INLINE_PARENTS.has(node.type) || node.children.length === 0;\n\n/** Fold one mdast node into the plain-text accumulator. */\nconst collectText = (node: Nodes, out: string[], walk: Walk): void => {\n if (NON_PROSE.has(node.type)) {\n return;\n }\n switch (node.type) {\n case \"code\": {\n collectCode(node, out, walk);\n return;\n }\n // A component with a serializer indexes as the text the page shows —\n // a Card's title and call to action, a TypeTable's descriptions — read\n // from the Markdown the serializer emits (its contract is Markdown, not\n // MDX). One without a serializer walks its children like any block.\n case \"mdxJsxFlowElement\": {\n // SAFETY: DownlevelNode is a structural subset of Satteri's mdast\n // output — the walk checks for offsets before reading them.\n const text = downlevelComponentNode(\n node as DownlevelNode,\n walk.downlevel\n );\n if (text !== null) {\n collectText(markdownToMdast(text, MD_PARSE), out, walk);\n return;\n }\n break;\n }\n // Inline code is kept verbatim — `Array<T>` is a type parameter, not a\n // tag, and its tokens must stay searchable.\n case \"inlineCode\": {\n out.push(node.value);\n return;\n }\n // A raw-HTML run. In a `.md` page (or an `.mdx` page MDX couldn't parse)\n // CommonMark folds a block-level `<Callout>` with no blank lines into ONE\n // html node holding all its inner prose, so the node can't just be\n // dropped — strip the tag-shaped runs and keep the text.\n case \"html\": {\n out.push(\n node.value.replaceAll(HTML_COMMENT, \" \").replaceAll(HTML_OR_JSX, \" \"),\n \" \"\n );\n return;\n }\n case \"break\": {\n out.push(\" \");\n return;\n }\n // Trailing heading markers (`[#custom-id]`, `{#custom-id}`, `[!toc]`,\n // `[toc]`) are anchor\n\n // metadata, not prose — strip them so they never pollute the index. Only\n // a marker that ends the heading's final plain-text child counts,\n // mirroring the renderer: a heading ending in inline code or an image\n // keeps its bracketed text on the page (so it stays searchable), and a\n // heading that is nothing but markers renders them literally.\n case \"heading\": {\n const last = node.children.at(-1);\n const trailing = last?.type === \"text\" ? last : undefined;\n const inner: string[] = [];\n const kept = trailing ? node.children.slice(0, -1) : node.children;\n for (const child of kept) {\n collectText(child, inner, walk);\n }\n if (trailing) {\n const stripped = parseHeadingMarkers(trailing.value).text;\n const literal = stripped === \"\" && node.children.length === 1;\n inner.push(literal ? trailing.value : stripped);\n }\n out.push(inner.join(\"\").trimEnd(), \" \");\n return;\n }\n default: {\n break;\n }\n }\n if (\"value\" in node) {\n out.push(node.value);\n return;\n }\n if (\"children\" in node) {\n for (const child of node.children) {\n collectText(child, out, walk);\n }\n if (separatesWords(node)) {\n out.push(\" \");\n }\n }\n};\n\n/**\n * Reduce Markdown/MDX to plain, searchable text: parse (GFM included) and walk\n * the tree instead of regex-stripping the source, so reference-style links,\n * autolinks, setext headings, tables, and literal `*`/`~`/`>` in prose all\n * reduce correctly. This feeds the client index *and* every hosted-provider\n * record, so anything lost here is a permanent search-quality loss.\n */\nconst toPlainText = (\n markdown: string,\n format: PageFormat,\n walk: Walk\n): string => {\n const tree = parseMarkdown(markdown, format);\n if (!tree) {\n return \"\";\n }\n const out: string[] = [];\n collectText(tree, out, walk);\n return out.join(\"\").replaceAll(WHITESPACE, \" \").trim();\n};\n\ninterface Crumbs {\n breadcrumb: string[];\n section: string;\n}\n\n/**\n * Map each page route to its ancestor section labels by walking the nav sidebar\n * once. The nearest ancestor group — the sidebar section a page appears under —\n * becomes its `section`, the dimension the search filter pills group by, so the\n * pills mirror the visible sidebar (and honor folder-meta renames).\n */\nconst buildCrumbIndex = (sidebar: NavNode[]): Map<string, Crumbs> => {\n const index = new Map<string, Crumbs>();\n // A config-sidebar section's landing page (the group's `root`) lives on the\n // *group* node, not on any page leaf — record it under the group's own label\n // so the section's landing page carries the same facet as its children. A\n // real page leaf for the route (filesystem sidebars emit index pages as\n // leaves) wins, so group routes are merged in only where no leaf claimed one.\n const groupRoutes = new Map<string, Crumbs>();\n const walk = (nodes: NavNode[], trail: string[]): void => {\n for (const node of nodes) {\n if (node.kind === \"group\") {\n if (node.route && !groupRoutes.has(node.route)) {\n groupRoutes.set(node.route, {\n breadcrumb: [...trail, node.label],\n section: node.label,\n });\n }\n walk(node.children, [...trail, node.label]);\n } else if (node.route) {\n index.set(node.route, {\n breadcrumb: trail,\n section: trail.at(-1) ?? \"\",\n });\n }\n }\n };\n walk(sidebar, []);\n for (const [route, crumbs] of groupRoutes) {\n if (!index.has(route)) {\n index.set(route, crumbs);\n }\n }\n return index;\n};\n\ninterface BuildOptions {\n includeWhenDisabled?: boolean;\n content?: \"markdown\" | \"plain\";\n audience?: VisibilityAudience;\n}\n\n/**\n * Read a page's body and reduce it per the extraction options. The\n * `\"markdown\"` body is agent-facing, so its components are downleveled in the\n * source with the serializers every agent surface uses (front matter in scope\n * for prop expressions). The plain body is parsed as written and walked, with\n * the same serializers applied per component on the way, so the index reads a\n * `<Card>` or `<TypeTable>` as the text it shows.\n */\nconst pageBody = async (\n project: BlumeProject,\n page: PageRecord | undefined,\n options: Pick<BuildOptions, \"audience\" | \"content\"> | undefined,\n components: Record<string, ComponentMarkdown>\n): Promise<string> => {\n if (!page) {\n return \"\";\n }\n const parsed = matter(await readExpandedEntryText(project, page));\n const visible = applyAudienceVisibility(\n parsed.content,\n options?.audience ?? \"web\"\n );\n if (options?.content === \"markdown\") {\n return downlevelComponents(visible.trim(), components, parsed.data);\n }\n return toPlainText(visible, page.format, {\n downlevel: {\n frontmatter: parsed.data,\n registry: componentRegistry(components),\n source: visible,\n },\n includeCodeBlocks: project.config.search.indexing.includeCodeBlocks,\n });\n};\n\n/**\n * Build search documents from the content graph. Only indexable pages are\n * included (per the route manifest), and content comes from the source files,\n * so the index is identical in dev and build.\n *\n * Pass `includeWhenDisabled` to index pages on their content merits even when\n * the search provider is `none` — used by the MCP server, which is a separate\n * feature from on-page search.\n *\n * `content` selects the extraction: `\"plain\"` (default) strips Markdown to bare\n * searchable text; `\"markdown\"` keeps the body's Markdown — code blocks, lists,\n * headings — for Ask AI grounding, where fenced examples are often the answer\n * and stripping them makes the model unable to cite content the docs do contain.\n * The `\"markdown\"` body is agent-facing, so components are downleveled with the\n * same serializers the `.md` mirror, llms-full.txt and MCP `get_page` use: a\n * page whose body is a `<CardGroup>` of `<Card>`s is prose to a reader and bare\n * JSX to anything reading the source, and section landing pages are exactly\n * that shape.\n *\n * `audience` resolves `<Visibility>` blocks before extraction: `\"web\"`\n * (default) keeps web-only content and drops agents-only blocks — the site\n * search and hosted syncs must not surface content the page hides — while\n * `\"agents\"` mirrors llms-full.txt/MCP `get_page` (web removed, agents kept).\n *\n * Whether fenced code joins the plain extraction is site-wide policy, read\n * from `search.indexing.includeCodeBlocks` here rather than threaded by each\n * caller, so the client index, hosted syncs, eject, and the MCP `search_docs`\n * index can't drift apart.\n */\nexport const buildSearchDocuments = async (\n project: BlumeProject,\n options?: BuildOptions\n): Promise<SearchDocument[]> => {\n const pageById = new Map(project.graph.pages.map((page) => [page.id, page]));\n\n // Build the crumb index from every locale's sidebar (their nodes carry\n // locale-prefixed routes), so localized pages get the right section/breadcrumb.\n // Falls back to the single default-locale nav when i18n is off.\n const byLocale = Object.values(project.graph.navigationByLocale ?? {});\n // Archived versions' trees contribute too, so snapshot pages get their own\n // section/breadcrumb instead of falling through to the \"Docs\" default.\n const byVersion = Object.values(project.graph.navigationByVersion ?? {})\n .flatMap((locales) => Object.values(locales))\n .map((nav) => nav.sidebar);\n const sidebars = [\n ...(byLocale.length > 0\n ? byLocale.map((nav) => nav.sidebar)\n : [project.graph.navigation?.sidebar ?? []]),\n ...byVersion,\n ];\n const crumbs = new Map<string, Crumbs>();\n for (const sidebar of sidebars) {\n for (const [route, crumb] of buildCrumbIndex(sidebar)) {\n crumbs.set(route, crumb);\n }\n }\n\n const indexable = project.manifest.routes.filter((route) => {\n if (!options?.includeWhenDisabled) {\n return route.indexable;\n }\n const page = pageById.get(route.id);\n return page ? contentIndexable(page, project.config) : false;\n });\n\n // Built once — `downlevelComponents` rebuilds its registry per call\n // otherwise.\n const components = projectComponentSerializers(project);\n\n return await Promise.all(\n indexable.map(async (route) => {\n const page = pageById.get(route.id);\n const body = await pageBody(project, page, options, components);\n const tags = page?.meta?.search?.tags;\n const crumb = crumbs.get(route.path);\n const facets = page ? pageFacets(page, project.config) : undefined;\n const document: SearchDocument = {\n breadcrumb: crumb?.breadcrumb ?? [],\n content: body,\n contentType: route.contentType,\n description: page?.description ?? \"\",\n locale: route.locale,\n route: route.path,\n section: crumb?.section || \"Docs\",\n tags: tags && tags.length > 0 ? tags : undefined,\n title: route.title,\n version: route.version,\n };\n if (facets) {\n document.facets = facets;\n }\n return document;\n })\n );\n};\n\n/**\n * Map per-page search documents to the flat record shape hosted backends\n * ingest. One record per page, keyed by route; the first tag becomes the\n * faceting `tag`.\n */\nexport const toSearchRecords = (documents: SearchDocument[]): SearchRecord[] =>\n documents.map((doc) => ({\n _id: doc.route,\n content: doc.content,\n description: doc.description,\n locale: doc.locale,\n tag: doc.tags?.[0],\n title: doc.title,\n url: doc.route,\n version: doc.version || \"current\",\n }));\n",
|
|
16
|
-
"/**\n * The shared fence-meta grammar: how the tokens after a code fence's language\n * (```ts title=\"...\" {1,3-5} lineNumbers) split, and which of them carry\n * reserved meaning. Both the Shiki-side meta reader (`code-title.ts`) and the\n * MDAST plugins that rewrite marked fences (`ts2js.ts`) consume this, so the\n * two layers can't drift on token boundaries or keyword lists.\n */\n\n/**\n * Any quoted `key=\"...\"` attr (spaces allowed inside the quotes). The body\n * excludes only the delimiting quote, so `title=\"foo's file.ts\"` (an\n * apostrophe inside double quotes) still matches. The left boundary stops\n * `subtitle=\"...\"` (or any `*title=` attr) from reading as a title.\n */\nexport const QUOTED_ATTR = /[\\w-]+=(?:\"[^\"]*\"|'[^']*')/gu;\n\n/**\n * A Shiki `{1,3-5}` line-range token, braces included. Shiki tolerates\n * whitespace inside the braces (`{1, 3-5}`), so the range is one token even\n * when it contains spaces — split on whitespace alone, `3-5}` would surface\n * as a bare word and be promoted to the block title.\n */\nconst LINE_RANGE = /\\{[^}]*\\}/u;\n\n/**\n * One fence-meta token: a quoted attribute, a line range, or a bare word, so\n * a keyword inside a quoted value (`title=\"enable ts2js later\"`) never reads\n * as a bare token and a spaced line range never splits.\n */\nconst META_TOKEN = new RegExp(\n `${QUOTED_ATTR.source}|${LINE_RANGE.source}|\\\\S+`,\n \"gu\"\n);\n\n/** Split a raw fence meta string into its tokens. */\nexport const metaTokens = (meta: string | null | undefined): string[] =>\n meta?.match(META_TOKEN) ?? [];\n\n/**\n * Bare keywords with reserved meaning after the language; never promoted to\n * a block title.\n */\nexport const RESERVED_META_KEYWORDS: ReadonlySet<string> = new Set([\n \"lineNumbers\",\n \"ts2js\",\n \"twoslash\",\n]);\n\n/** A Shiki `{1,3-5}` line-range token. */\nexport const isLineRange = (token: string): boolean => token.startsWith(\"{\");\n",
|
|
17
|
-
"/**\n * Code-fence meta. A Shiki transformer reads the tokens after the language and\n * promotes them to attributes on the rendered `<pre>`:\n *\n * - a title — the first bare token (```ts blume.config.ts) or `title=\"...\"` —\n * becomes `data-title`; the theme's code header shows it, falling back to the\n * language label.\n * - the `lineNumbers` keyword (```ts file.ts lineNumbers) becomes\n * `data-line-numbers`; the theme renders a counter-driven line-number gutter.\n */\n\nimport {\n isLineRange,\n metaTokens,\n QUOTED_ATTR,\n RESERVED_META_KEYWORDS,\n} from \"./fence-meta.ts\";\n\n/** The slice of Shiki's transformer `this` context Blume reads. */\ninterface CodeMetaContext {\n options: { meta?: { __raw?: string } };\n}\n\n/** The `<pre>` hast node a Shiki `pre` hook receives. */\ninterface PreNode {\n properties: Record<string, boolean | number | string | undefined>;\n}\n\n/** A Shiki-compatible transformer, typed structurally to avoid a Shiki dep. */\nexport interface CodeTitleTransformer {\n name: string;\n pre: (this: CodeMetaContext, node: PreNode) => void;\n}\n\nconst TITLE_ATTR = /(?:^|\\s)title=(?:\"(?<dq>[^\"]*)\"|'(?<sq>[^']*)')/u;\nconst LINE_NUMBERS = /(?:^|\\s)lineNumbers(?=\\s|$)/u;\n\n// Quoted attrs are blanked before keyword/bare-token scans so a quoted value\n// can't leak tokens (`title=\"enable lineNumbers later\"`).\nconst withoutQuotedAttrs = (raw: string): string =>\n raw.replace(QUOTED_ATTR, \" \");\n\n// The first bare token is the title (```ts blume.config.ts): a non-empty token\n// that isn't a Shiki line range (`{1,3-5}`), a `key=value` attr, or a reserved\n// keyword.\nconst isTitleToken = (token: string): boolean =>\n token.length > 0 &&\n !isLineRange(token) &&\n !token.includes(\"=\") &&\n !RESERVED_META_KEYWORDS.has(token);\n\n/** The title a fence's meta string promotes to `data-title`, if any. */\nexport const parseCodeTitle = (raw: string | undefined): string | undefined => {\n if (!raw) {\n return undefined;\n }\n // Blank every *other* quoted attr first, so a `title=\"…\"` embedded in\n // another attribute's value (`caption='set title=\"X\" here'`) can't be\n // promoted to the block title.\n const scrubbed = raw.replace(QUOTED_ATTR, (attr) =>\n attr.startsWith(\"title=\") ? attr : \" \"\n );\n const explicit = scrubbed.match(TITLE_ATTR);\n const attrTitle = explicit?.groups?.dq ?? explicit?.groups?.sq;\n if (attrTitle) {\n return attrTitle;\n }\n // The shared tokenizer keeps a quoted attr (rejected below by its `=`) and\n // a spaced line range (`{1, 3-5}`) whole, so neither can shed a fragment\n // that reads as a bare title.\n return metaTokens(raw).find(isTitleToken);\n};\n\nconst hasLineNumbers = (raw: string | undefined): boolean =>\n Boolean(raw && LINE_NUMBERS.test(withoutQuotedAttrs(raw)));\n\n/** Build the transformer. Runs after Shiki's built-in `data-language` hook. */\nexport const codeTitleTransformer = (): CodeTitleTransformer => ({\n name: \"blume:code-meta\",\n pre(node) {\n const raw = this.options.meta?.__raw;\n const title = parseCodeTitle(raw);\n if (title) {\n node.properties.dataTitle = title;\n }\n if (hasLineNumbers(raw)) {\n node.properties.dataLineNumbers = true;\n }\n },\n});\n",
|
|
18
|
-
"import type { ResolvedConfig } from \"../core/schema.ts\";\nimport type { PageRecord } from \"../core/types.ts\";\n\n/**\n * Resolve a page's facet values: the subset of its validated custom\n * frontmatter named by its content type's `facets` declaration. Strings facet\n * as-is; numbers and booleans are stringified so an enum-like `priority: 1`\n * or `enforced: true` still filters; any other shape (objects, arrays,\n * transformed dates) is not a facet value and is skipped. Returns `undefined`\n * when nothing facets, so the field stays absent from serialized documents\n * rather than shipping as `{}` on every page.\n */\n/** Whether a custom frontmatter value stringifies into a usable facet value. */\nconst isFacetValue = <T>(value: T): value is T & (string | number | boolean) =>\n typeof value === \"string\" ||\n typeof value === \"number\" ||\n typeof value === \"boolean\";\n\nexport const pageFacets = (\n page: Pick<PageRecord, \"contentType\" | \"custom\">,\n config: ResolvedConfig\n): Record<string, string> | undefined => {\n const declared = config.content.types[page.contentType]?.facets;\n if (!declared || declared.length === 0 || !page.custom) {\n return;\n }\n const facets: Record<string, string> = {};\n for (const key of declared) {\n const value = page.custom[key];\n if (isFacetValue(value)) {\n facets[key] = String(value);\n }\n }\n return Object.keys(facets).length > 0 ? facets : undefined;\n};\n",
|
|
19
|
-
"import { normalizeBasePath } from \"../../core/base-path.ts\";\nimport type { BlumeProject } from \"../../core/project-graph.ts\";\nimport type { Navigation } from \"../../core/types.ts\";\nimport { buildSearchDocuments } from \"../../search/documents.ts\";\nimport { pageFacets } from \"../../search/facets.ts\";\nimport type { OramaDoc } from \"../../search/orama-index.ts\";\nimport { agentMarkdown, buildRawMarkdown } from \"../markdown.ts\";\n\n/** A page entry surfaced by the `list_pages` MCP tool. */\nexport interface McpRoute {\n contentType: string;\n description?: string;\n /** Declared facet values (`content.types.<type>.facets`), key → value. */\n facets?: Record<string, string>;\n indexable: boolean;\n lastModified: string | null;\n /** Resolved locale code (the default locale when not under i18n). */\n locale: string;\n route: string;\n title: string;\n /** Docs version (`\"\"` for the current docs). */\n version: string;\n}\n\n/**\n * The self-contained snapshot the generated MCP endpoint serves. Bundles the\n * search documents, raw page Markdown, route list, and navigation so the server\n * works regardless of the configured search provider and needs no filesystem\n * access at request time. Serialized to `generated/mcp-data.json`.\n */\nexport interface McpData {\n /**\n * Normalized `deployment.base` (`\"\"` or `/seg`), layered onto routes when\n * emitting URLs — the site is base-less and routes are base-less manifest\n * paths, matching the sitemap/llms.txt convention.\n */\n base: string;\n /**\n * The site's `i18n.defaultLocale`, when i18n is configured. Selects a\n * word-segmenting Orama tokenizer for every non-Latin script, so\n * `search_docs` can match CJK, Cyrillic, Greek, Hebrew, or Devanagari\n * content.\n */\n defaultLocale?: string;\n /**\n * Archived docs version ids, in configured order. Present only on a\n * versioned site; its presence is what makes `search_docs`/`list_pages`\n * default to the current docs.\n */\n archivedVersions?: string[];\n documents: OramaDoc[];\n instructions?: string;\n name: string;\n navigation: Navigation;\n /** Per-locale trees for a locale-aware `get_navigation` (i18n sites only). */\n navigationByLocale?: Record<string, Navigation>;\n /** Per-archived-version trees, keyed by version id then locale code. */\n navigationByVersion?: Record<string, Record<string, Navigation>>;\n pages: Record<string, string>;\n routes: McpRoute[];\n site: string | null;\n version: string;\n}\n\n/** Build the MCP data snapshot from a resolved project. */\nexport const buildMcpData = async (project: BlumeProject): Promise<McpData> => {\n const { config, graph, manifest } = project;\n const [documents, rawMarkdown] = await Promise.all([\n // The MCP server is independent of on-page search, so index docs even when\n // the search provider is `none`. Documents are agent-facing, so\n // `<Visibility>` resolves like `get_page`/llms-full.txt (web-only content\n // removed, agents-only kept).\n buildSearchDocuments(project, {\n audience: \"agents\",\n includeWhenDisabled: true,\n }),\n buildRawMarkdown(project),\n ]);\n\n // `get_page` serves the agent variant: components downleveled to Markdown.\n const pages = Object.fromEntries(\n Object.entries(rawMarkdown).map(([route, entry]) => [\n route,\n agentMarkdown(entry),\n ])\n );\n\n const pageById = new Map(graph.pages.map((page) => [page.id, page]));\n\n const routes: McpRoute[] = [];\n for (const route of manifest.routes) {\n if (route.hidden) {\n continue;\n }\n const page = pageById.get(route.id);\n const facets = page ? pageFacets(page, config) : undefined;\n const entry: McpRoute = {\n contentType: route.contentType,\n description: page?.description,\n indexable: route.indexable,\n lastModified: route.lastModified ?? null,\n locale: route.locale,\n route: route.path,\n title: route.title,\n version: route.version,\n };\n if (facets) {\n entry.facets = facets;\n }\n routes.push(entry);\n }\n\n const data: McpData = {\n base: normalizeBasePath(config.deployment.base),\n defaultLocale: config.i18n?.defaultLocale,\n documents: documents.map((doc) => {\n const document: OramaDoc = {\n content: doc.content,\n contentType: doc.contentType,\n description: doc.description,\n locale: doc.locale,\n route: doc.route,\n title: doc.title,\n version: doc.version,\n };\n if (doc.facets) {\n document.facets = doc.facets;\n }\n return document;\n }),\n instructions: config.ai.mcp.instructions,\n name: config.ai.mcp.name ?? config.title,\n navigation: graph.navigation,\n pages,\n routes,\n site: config.deployment.site ?? null,\n version: manifest.blumeVersion,\n };\n if (config.versions) {\n data.archivedVersions = config.versions.archived.map(\n (version) => version.id\n );\n data.navigationByVersion = graph.navigationByVersion;\n }\n if (config.i18n) {\n data.navigationByLocale = graph.navigationByLocale;\n }\n return data;\n};\n"
|
|
20
|
-
],
|
|
21
|
-
"mappings": "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",
|
|
22
|
-
"debugId": "C8F15B2ECD8107F364756E2164756E21",
|
|
23
|
-
"names": []
|
|
24
|
-
}
|
|
@@ -1,240 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: OpenAPI / AsyncAPI
|
|
3
|
-
description: Drop in an OpenAPI or AsyncAPI spec and get a native API reference — one real page per operation, in your sidebar and search.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
Point Blume at an OpenAPI spec and it generates a native API reference: one **real page per operation**, grouped by tag in a tab-scoped sidebar, with schema tables, request/response examples, generated code samples, and an interactive [Try it](#try-it-playground) panel. Because each operation is a genuine Blume page, it gets its own URL, shows up in **site search** and `llms.txt`, and gets an Open Graph image — the same as any hand-written doc. The config below points Blume at the public Petstore spec as an example.
|
|
7
|
-
|
|
8
|
-
```ts blume.config.ts lineNumbers
|
|
9
|
-
openapi: {
|
|
10
|
-
enabled: true,
|
|
11
|
-
spec: "https://petstore3.swagger.io/api/v3/openapi.json",
|
|
12
|
-
}
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
That mounts the reference at `/reference` (an overview page) with each operation at `/reference/<tag>/<operation>`. The `spec` is either an `http(s)` URL or a path to a local file in your project. Blume parses it with [Scalar's OpenAPI parser](https://github.com/scalar/scalar) — Swagger 2.0 and OpenAPI 3.0 specs are upgraded to 3.1 automatically. Documenting a GraphQL API instead? See the [GraphQL reference](/docs/advanced/graphql).
|
|
16
|
-
|
|
17
|
-
The reference doesn't add a header tab on its own. To surface it, point a [navigation tab](/docs/content/navigation#tabs) at its route — this also scopes the operations sidebar for the native renderer:
|
|
18
|
-
|
|
19
|
-
```ts blume.config.ts
|
|
20
|
-
navigation: {
|
|
21
|
-
tabs: [{ label: "API", path: "/reference" }],
|
|
22
|
-
}
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
:::note
|
|
26
|
-
Operations are indexed for search by their **summary and tag**. The rendered schema tables and code samples aren't full-text indexed; search matches an operation's title and section, then links to its own page.
|
|
27
|
-
:::
|
|
28
|
-
|
|
29
|
-
## A local spec
|
|
30
|
-
|
|
31
|
-
A relative path is resolved from your project root and read at build time. Both JSON and YAML work:
|
|
32
|
-
|
|
33
|
-
```ts blume.config.ts lineNumbers
|
|
34
|
-
openapi: {
|
|
35
|
-
enabled: true,
|
|
36
|
-
spec: "./openapi.yaml",
|
|
37
|
-
}
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
## Route
|
|
41
|
-
|
|
42
|
-
`route` controls where the reference mounts — the overview page and the prefix for every operation route (and the route you point a navigation tab at):
|
|
43
|
-
|
|
44
|
-
```ts blume.config.ts lineNumbers
|
|
45
|
-
openapi: {
|
|
46
|
-
enabled: true,
|
|
47
|
-
route: "/api", // overview at /api, operations at /api/<tag>/<operation>
|
|
48
|
-
spec: "./openapi.yaml",
|
|
49
|
-
}
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
## Code samples and schemas
|
|
53
|
-
|
|
54
|
-
`codeSamples` picks which languages render per operation (built in: `curl`, `js`, `python`); `expandSchemas` starts nested schema rows expanded rather than collapsed:
|
|
55
|
-
|
|
56
|
-
```ts blume.config.ts lineNumbers
|
|
57
|
-
openapi: {
|
|
58
|
-
enabled: true,
|
|
59
|
-
spec: "./openapi.yaml",
|
|
60
|
-
codeSamples: ["curl", "js"],
|
|
61
|
-
expandSchemas: true,
|
|
62
|
-
}
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
## Try it playground
|
|
66
|
-
|
|
67
|
-
Operation pages rendered natively ship an interactive **Try it** panel by default. Blume generates the form from the operation itself: an input per path, query, and header parameter, a body editor built from the request-body schema, everything prefilled from the spec's examples. A server picker lists the spec's `servers`, with a free-text field for any other base URL, and auth inputs match the operation's [resolved security](#authorization) — bearer token, API key, and basic credentials, with OAuth2 as a token paste field (bring an access token; Blume doesn't run the flow).
|
|
68
|
-
|
|
69
|
-
The panel and the code samples stay in lockstep: values typed into the form update the generated samples live, so a copied curl command always matches exactly what **Send** would do. And it stays out of the way — the panel is server-rendered collapsed, and its JavaScript loads only when a reader first opens it. Readers who never touch it download none of it.
|
|
70
|
-
|
|
71
|
-
`playground: false` is the entire off switch:
|
|
72
|
-
|
|
73
|
-
```ts blume.config.ts lineNumbers
|
|
74
|
-
openapi: {
|
|
75
|
-
enabled: true,
|
|
76
|
-
spec: "./openapi.yaml",
|
|
77
|
-
playground: false,
|
|
78
|
-
}
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
### Credentials
|
|
82
|
-
|
|
83
|
-
Credentials typed into the auth inputs stay in memory and vanish on reload. Checking **Remember on this device** persists them in `localStorage`, scoped to the docs origin — they're never sent anywhere except the API being called. Code samples keep showing placeholders (`YOUR_TOKEN` and friends) whatever's typed, unless the reader toggles **Include my values in samples**.
|
|
84
|
-
|
|
85
|
-
### CORS and the proxy
|
|
86
|
-
|
|
87
|
-
As with the [Scalar renderer](#the-scalar-renderer), requests go **directly from the browser** to the target API, so the API must allow cross-origin requests from the docs site (`Access-Control-Allow-Origin`). For APIs that can't, set `playground.proxy`: a URL routes requests through a proxy you host, and `true` enables the built-in `/_api-proxy` route — which needs a server build, so it requires [`deployment.output: "server"`](/docs/deployment#server-rendering):
|
|
88
|
-
|
|
89
|
-
```ts blume.config.ts lineNumbers
|
|
90
|
-
openapi: {
|
|
91
|
-
enabled: true,
|
|
92
|
-
spec: "./openapi.yaml",
|
|
93
|
-
playground: {
|
|
94
|
-
proxy: true, // or a URL of your own
|
|
95
|
-
},
|
|
96
|
-
}
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
The built-in proxy only forwards requests to the origins your specs declare in `servers` — including across redirects — so a public docs deployment can't be aimed at other hosts on its network. A **Custom base URL** typed into the panel isn't a documented server: with the proxy enabled, requests to it are refused with a 403.
|
|
100
|
-
|
|
101
|
-
## Multiple specs
|
|
102
|
-
|
|
103
|
-
Use `sources` to publish more than one spec. Each source gets its own overview route, operation pages, and header tab. Give each a `label` (used for the tab and to derive its route), or set an explicit `route`:
|
|
104
|
-
|
|
105
|
-
```ts blume.config.ts lineNumbers
|
|
106
|
-
openapi: {
|
|
107
|
-
enabled: true,
|
|
108
|
-
sources: [
|
|
109
|
-
{ label: "Public API", spec: "./public.json" }, // → /reference/public-api
|
|
110
|
-
{ label: "Admin API", route: "/admin", spec: "./admin.json" },
|
|
111
|
-
],
|
|
112
|
-
}
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
`spec` is shorthand for a single-entry `sources`, so you only reach for `sources` when you have more than one.
|
|
116
|
-
|
|
117
|
-
### Per-source indexing
|
|
118
|
-
|
|
119
|
-
Generated pages participate in search, `llms.txt`, and crawler indexing by default. A secondary or overlapping spec can opt out of any surface without hiding its pages or removing it from navigation:
|
|
120
|
-
|
|
121
|
-
```ts blume.config.ts lineNumbers
|
|
122
|
-
openapi: {
|
|
123
|
-
enabled: true,
|
|
124
|
-
sources: [
|
|
125
|
-
{ label: "Public API", route: "/api", spec: "./public.json" },
|
|
126
|
-
{
|
|
127
|
-
label: "Platform API",
|
|
128
|
-
route: "/platform",
|
|
129
|
-
spec: "./platform.json",
|
|
130
|
-
includeInSearch: false,
|
|
131
|
-
includeInLlms: false,
|
|
132
|
-
noindex: true,
|
|
133
|
-
},
|
|
134
|
-
],
|
|
135
|
-
}
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
- `includeInSearch: false` keeps the source's overview and operations out of site search.
|
|
139
|
-
- `includeInLlms: false` keeps them out of both `llms.txt` files.
|
|
140
|
-
- `noindex: true` adds crawler noindex metadata and removes the pages from the sitemap.
|
|
141
|
-
|
|
142
|
-
Each operation page's meta description is the operation's own `description` (or `summary`), followed by a generated sentence naming the endpoint — "Reference for the `GET /pets` endpoint in the Petstore API." — so a spec of terse one-line summaries still ships a distinct, snippet-length description per page. That sentence is English. On a site whose spec prose is written in another language, set `seoDescriptionSuffix: false` on the source to drop it and describe each page with the authored prose alone; an operation with neither a `description` nor a `summary` falls back to its title (`GET /pets`), so no page ships an empty description:
|
|
143
|
-
|
|
144
|
-
```ts blume.config.ts lineNumbers
|
|
145
|
-
openapi: {
|
|
146
|
-
enabled: true,
|
|
147
|
-
sources: [{ spec: "./openapi.de.json", seoDescriptionSuffix: false }],
|
|
148
|
-
}
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
With the [Scalar renderer](#the-scalar-renderer), only `noindex` applies — a Scalar-rendered reference already sits outside Blume's search and `llms.txt`, so the two `include*` settings have nothing to act on there.
|
|
152
|
-
|
|
153
|
-
## Authorization
|
|
154
|
-
|
|
155
|
-
Operations that declare [security requirements](https://spec.openapis.org/oas/v3.1.0#security-requirement-object) render an **Authorization** section above their parameters, and the generated code samples send a placeholder credential (`Authorization: Bearer YOUR_TOKEN`, an API-key header, or a query key — whatever the scheme calls for). There's nothing to configure: Blume reads `security` from the spec, so the reference always matches what the API actually enforces.
|
|
156
|
-
|
|
157
|
-
The OpenAPI semantics carry over as written:
|
|
158
|
-
|
|
159
|
-
- An operation's own `security` overrides the document's root default; `security: []` marks it **public** and renders no Authorization section.
|
|
160
|
-
- Multiple requirement entries are alternatives — rendered as "or" groups; every scheme inside one entry is required together. The first alternative feeds the code samples.
|
|
161
|
-
- An empty `{}` entry means auth is **optional** for that operation, and the section says so.
|
|
162
|
-
- OAuth2 scopes are listed per scheme; scheme `description`s from `components.securitySchemes` render inline.
|
|
163
|
-
|
|
164
|
-
## The Scalar renderer
|
|
165
|
-
|
|
166
|
-
The native renderer is the default — operation pages, search integration, and the [Try it playground](#try-it-playground) above are all its work. If you'd rather embed [Scalar](https://scalar.com)'s self-contained API reference UI — its own sidebar, search, theme, and request client on a single route — set `renderer: "scalar"`:
|
|
167
|
-
|
|
168
|
-
```ts blume.config.ts lineNumbers
|
|
169
|
-
openapi: {
|
|
170
|
-
enabled: true,
|
|
171
|
-
renderer: "scalar",
|
|
172
|
-
spec: "./openapi.yaml",
|
|
173
|
-
theme: "purple", // a Scalar theme name (Scalar renderer only)
|
|
174
|
-
}
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
A Scalar-rendered reference is a self-contained embed on its own route — it doesn't weave into Blume's sidebar, search, or `llms.txt`, and Blume's [`playground`](#try-it-playground) config doesn't apply to it. It does follow Blume's light/dark toggle: the embed is pinned to the page's theme when it mounts and switches with it, so Scalar's own theme switch is hidden (set `scalar.forceDarkModeState` or `scalar.darkMode` to hand color mode back to Scalar). Scalar brings its own request client, which calls your **target API directly from the browser** (the `playground.proxy` route isn't available here), so the API must allow cross-origin requests from the docs site (`Access-Control-Allow-Origin`). `theme` applies to the Scalar renderer only.
|
|
178
|
-
|
|
179
|
-
### Passing Scalar options
|
|
180
|
-
|
|
181
|
-
`theme` is a shorthand for the one option most people reach for, but Scalar supports many more. A `scalar` object forwards any [Scalar configuration](https://github.com/scalar/scalar/blob/main/documentation/configuration.md) straight to the embedded reference — Blume doesn't gate the keys, so anything Scalar accepts flows through:
|
|
182
|
-
|
|
183
|
-
```ts blume.config.ts lineNumbers
|
|
184
|
-
openapi: {
|
|
185
|
-
enabled: true,
|
|
186
|
-
renderer: "scalar",
|
|
187
|
-
spec: "./openapi.yaml",
|
|
188
|
-
scalar: {
|
|
189
|
-
localization: { locale: "es" }, // translate Scalar's own UI
|
|
190
|
-
agent: { disabled: true }, // disable the Scalar Agent
|
|
191
|
-
hideTestRequestButton: true,
|
|
192
|
-
orderSchemaPropertiesBy: "preserve",
|
|
193
|
-
},
|
|
194
|
-
}
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
Blume's own [`i18n`](/docs/content/i18n) translates the docs chrome, but Scalar has a separate localization system — set `scalar.localization.locale` to translate the embedded reference too. Options in the `scalar` object win over Blume's derived config, so anything set here (including `theme`, `customCss`, or the spec `content`/`url`) overrides Blume's defaults. The same `scalar` block works on the `asyncapi` reference.
|
|
198
|
-
|
|
199
|
-
## AsyncAPI
|
|
200
|
-
|
|
201
|
-
Event-driven APIs use a sibling `asyncapi` block with the same shape — and the same native renderer. Each `send`/`receive` operation becomes a real page with message payload and header schema tables, channel parameters, protocol bindings, an Authorization section derived from the spec's `securitySchemes` (server-level and operation-level, alternatives as "or" groups), and a [Try it](#try-it-for-events) message composer. Only the default route differs (`/events`):
|
|
202
|
-
|
|
203
|
-
```ts blume.config.ts lineNumbers
|
|
204
|
-
asyncapi: {
|
|
205
|
-
enabled: true,
|
|
206
|
-
spec: "./asyncapi.yaml",
|
|
207
|
-
}
|
|
208
|
-
```
|
|
209
|
-
|
|
210
|
-
AsyncAPI **2.x specs are normalized to 3.x automatically** with the official AsyncAPI converter, so `publish`/`subscribe` channels map onto `send`/`receive` operation pages with stable URLs — later upgrading the spec file itself through the converter moves nothing. Operations group by tag; untagged operations group under their channel address.
|
|
211
|
-
|
|
212
|
-
Code samples are **protocol-aware**, keyed off the operation's binding (or its servers' protocol): `wscat` and a browser `WebSocket` snippet for WebSockets, `kcat` for Kafka, `mosquitto_pub`/`mosquitto_sub` for MQTT. `codeSamples` filters that set, the same way it picks languages on the `openapi` block; a protocol without a supported tool renders the message payload example alone rather than a fabricated client.
|
|
213
|
-
|
|
214
|
-
Everything documented above carries over, [`playground`](#try-it-for-events) included: `route`, `sources` with `label`/`route`, `expandSchemas`, the [per-source indexing](#per-source-indexing) flags (`seoDescriptionSuffix` too — the generated sentence names the channel and action instead of an endpoint), and search indexing by operation summary and tag.
|
|
215
|
-
|
|
216
|
-
Setting `renderer: "scalar"` opts back into the embedded Scalar SPA, where — as with OpenAPI — only `noindex` applies. Scalar has no AsyncAPI playground of its own; its embed auto-detects the document type and renders channels, operations, messages, and a Models section, so that swap trades the composer away.
|
|
217
|
-
|
|
218
|
-
### Try it for events
|
|
219
|
-
|
|
220
|
-
Operation pages rendered natively ship a **Try it** panel here too, on the same terms as the [OpenAPI panel](#try-it-playground): server-rendered collapsed, with its JavaScript loaded only when a reader first opens it.
|
|
221
|
-
|
|
222
|
-
Whatever the protocol, the panel opens with a payload editor prefilled from the message's `examples` — or, when the message declares none, from a value sampled out of the payload schema — validated against the message payload schema as you type. Under it sit an input per channel parameter and a server picker fed by the channel's `servers`, with a free-text field for any other URL. The protocol-aware code samples stay in lockstep with the form exactly as curl, js, and python do on an HTTP operation: the channel address template is filled in with the parameter values you type, so a copied `wscat`, `WebSocket`, `kcat`, or `mosquitto_pub` snippet matches what the form says.
|
|
223
|
-
|
|
224
|
-
Live connect is WebSocket-only. On a `ws` or `wss` binding the panel connects to the resolved channel URL, shows the connection state, and logs every frame with a timestamp. AsyncAPI 3 states an action from the API's side, and the panel follows it: a `receive` operation is one the API receives from you, so it gets a **Send** button that publishes the composed payload; a `send` operation only streams messages at you, so it connects and logs. There's no reconnect logic — once a socket closes, it stays closed until you connect again. Kafka, MQTT, AMQP, and every other protocol get the composer and the copyable CLI samples, and the panel says as much on the page: Blume doesn't fake broker connectivity from a browser tab.
|
|
225
|
-
|
|
226
|
-
`asyncapi.playground` mirrors `openapi.playground` — on by default with the native renderer, and `false` is the entire off switch:
|
|
227
|
-
|
|
228
|
-
```ts blume.config.ts lineNumbers
|
|
229
|
-
asyncapi: {
|
|
230
|
-
enabled: true,
|
|
231
|
-
spec: "./asyncapi.yaml",
|
|
232
|
-
playground: false,
|
|
233
|
-
}
|
|
234
|
-
```
|
|
235
|
-
|
|
236
|
-
:::note
|
|
237
|
-
`playground.proxy` doesn't apply to event operations. It forwards HTTP requests, and a WebSocket connect goes straight from the browser to the server named in the URL, so there's nothing for a proxy to sit in front of.
|
|
238
|
-
:::
|
|
239
|
-
|
|
240
|
-
The event composer collects no broker credentials. Each operation page's **Authorization** section documents what the broker expects, and a WebSocket connect carries only what's already in the URL. Nothing is persisted for event operations.
|