blume 2.0.3 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +1 -0
- package/CHANGELOG.md +142 -0
- package/README.md +1 -1
- package/dist/cli/{chunk-5f86nr5m.js → chunk-073rdvgb.js} +21 -48
- package/dist/cli/{chunk-5f86nr5m.js.map → chunk-073rdvgb.js.map} +4 -5
- package/dist/cli/{chunk-ga0pf4aj.js → chunk-0n9xkp3d.js} +12 -3
- package/dist/cli/{chunk-ga0pf4aj.js.map → chunk-0n9xkp3d.js.map} +3 -3
- package/dist/cli/chunk-150jk7km.js +52 -0
- package/dist/cli/chunk-150jk7km.js.map +10 -0
- package/dist/cli/{chunk-9t7a85s3.js → chunk-313vnn40.js} +30 -19
- package/dist/cli/chunk-313vnn40.js.map +10 -0
- package/dist/cli/{chunk-tkacnehg.js → chunk-3v3ffbrj.js} +2 -2
- package/dist/cli/{chunk-q58y5e6a.js → chunk-3x6x3pph.js} +46 -24
- package/dist/cli/chunk-3x6x3pph.js.map +10 -0
- package/dist/cli/{chunk-1d7ve1dm.js → chunk-42t4jxdx.js} +37 -5
- package/dist/cli/chunk-42t4jxdx.js.map +12 -0
- package/dist/cli/{chunk-8ktnccpt.js → chunk-54ztw1nj.js} +21 -2
- package/dist/cli/chunk-54ztw1nj.js.map +10 -0
- package/dist/cli/{chunk-hm3vjy5s.js → chunk-5vjtgs6e.js} +28 -10
- package/dist/cli/{chunk-hm3vjy5s.js.map → chunk-5vjtgs6e.js.map} +4 -4
- package/dist/cli/chunk-66w9j7p6.js +322 -0
- package/dist/cli/chunk-66w9j7p6.js.map +14 -0
- package/dist/cli/{chunk-2hsdwb9n.js → chunk-7ppf1rva.js} +34 -18
- package/dist/cli/chunk-7ppf1rva.js.map +10 -0
- package/dist/cli/{chunk-r9rcc4w7.js → chunk-8y2kmjv8.js} +7 -7
- package/dist/cli/{chunk-r20tn01b.js → chunk-c1y538kd.js} +8 -2
- package/dist/cli/{chunk-r20tn01b.js.map → chunk-c1y538kd.js.map} +3 -3
- package/dist/cli/{chunk-2eytanqx.js → chunk-c887fj0s.js} +2 -2
- package/dist/cli/{chunk-p3v96n38.js → chunk-cfyg47zg.js} +16 -6
- package/dist/cli/chunk-cfyg47zg.js.map +10 -0
- package/dist/cli/chunk-cm89scdv.js +443 -0
- package/dist/cli/chunk-cm89scdv.js.map +11 -0
- package/dist/cli/{chunk-akbpwfxc.js → chunk-dzwehpy7.js} +2 -2
- package/dist/cli/{chunk-wrr3j9w9.js → chunk-e6atmn4h.js} +299 -72
- package/dist/cli/chunk-e6atmn4h.js.map +36 -0
- package/dist/cli/{chunk-tzmab476.js → chunk-e8xw9qkh.js} +8 -6
- package/dist/cli/{chunk-tzmab476.js.map → chunk-e8xw9qkh.js.map} +3 -3
- package/dist/cli/{chunk-6dsbexzp.js → chunk-e9q7a2ak.js} +17 -15
- package/dist/cli/{chunk-6dsbexzp.js.map → chunk-e9q7a2ak.js.map} +2 -2
- package/dist/cli/{chunk-pv29h0wf.js → chunk-ehweg4fr.js} +5155 -1249
- package/dist/cli/chunk-ehweg4fr.js.map +218 -0
- package/dist/cli/{chunk-c8chx29p.js → chunk-ep6nd0rn.js} +390 -318
- package/dist/cli/chunk-ep6nd0rn.js.map +36 -0
- package/dist/cli/{chunk-p73c0m7w.js → chunk-fdrkhpjy.js} +18 -15
- package/dist/cli/{chunk-p73c0m7w.js.map → chunk-fdrkhpjy.js.map} +3 -3
- package/dist/cli/{chunk-2z928egk.js → chunk-ft9vfys6.js} +5 -5
- package/dist/cli/{chunk-5m5nmvyq.js → chunk-gb420vvz.js} +316 -79
- package/dist/cli/chunk-gb420vvz.js.map +17 -0
- package/dist/cli/{chunk-vg9r4eb9.js → chunk-hebn0tpx.js} +86 -13
- package/dist/cli/chunk-hebn0tpx.js.map +14 -0
- package/dist/cli/{chunk-35d4wj9f.js → chunk-htrwgabr.js} +41 -23
- package/dist/cli/chunk-htrwgabr.js.map +15 -0
- package/dist/cli/{chunk-g4hq16wv.js → chunk-hx3xqnk9.js} +16 -14
- package/dist/cli/{chunk-g4hq16wv.js.map → chunk-hx3xqnk9.js.map} +2 -2
- package/dist/cli/chunk-j134vrp2.js +65 -0
- package/dist/cli/chunk-j134vrp2.js.map +10 -0
- package/dist/cli/{chunk-a58773jm.js → chunk-kma9f6sf.js} +2 -2
- package/dist/cli/chunk-kwrg63tm.js +31 -0
- package/dist/cli/chunk-kwrg63tm.js.map +10 -0
- package/dist/cli/{chunk-ey84smr6.js → chunk-mrcccgnh.js} +6 -6
- package/dist/cli/{chunk-ey84smr6.js.map → chunk-mrcccgnh.js.map} +1 -1
- package/dist/cli/{chunk-yfyb25rh.js → chunk-n0v7m8ry.js} +24 -8
- package/dist/cli/chunk-n0v7m8ry.js.map +14 -0
- package/dist/cli/{chunk-j85vccga.js → chunk-ncj7w596.js} +576 -367
- package/dist/cli/chunk-ncj7w596.js.map +35 -0
- package/dist/cli/{chunk-5xvm6tfj.js → chunk-nq1d20w2.js} +17 -15
- package/dist/cli/{chunk-5xvm6tfj.js.map → chunk-nq1d20w2.js.map} +2 -2
- package/dist/cli/{chunk-pehfxfta.js → chunk-nrns10mj.js} +6 -98
- package/dist/cli/chunk-nrns10mj.js.map +10 -0
- package/dist/cli/{chunk-crgn1q09.js → chunk-sds923fa.js} +4 -4
- package/dist/cli/chunk-t2m5j4pr.js +104 -0
- package/dist/cli/chunk-t2m5j4pr.js.map +11 -0
- package/dist/cli/{chunk-acanzt5p.js → chunk-tdqhyxfz.js} +6 -6
- package/dist/cli/chunk-tphac74f.js +126 -0
- package/dist/cli/chunk-tphac74f.js.map +11 -0
- package/dist/cli/chunk-w1r4vase.js +18 -0
- package/dist/cli/chunk-w1r4vase.js.map +10 -0
- package/dist/cli/{chunk-3em5wd2y.js → chunk-zccbjcj0.js} +4 -3
- package/dist/cli/{chunk-3em5wd2y.js.map → chunk-zccbjcj0.js.map} +2 -2
- package/dist/cli/index.js +31 -20
- package/dist/cli/index.js.map +5 -5
- package/dist/types/ai/agent-surface.d.ts +8 -7
- package/dist/types/ai/ai-catalog.d.ts +7 -0
- package/dist/types/ai/ask-context.d.ts +26 -0
- package/dist/types/ai/ask.d.ts +271 -58
- package/dist/types/ai/component-markdown.d.ts +2 -0
- package/dist/types/ai/index.d.ts +2 -2
- package/dist/types/ai/openapi-components.d.ts +4 -1
- package/dist/types/ai/site-skill.d.ts +26 -0
- package/dist/types/ai/skills.d.ts +16 -0
- package/dist/types/astro/integration.d.ts +9 -0
- package/dist/types/astro/markdown-negotiation.d.ts +3 -0
- package/dist/types/astro/pages.d.ts +10 -0
- package/dist/types/captcha/hcaptcha.d.ts +27 -0
- package/dist/types/captcha/index.d.ts +28 -0
- package/dist/types/captcha/schema.d.ts +29 -0
- package/dist/types/captcha/turnstile.d.ts +28 -0
- package/dist/types/cli/init/scaffold.d.ts +1 -1
- package/dist/types/components/content/api-field.d.ts +62 -0
- package/dist/types/components/content/api-page.d.ts +100 -0
- package/dist/types/components/layout/nav-utils.d.ts +26 -0
- package/dist/types/components/openapi/code-samples.d.ts +29 -0
- package/dist/types/components/openapi/helpers.d.ts +158 -0
- package/dist/types/components/openapi/security.d.ts +95 -0
- package/dist/types/components/openapi/snippets.d.ts +60 -0
- package/dist/types/consent/ethyca.d.ts +48 -0
- package/dist/types/consent/index.d.ts +37 -0
- package/dist/types/consent/native.d.ts +28 -0
- package/dist/types/consent/osano.d.ts +42 -0
- package/dist/types/consent/schema.d.ts +110 -0
- package/dist/types/core/changelog-index.d.ts +8 -0
- package/dist/types/core/config-input.d.ts +217 -23
- package/dist/types/core/data.d.ts +53 -4
- package/dist/types/core/footer.d.ts +8 -0
- package/dist/types/core/i18n-ui.d.ts +88 -0
- package/dist/types/core/includes.d.ts +8 -2
- package/dist/types/core/links.d.ts +8 -0
- package/dist/types/core/localizable.d.ts +7 -0
- package/dist/types/core/page-modes.d.ts +29 -0
- package/dist/types/core/probe.d.ts +2 -0
- package/dist/types/core/project-graph.d.ts +6 -0
- package/dist/types/core/redirect-patterns.d.ts +152 -0
- package/dist/types/core/related.d.ts +40 -0
- package/dist/types/core/schema.d.ts +670 -56
- package/dist/types/core/sources/cache.d.ts +3 -0
- package/dist/types/core/sources/contentful-rich-text.d.ts +11 -2
- package/dist/types/core/sources/contentful.d.ts +17 -1
- package/dist/types/core/sources/lexical.d.ts +5 -0
- package/dist/types/core/sources/normalize.d.ts +20 -1
- package/dist/types/core/sources/read.d.ts +5 -0
- package/dist/types/core/sources/remote.d.ts +37 -10
- package/dist/types/core/sources/sanity.d.ts +1 -1
- package/dist/types/core/types.d.ts +18 -1
- package/dist/types/core/variables.d.ts +42 -0
- package/dist/types/deploy/adapter-output.d.ts +2 -1
- package/dist/types/deploy/adapters/cloudflare.d.ts +7 -5
- package/dist/types/deploy/adapters/node.d.ts +58 -23
- package/dist/types/deploy/adapters/registry.d.ts +10 -0
- package/dist/types/deploy/adapters/types.d.ts +3 -2
- package/dist/types/deploy/node-headers.d.ts +13 -2
- package/dist/types/deploy/platforms/cloudflare.d.ts +15 -7
- package/dist/types/deploy/platforms/netlify.d.ts +7 -0
- package/dist/types/deploy/platforms/node.d.ts +3 -1
- package/dist/types/deploy/platforms/types.d.ts +18 -7
- package/dist/types/deploy/platforms/vercel.d.ts +26 -1
- package/dist/types/deploy/redirects.d.ts +55 -11
- package/dist/types/deploy/sitemap.d.ts +2 -1
- package/dist/types/deploy/vercel-negotiation.d.ts +51 -21
- package/dist/types/markdown/code-title.d.ts +17 -0
- package/dist/types/markdown/features.d.ts +3 -2
- package/dist/types/narration/build.d.ts +70 -0
- package/dist/types/narration/provider.d.ts +26 -0
- package/dist/types/narration/script.d.ts +154 -0
- package/dist/types/openapi/checks.d.ts +27 -0
- package/dist/types/openapi/graphql.d.ts +6 -0
- package/dist/types/openapi/model.d.ts +27 -10
- package/dist/types/openapi/overlay.d.ts +54 -0
- package/dist/types/openapi/parse.d.ts +16 -0
- package/dist/types/openapi/references.d.ts +30 -4
- package/dist/types/ratelimit/cloudflare.d.ts +39 -0
- package/dist/types/ratelimit/index.d.ts +27 -0
- package/dist/types/ratelimit/memory.d.ts +36 -0
- package/dist/types/ratelimit/schema.d.ts +119 -0
- package/dist/types/ratelimit/upstash.d.ts +46 -0
- package/dist/types/ratelimit/wrangler.d.ts +23 -0
- package/dist/types/reference/asyncapi.d.ts +10 -10
- package/dist/types/reference/graphql.d.ts +89 -11
- package/dist/types/reference/index.d.ts +1 -1
- package/dist/types/reference/openapi.d.ts +33 -12
- package/dist/types/reference/options.d.ts +82 -6
- package/dist/types/reference/scalar.d.ts +13 -0
- package/dist/types/reference/schema.d.ts +144 -36
- package/dist/types/search/adapters/registry.d.ts +3 -0
- package/dist/types/search/adapters/typesense.d.ts +14 -4
- package/dist/types/search/build.d.ts +3 -1
- package/dist/types/search/documents.d.ts +12 -1
- package/dist/types/search/orama-index.d.ts +13 -9
- package/dist/types/search/sync/algolia.d.ts +5 -2
- package/dist/types/search/sync/index.d.ts +3 -1
- package/dist/types/search/sync/typesense.d.ts +11 -7
- package/dist/types/seo/metatags.d.ts +20 -0
- package/dist/types/sources/contentful.d.ts +16 -0
- package/dist/types/sources/github-releases.d.ts +8 -1
- package/dist/types/sources/payload.d.ts +2 -0
- package/dist/types/sources/registry.d.ts +54 -0
- package/dist/types/sources/shared.d.ts +3 -0
- package/dist/types/sources/strapi.d.ts +2 -0
- package/docs/01-quickstart.mdx +4 -2
- package/docs/02-deployment.mdx +71 -17
- package/docs/03-upgrading.mdx +4 -4
- package/docs/04-migrating.mdx +1 -1
- package/docs/advanced/changelog.mdx +13 -2
- package/docs/advanced/skills.mdx +15 -1
- package/docs/cli/audit.mdx +51 -8
- package/docs/cli/doctor.mdx +5 -1
- package/docs/cli/evals.mdx +4 -4
- package/docs/cli/index.mdx +1 -0
- package/docs/cli/translate.mdx +1 -0
- package/docs/cli/validate.mdx +4 -3
- package/docs/configuration/analytics.mdx +3 -1
- package/docs/configuration/assistant.mdx +194 -38
- package/docs/configuration/consent.mdx +100 -0
- package/docs/configuration/customization.mdx +8 -6
- package/docs/configuration/export.mdx +5 -3
- package/docs/configuration/index.mdx +64 -5
- package/docs/configuration/meta.ts +3 -0
- package/docs/configuration/narration.mdx +119 -0
- package/docs/configuration/rate-limiting.mdx +90 -0
- package/docs/configuration/search.mdx +63 -12
- package/docs/configuration/theming.mdx +3 -1
- package/docs/content/components.mdx +108 -2
- package/docs/content/frontmatter.mdx +80 -2
- package/docs/content/i18n.mdx +39 -4
- package/docs/content/includes.mdx +16 -0
- package/docs/content/meta.mdx +1 -0
- package/docs/content/meta.ts +1 -0
- package/docs/content/navigation.mdx +32 -10
- package/docs/content/sources/contentful.mdx +35 -0
- package/docs/content/sources/custom.mdx +43 -0
- package/docs/content/sources/github-releases.mdx +39 -0
- package/docs/content/sources/index.mdx +91 -0
- package/docs/content/sources/meta.ts +16 -0
- package/docs/content/sources/notion.mdx +32 -0
- package/docs/content/sources/obsidian.mdx +37 -0
- package/docs/content/sources/payload.mdx +31 -0
- package/docs/content/sources/remote-mdx.mdx +44 -0
- package/docs/content/sources/sanity.mdx +29 -0
- package/docs/content/sources/strapi.mdx +32 -0
- package/docs/content/syntax.mdx +69 -4
- package/docs/content/variables.mdx +41 -0
- package/docs/discoverability/agent-discovery.mdx +29 -2
- package/docs/discoverability/index.mdx +2 -2
- package/docs/discoverability/json-api.mdx +2 -0
- package/docs/discoverability/llms-txt.mdx +2 -2
- package/docs/discoverability/markdown.mdx +3 -1
- package/docs/discoverability/mcp.mdx +4 -0
- package/docs/discoverability/metadata.mdx +17 -0
- package/docs/discoverability/sitemap-and-robots.mdx +1 -1
- package/docs/references/api-pages.mdx +74 -0
- package/docs/references/asyncapi.mdx +4 -2
- package/docs/references/graphql.mdx +14 -2
- package/docs/references/meta.ts +1 -1
- package/docs/references/openapi.mdx +148 -7
- package/docs/references/scalar.mdx +3 -1
- package/package.json +43 -8
- package/skills/blume/SKILL.md +3 -0
- package/skills/blume-migrate/SKILL.md +6 -6
- package/skills/blume-migrate/references/docusaurus.md +7 -7
- package/skills/blume-migrate/references/fumadocs.md +17 -13
- package/skills/blume-migrate/references/mintlify.md +37 -19
- package/skills/blume-migrate/references/nextra.md +10 -7
- package/skills/blume-migrate/references/starlight.md +11 -8
- package/skills/blume-migrate/scripts/mintlify-codemod.mjs +60 -14
- package/skills/blume-write-skill/SKILL.md +82 -0
- package/src/ai/agent-readability.ts +2 -1
- package/src/ai/agent-surface.ts +9 -7
- package/src/ai/ai-catalog.ts +6 -3
- package/src/ai/ask-context.ts +103 -20
- package/src/ai/ask-data.ts +6 -0
- package/src/ai/ask-limits.ts +13 -0
- package/src/ai/ask-tools.ts +142 -0
- package/src/ai/ask.ts +354 -76
- package/src/ai/changelog-markdown.ts +11 -6
- package/src/ai/component-markdown.ts +76 -1
- package/src/ai/cors.ts +14 -3
- package/src/ai/index.ts +12 -0
- package/src/ai/llms.ts +6 -2
- package/src/ai/markdown.ts +2 -1
- package/src/ai/mcp/data.ts +6 -0
- package/src/ai/mcp/query.ts +14 -6
- package/src/ai/openapi-components.ts +36 -6
- package/src/ai/relative-links.ts +4 -38
- package/src/ai/site-skill.ts +274 -0
- package/src/ai/skills.ts +35 -2
- package/src/ai/write-skill.ts +97 -0
- package/src/astro/generate.ts +210 -66
- package/src/astro/index.ts +1 -0
- package/src/astro/integration.ts +42 -4
- package/src/astro/markdown-negotiation.ts +3 -0
- package/src/astro/module-types.ts +9 -1
- package/src/astro/pages.ts +23 -0
- package/src/astro/templates.ts +406 -108
- package/src/astro/variables.ts +31 -0
- package/src/audit/catalog.ts +38 -3
- package/src/audit/checks/contrast.ts +183 -0
- package/src/audit/checks/i18n.ts +2 -3
- package/src/audit/checks/indexability.ts +2 -3
- package/src/audit/checks/links.ts +19 -13
- package/src/audit/checks/network.ts +110 -6
- package/src/audit/checks/redirects.ts +6 -4
- package/src/audit/checks/robots.ts +58 -2
- package/src/audit/checks/sitemap.ts +2 -3
- package/src/audit/redirects.ts +46 -15
- package/src/audit/run.ts +5 -0
- package/src/audit/types.ts +4 -1
- package/src/audit/url.ts +14 -5
- package/src/blume-modules.d.ts +12 -1
- package/src/captcha/client.ts +152 -0
- package/src/captcha/hcaptcha.ts +36 -0
- package/src/captcha/index.ts +28 -0
- package/src/captcha/schema.ts +32 -0
- package/src/captcha/turnstile.ts +38 -0
- package/src/captcha/verify.ts +71 -0
- package/src/cli/args.ts +51 -0
- package/src/cli/build-failure.ts +23 -0
- package/src/cli/command-meta.ts +5 -0
- package/src/cli/commands/audit.ts +10 -1
- package/src/cli/commands/dev.ts +18 -2
- package/src/cli/commands/doctor.ts +67 -28
- package/src/cli/commands/eval.ts +36 -4
- package/src/cli/commands/init.ts +6 -4
- package/src/cli/commands/skill.ts +96 -0
- package/src/cli/commands/validate.ts +10 -1
- package/src/cli/index.ts +5 -0
- package/src/cli/init/scaffold.ts +3 -3
- package/src/cli/prepare.ts +6 -3
- package/src/cli/required-secrets.ts +72 -30
- package/src/components/BlumePage.astro +2 -0
- package/src/components/content/ApiEndpoint.astro +18 -0
- package/src/components/content/ApiField.astro +79 -0
- package/src/components/content/ApiPlayground.astro +66 -0
- package/src/components/content/ApiRail.astro +19 -0
- package/src/components/content/Color.astro +1 -0
- package/src/components/content/FileTree.astro +1 -0
- package/src/components/content/GithubInfo.astro +1 -0
- package/src/components/content/Panel.astro +1 -0
- package/src/components/content/ParamField.astro +25 -0
- package/src/components/content/RequestExample.astro +17 -0
- package/src/components/content/ResponseExample.astro +17 -0
- package/src/components/content/ResponseField.astro +32 -0
- package/src/components/content/Step.astro +1 -0
- package/src/components/content/Tile.astro +1 -0
- package/src/components/content/Tooltip.astro +1 -0
- package/src/components/content/Tree.astro +1 -0
- package/src/components/content/TypeTable.astro +4 -1
- package/src/components/content/View.astro +21 -0
- package/src/components/content/ViewSwitcher.astro +168 -0
- package/src/components/content/api-field.ts +103 -0
- package/src/components/content/api-page.ts +248 -0
- package/src/components/content/mermaid-element.ts +15 -1
- package/src/components/content/view-switcher.ts +71 -0
- package/src/components/index.ts +2 -0
- package/src/components/islands/Assistant.astro +10 -1
- package/src/components/islands/assistant.tsx +104 -14
- package/src/components/islands/code-question.ts +62 -0
- package/src/components/islands/hooks.ts +136 -21
- package/src/components/islands/support-link.ts +68 -0
- package/src/components/layout/Analytics.astro +11 -2
- package/src/components/layout/Banner.astro +14 -10
- package/src/components/layout/Breadcrumbs.astro +6 -2
- package/src/components/layout/ConsentBanner.astro +62 -0
- package/src/components/layout/ConsentHead.astro +38 -0
- package/src/components/layout/Directory.astro +94 -0
- package/src/components/layout/DrawerSwitchers.astro +51 -0
- package/src/components/layout/Empty.astro +2 -2
- package/src/components/layout/Header.astro +122 -80
- package/src/components/layout/LanguageSwitcher.astro +48 -15
- package/src/components/layout/NarrationPlayer.astro +1085 -0
- package/src/components/layout/NavSelector.astro +33 -7
- package/src/components/layout/PageActions.astro +139 -24
- package/src/components/layout/PageFeedback.astro +54 -49
- package/src/components/layout/PageLayout.astro +71 -3
- package/src/components/layout/ReferenceLayout.astro +17 -6
- package/src/components/layout/RelatedPages.astro +42 -0
- package/src/components/layout/RootLayout.astro +318 -47
- package/src/components/layout/Search.astro +47 -19
- package/src/components/layout/SiteFooter.astro +124 -0
- package/src/components/layout/SiteMetatags.astro +16 -0
- package/src/components/layout/TableOfContents.astro +24 -17
- package/src/components/layout/consent/native.ts +79 -0
- package/src/components/layout/consent/types.ts +37 -0
- package/src/components/layout/footer-links.ts +53 -0
- package/src/components/layout/footer-socials.ts +87 -0
- package/src/components/layout/head-scripts.ts +6 -1
- package/src/components/layout/locale-redirect.ts +107 -0
- package/src/components/layout/nav-utils.ts +83 -0
- package/src/components/layout/page-feedback.ts +96 -0
- package/src/components/layout/search/algolia.ts +10 -1
- package/src/components/layout/search/analytics.ts +126 -0
- package/src/components/layout/search/endpoint.ts +48 -5
- package/src/components/layout/search/flexsearch.ts +17 -5
- package/src/components/layout/search/orama-cloud.ts +26 -14
- package/src/components/layout/search/pagefind.ts +114 -5
- package/src/components/layout/search/types.ts +32 -0
- package/src/components/layout/search/typesense.ts +19 -3
- package/src/components/openapi/AsyncApiOperation.astro +14 -7
- package/src/components/openapi/Authorization.astro +10 -0
- package/src/components/openapi/GraphqlOperation.astro +5 -0
- package/src/components/openapi/Operation.astro +184 -13
- package/src/components/openapi/ParametersTable.astro +8 -4
- package/src/components/openapi/Playground.astro +7 -1
- package/src/components/openapi/RequestBody.astro +24 -5
- package/src/components/openapi/RequestPanel.astro +59 -7
- package/src/components/openapi/Responses.astro +7 -4
- package/src/components/openapi/SchemaTable.astro +15 -3
- package/src/components/openapi/async-snippets.ts +65 -9
- package/src/components/openapi/async.ts +123 -29
- package/src/components/openapi/code-samples.ts +97 -0
- package/src/components/openapi/graphql-helpers.ts +35 -12
- package/src/components/openapi/helpers.ts +36 -2
- package/src/components/openapi/message-model.ts +54 -11
- package/src/components/openapi/message.ts +14 -4
- package/src/components/openapi/operation-model.ts +16 -7
- package/src/components/openapi/playground-client.ts +24 -5
- package/src/components/openapi/playground-schema.ts +1 -1
- package/src/components/openapi/schema-tree.ts +44 -14
- package/src/components/openapi/security.ts +16 -3
- package/src/components/openapi/snippets.ts +873 -10
- package/src/components/openapi/validate-json.ts +4 -3
- package/src/components/props.ts +6 -0
- package/src/consent/client.ts +95 -0
- package/src/consent/clients.ts +19 -0
- package/src/consent/ethyca.ts +71 -0
- package/src/consent/head.ts +24 -0
- package/src/consent/index.ts +37 -0
- package/src/consent/init.ts +29 -0
- package/src/consent/native.ts +36 -0
- package/src/consent/osano.ts +59 -0
- package/src/consent/schema.ts +30 -0
- package/src/core/builtin-tags.ts +5 -0
- package/src/core/changelog-index.ts +31 -1
- package/src/core/client-address.ts +19 -0
- package/src/core/component-diagnostics.ts +53 -0
- package/src/core/component-overrides.ts +32 -0
- package/src/core/config-input.ts +225 -23
- package/src/core/data.ts +46 -4
- package/src/core/diagnostics.ts +8 -2
- package/src/core/footer.ts +26 -0
- package/src/core/graph.ts +39 -36
- package/src/core/i18n-ui.ts +84 -2
- package/src/core/includes.ts +21 -4
- package/src/core/links.ts +63 -19
- package/src/core/localizable.ts +26 -0
- package/src/core/navigation.ts +43 -12
- package/src/core/page-modes.ts +56 -0
- package/src/core/probe.ts +26 -6
- package/src/core/project-graph.ts +163 -16
- package/src/core/redirect-patterns.ts +520 -0
- package/src/core/related.ts +131 -0
- package/src/core/schema.ts +339 -66
- package/src/core/sources/cache.ts +6 -0
- package/src/core/sources/contentful-rich-text.ts +29 -6
- package/src/core/sources/contentful.ts +135 -25
- package/src/core/sources/filesystem.ts +26 -4
- package/src/core/sources/github-releases.ts +12 -1
- package/src/core/sources/lexical.ts +15 -10
- package/src/core/sources/mdx-remote.ts +62 -10
- package/src/core/sources/normalize.ts +228 -41
- package/src/core/sources/notion.ts +7 -1
- package/src/core/sources/obsidian.ts +16 -3
- package/src/core/sources/payload.ts +38 -15
- package/src/core/sources/read.ts +8 -3
- package/src/core/sources/remote.ts +72 -15
- package/src/core/sources/resolve.ts +11 -1
- package/src/core/sources/sanity.ts +43 -25
- package/src/core/sources/strapi.ts +2 -2
- package/src/core/types.ts +19 -1
- package/src/core/ui-packs/ar.ts +44 -0
- package/src/core/ui-packs/bg.ts +46 -0
- package/src/core/ui-packs/bn.ts +44 -0
- package/src/core/ui-packs/ca.ts +47 -0
- package/src/core/ui-packs/cs.ts +45 -0
- package/src/core/ui-packs/da.ts +46 -0
- package/src/core/ui-packs/de.ts +47 -0
- package/src/core/ui-packs/el.ts +46 -0
- package/src/core/ui-packs/es.ts +47 -0
- package/src/core/ui-packs/fa.ts +46 -0
- package/src/core/ui-packs/fi.ts +46 -0
- package/src/core/ui-packs/fr.ts +47 -0
- package/src/core/ui-packs/he.ts +44 -0
- package/src/core/ui-packs/hi.ts +45 -0
- package/src/core/ui-packs/hr.ts +46 -0
- package/src/core/ui-packs/hu.ts +45 -0
- package/src/core/ui-packs/id.ts +46 -0
- package/src/core/ui-packs/it.ts +46 -0
- package/src/core/ui-packs/ja.ts +46 -0
- package/src/core/ui-packs/ko.ts +45 -0
- package/src/core/ui-packs/nl.ts +47 -0
- package/src/core/ui-packs/no.ts +45 -0
- package/src/core/ui-packs/pl.ts +46 -0
- package/src/core/ui-packs/pt-br.ts +47 -0
- package/src/core/ui-packs/pt.ts +46 -0
- package/src/core/ui-packs/ro.ts +46 -0
- package/src/core/ui-packs/ru.ts +46 -0
- package/src/core/ui-packs/sk.ts +45 -0
- package/src/core/ui-packs/sr.ts +46 -0
- package/src/core/ui-packs/sv.ts +45 -0
- package/src/core/ui-packs/th.ts +44 -0
- package/src/core/ui-packs/tr.ts +46 -0
- package/src/core/ui-packs/uk.ts +46 -0
- package/src/core/ui-packs/vi.ts +46 -0
- package/src/core/ui-packs/zh-tw.ts +44 -0
- package/src/core/ui-packs/zh.ts +44 -0
- package/src/core/variables.ts +103 -0
- package/src/deploy/adapter-output.ts +7 -2
- package/src/deploy/adapters/cloudflare.ts +7 -5
- package/src/deploy/adapters/node.ts +47 -9
- package/src/deploy/adapters/types.ts +7 -3
- package/src/deploy/artifacts.ts +79 -9
- package/src/deploy/cloudflare-negotiation.ts +35 -5
- package/src/deploy/headers.ts +2 -1
- package/src/deploy/node-headers.ts +63 -9
- package/src/deploy/platforms/cloudflare.ts +45 -9
- package/src/deploy/platforms/netlify.ts +35 -2
- package/src/deploy/platforms/node.ts +12 -2
- package/src/deploy/platforms/types.ts +21 -7
- package/src/deploy/platforms/vercel.ts +149 -17
- package/src/deploy/redirects.ts +151 -24
- package/src/deploy/rss.ts +3 -1
- package/src/deploy/sitemap.ts +39 -5
- package/src/deploy/vercel-negotiation.ts +326 -102
- package/src/eval/agents.ts +18 -1
- package/src/eval/prompts.ts +6 -3
- package/src/eval/report.ts +34 -13
- package/src/eval/run.ts +5 -0
- package/src/markdown/api-rail.ts +259 -0
- package/src/markdown/code-title.ts +35 -9
- package/src/markdown/features.ts +3 -2
- package/src/markdown/fence-meta.ts +2 -0
- package/src/markdown/include.ts +11 -1
- package/src/markdown/index.ts +28 -5
- package/src/markdown/math.ts +4 -4
- package/src/markdown/relative-links.ts +3 -2
- package/src/markdown/variables.ts +56 -0
- package/src/markdown/views.ts +82 -0
- package/src/narration/build.ts +412 -0
- package/src/narration/player.ts +249 -0
- package/src/narration/provider.ts +29 -0
- package/src/narration/script.ts +592 -0
- package/src/og/card.ts +7 -6
- package/src/openapi/checks.ts +273 -0
- package/src/openapi/graphql-build.ts +21 -0
- package/src/openapi/graphql.ts +11 -0
- package/src/openapi/model.ts +72 -27
- package/src/openapi/overlay.ts +256 -0
- package/src/openapi/parse.ts +182 -4
- package/src/openapi/references.ts +64 -8
- package/src/openapi/render-mdx.ts +5 -1
- package/src/openapi/scalar.ts +40 -6
- package/src/openapi/source.ts +40 -15
- package/src/ratelimit/cloudflare.ts +54 -0
- package/src/ratelimit/index.ts +30 -0
- package/src/ratelimit/memory.ts +45 -0
- package/src/ratelimit/runtime.ts +247 -0
- package/src/ratelimit/schema.ts +28 -0
- package/src/ratelimit/upstash.ts +60 -0
- package/src/ratelimit/wrangler.ts +70 -0
- package/src/reference/asyncapi.ts +2 -2
- package/src/reference/graphql.ts +20 -3
- package/src/reference/index.ts +1 -0
- package/src/reference/openapi.ts +21 -7
- package/src/reference/options.ts +80 -8
- package/src/reference/scalar.ts +33 -8
- package/src/registry/eject.ts +23 -5
- package/src/registry/registry.ts +7 -0
- package/src/search/adapters/locale-scope.ts +30 -0
- package/src/search/adapters/typesense.ts +14 -4
- package/src/search/build.ts +21 -3
- package/src/search/documents.ts +26 -2
- package/src/search/orama-index.ts +191 -34
- package/src/search/pagefind-ranking.ts +39 -0
- package/src/search/source-pages.ts +29 -0
- package/src/search/sync/algolia.ts +111 -10
- package/src/search/sync/index.ts +52 -13
- package/src/search/sync/orama-cloud.ts +3 -0
- package/src/search/sync/typesense.ts +76 -26
- package/src/seo/metatags.ts +67 -0
- package/src/sources/contentful.ts +12 -0
- package/src/sources/github-releases.ts +7 -1
- package/src/sources/shared.ts +3 -0
- package/src/theme/chrome-icons.ts +1 -0
- package/src/theme/color.ts +500 -0
- package/src/theme/entry.ts +85 -6
- package/src/theme/palette.ts +121 -45
- package/src/translate/prompts.ts +1 -0
- package/src/translate/validate.ts +31 -3
- package/src/upgrade/upgrade.ts +4 -171
- package/dist/cli/chunk-1d7ve1dm.js.map +0 -12
- package/dist/cli/chunk-2hsdwb9n.js.map +0 -10
- package/dist/cli/chunk-35d4wj9f.js.map +0 -15
- package/dist/cli/chunk-364znk6q.js +0 -75
- package/dist/cli/chunk-364znk6q.js.map +0 -11
- package/dist/cli/chunk-5m5nmvyq.js.map +0 -13
- package/dist/cli/chunk-8ktnccpt.js.map +0 -10
- package/dist/cli/chunk-9t7a85s3.js.map +0 -10
- package/dist/cli/chunk-b07cmahc.js +0 -145
- package/dist/cli/chunk-b07cmahc.js.map +0 -11
- package/dist/cli/chunk-c8chx29p.js.map +0 -36
- package/dist/cli/chunk-e04dxsz1.js +0 -39
- package/dist/cli/chunk-e04dxsz1.js.map +0 -10
- package/dist/cli/chunk-j85vccga.js.map +0 -36
- package/dist/cli/chunk-p3v96n38.js.map +0 -10
- package/dist/cli/chunk-pehfxfta.js.map +0 -10
- package/dist/cli/chunk-pv29h0wf.js.map +0 -190
- package/dist/cli/chunk-q58y5e6a.js.map +0 -10
- package/dist/cli/chunk-vg9r4eb9.js.map +0 -14
- package/dist/cli/chunk-wrr3j9w9.js.map +0 -35
- package/dist/cli/chunk-yfyb25rh.js.map +0 -14
- package/docs/content/sources.mdx +0 -334
- /package/dist/cli/{chunk-tkacnehg.js.map → chunk-3v3ffbrj.js.map} +0 -0
- /package/dist/cli/{chunk-r9rcc4w7.js.map → chunk-8y2kmjv8.js.map} +0 -0
- /package/dist/cli/{chunk-2eytanqx.js.map → chunk-c887fj0s.js.map} +0 -0
- /package/dist/cli/{chunk-akbpwfxc.js.map → chunk-dzwehpy7.js.map} +0 -0
- /package/dist/cli/{chunk-2z928egk.js.map → chunk-ft9vfys6.js.map} +0 -0
- /package/dist/cli/{chunk-a58773jm.js.map → chunk-kma9f6sf.js.map} +0 -0
- /package/dist/cli/{chunk-crgn1q09.js.map → chunk-sds923fa.js.map} +0 -0
- /package/dist/cli/{chunk-acanzt5p.js.map → chunk-tdqhyxfz.js.map} +0 -0
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../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 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 { EN_UI, resolveUIStrings } from \"../core/i18n-ui.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 { directiveSource } from \"../markdown/directives.ts\";\nimport type { DirectiveNode } from \"../markdown/directives.ts\";\nimport {\n MARKDOWN_BODY_FEATURES,\n MDX_BODY_FEATURES,\n} 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]);\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 — as the renderer reads it.\nconst MD_PARSE = { features: MARKDOWN_BODY_FEATURES };\nconst MDX_PARSE = { features: MDX_BODY_FEATURES };\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 assistant 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/**\n * A text or leaf directive as the page shows it. Blume renders neither (see\n * `markdown/directives.ts`), and prose is full of text that parses as one —\n * `16:9`, `og:image` — so the page shows the source the author wrote, and the\n * index reads the same text rather than dropping the `:name` part.\n */\nconst directiveText = (\n node: Extract<Nodes, { type: \"leafDirective\" | \"textDirective\" }>,\n walk: Walk\n): string =>\n directiveSource(\n // SAFETY: DirectiveNode is a structural subset of Satteri's directive\n // nodes — a `name`, optional attributes and children, and a position.\n node as DirectiveNode,\n node.type === \"textDirective\" ? \":\" : \"::\",\n // The walked tree was parsed from this text, so its offsets index into it.\n walk.downlevel.source\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 case \"textDirective\": {\n out.push(directiveText(node, walk));\n return;\n }\n case \"leafDirective\": {\n out.push(directiveText(node, walk), \" \");\n return;\n }\n case \"heading\": {\n // oxlint-disable-next-line no-use-before-define -- mutual recursion: a heading's children are walked like any others\n collectHeading(node, out, walk);\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 * Fold a heading into the accumulator. Trailing heading markers\n * (`[#custom-id]`, `{#custom-id}`, `[!toc]`, `[toc]`) are anchor metadata,\n * not prose — strip them so they never pollute the index. Only a marker that\n * ends the heading's final plain-text child counts, mirroring the renderer: a\n * heading ending in inline code or an image keeps its bracketed text on the\n * page (so it stays searchable), and a heading that is nothing but markers\n * renders them literally.\n */\nconst collectHeading = (\n node: Extract<Nodes, { type: \"heading\" }>,\n out: string[],\n walk: Walk\n): void => {\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};\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 assistant 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 // A fallback route renders the fallback locale's page at another locale's\n // URL, so indexing it would only repeat that page as a second hit — the\n // reason site search leaves it out too.\n if (route.fallback) {\n return false;\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 // A page in no sidebar group falls under the \"Docs\" section, in its own\n // language: the label is a UI string, resolved once per locale.\n const { i18n } = project.config;\n const docsSections = new Map<string, string>();\n const docsSection = (locale: string): string => {\n let label = docsSections.get(locale);\n if (label === undefined) {\n label = (\n i18n\n ? resolveUIStrings(locale, {\n defaultLocale: i18n.defaultLocale,\n overrides: i18n.ui,\n })\n : EN_UI\n ).search.docs;\n docsSections.set(locale, label);\n }\n return label;\n };\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 || docsSection(route.locale),\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",
|
|
6
|
-
"/**\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",
|
|
7
|
-
"/**\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",
|
|
8
|
-
"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",
|
|
9
|
-
"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 // Fallback routes render the fallback locale's page at another locale's\n // URL: listing them would pass that page off as a translation, once per\n // locale. `get_page` still resolves them through `pages`, as llms.txt and\n // search leave them out.\n for (const route of manifest.routes) {\n if (route.hidden || route.fallback) {\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.options.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.agents.mcp.instructions,\n name: config.agents.mcp.name ?? config.title,\n navigation: graph.navigation,\n pages,\n routes,\n site: config.deployment.options.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"
|
|
10
|
-
],
|
|
11
|
-
"mappings": "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",
|
|
12
|
-
"debugId": "447C9153F372D54864756E2164756E21",
|
|
13
|
-
"names": []
|
|
14
|
-
}
|
package/docs/content/sources.mdx
DELETED
|
@@ -1,334 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Content sources
|
|
3
|
-
description: Pull docs from local files, a remote repository, a CMS, or any custom backend — and mix several sources into one static-first site read at build time.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
By default Blume reads a folder of `.md`/`.mdx` files. **Content sources** let you pull pages from somewhere else — a remote repository, a CMS, or any custom backend — and mix several sources into a single site. Sources are read at build time; Blume stays static-first.
|
|
7
|
-
|
|
8
|
-
## The default
|
|
9
|
-
|
|
10
|
-
With no configuration, Blume scans your content root (`docs` by default) as one filesystem source. The top-level `content.root`, `content.include`, and `content.exclude` options are shorthand for that single source — nothing to import, nothing to change.
|
|
11
|
-
|
|
12
|
-
```ts blume.config.ts
|
|
13
|
-
import { defineConfig } from "blume";
|
|
14
|
-
|
|
15
|
-
export default defineConfig({
|
|
16
|
-
content: { root: "docs" },
|
|
17
|
-
});
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
## Adapters
|
|
21
|
-
|
|
22
|
-
Each entry in `content.sources` is an **adapter**: a factory imported from `blume/sources` that returns a plain descriptor Blume reads at build time. Add a `sources` array to compose sources. When `sources` is present it replaces the implicit default, so include a `filesystem()` entry for your local docs — and move `root`, `include`, or `exclude` into it. The shorthand and `sources` can't be combined; Blume reports which field to move.
|
|
23
|
-
|
|
24
|
-
```ts blume.config.ts
|
|
25
|
-
import { defineConfig } from "blume";
|
|
26
|
-
import { filesystem, mdxRemote } from "blume/sources";
|
|
27
|
-
|
|
28
|
-
export default defineConfig({
|
|
29
|
-
content: {
|
|
30
|
-
sources: [
|
|
31
|
-
// Local docs at the site root
|
|
32
|
-
filesystem({ root: "docs" }),
|
|
33
|
-
|
|
34
|
-
// Remote MDX from a GitHub repo, mounted under /sdk
|
|
35
|
-
mdxRemote({
|
|
36
|
-
prefix: "sdk",
|
|
37
|
-
github: { owner: "acme", repo: "sdk", ref: "main", path: "docs" },
|
|
38
|
-
}),
|
|
39
|
-
],
|
|
40
|
-
},
|
|
41
|
-
});
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
Each built-in adapter is a factory exported from `blume/sources`, covered in its own section below, and [`custom()`](#custom-sources) plugs in any other backend. Every adapter that takes an options object accepts two shared options:
|
|
45
|
-
|
|
46
|
-
- **`prefix`** namespaces the source's routes under `/<prefix>/…`, which also becomes the source's name in diagnostics and its cache directory. If two sources resolve to the same route, Blume reports a `BLUME_DUPLICATE_ROUTE` build error — give each source a distinct `prefix`.
|
|
47
|
-
- **`pollInterval`** (seconds) makes a remote source re-fetch on that interval in dev, reloading only when the content actually changes. Leave it unset to fetch once and freeze for the session. Local sources (`filesystem()`, `obsidian()`) watch the filesystem instead and ignore it.
|
|
48
|
-
|
|
49
|
-
`custom(source)` is the exception: it takes a `ContentSource` instance rather than an options object, so there is nothing to pass `prefix` or `pollInterval` to. The source sets its own `prefix` property, and re-fetching is whatever its `watch` method implements.
|
|
50
|
-
|
|
51
|
-
An adapter's descriptor also declares the SDK it needs and the environment variables it reads, so the generated project declares that package, `blume dev` and `blume build` warn when a variable is unset, and `blume doctor` lists the configured sources. Options are validated when the config loads: a missing required option, an unknown key, or a leftover 1.x `{ type: "…" }` object fails with a message naming the fix.
|
|
52
|
-
|
|
53
|
-
A single `filesystem()` source roots the generated docs collection at its own directory. Several filesystem sources must share one root and partition it with `include` globs — a second source rooted elsewhere is reported as `BLUME_ENTRY_ID_MISMATCH` so its pages can't silently 404.
|
|
54
|
-
|
|
55
|
-
## Obsidian
|
|
56
|
-
|
|
57
|
-
The built-in `obsidian()` adapter reads an [Obsidian](https://obsidian.md) vault in place. There is no export step and nothing generated into your repo: the vault stays the source of truth, and Blume lowers Obsidian's dialect to Markdown as it loads.
|
|
58
|
-
|
|
59
|
-
```ts blume.config.ts
|
|
60
|
-
import { defineConfig } from "blume";
|
|
61
|
-
import { filesystem, obsidian } from "blume/sources";
|
|
62
|
-
|
|
63
|
-
export default defineConfig({
|
|
64
|
-
content: {
|
|
65
|
-
sources: [
|
|
66
|
-
filesystem({ root: "docs" }),
|
|
67
|
-
obsidian({
|
|
68
|
-
prefix: "notes",
|
|
69
|
-
vault: "vault",
|
|
70
|
-
// Vault folder names to skip at any depth, on top of dot-folders
|
|
71
|
-
exclude: ["Templates", "Daily"],
|
|
72
|
-
}),
|
|
73
|
-
],
|
|
74
|
-
},
|
|
75
|
-
});
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
`[[Wikilinks]]` become route links, addressed by note name across the whole vault rather than by path, the way Obsidian addresses notes. Custom link text (`[[Note|label]]`), heading anchors (`[[Note#Install]]`), full paths (`[[folder/Note]]` and `[[folder/Note.md]]`), the partial paths Obsidian's default "shortest path when possible" setting writes (`[[guides/Note]]`), and the `[[Note\|label]]` form Obsidian writes inside a table cell all work, and a note that sets `slug` in its frontmatter is linked at the route that slug publishes. When two notes share a name, a note whose full vault path is exactly that name wins — Obsidian resolves a link as a path before a name — then the first in vault order (folders before notes, case-insensitively, like Obsidian's file explorer). Blume warns only when a wikilink actually resolves through such a collision; write a longer path to disambiguate. A block reference (`[[Note#^id]]`) links to its note without an anchor: blocks render with no id to land on. A heading anchor resolves against the target note's real headings, matched the way Obsidian's autocomplete writes them (with `**bold**`, `` `code` ``, and link syntax stripped) and slugged by the same `extractHeadings` pass that fills the page manifest — so a link to `#Install` lands on the heading rather than on an id no page emits. `[[#Install]]` addresses a heading in the note you are writing. A link to a heading that doesn't exist keeps the page link, drops the anchor, and warns.
|
|
79
|
-
|
|
80
|
-
Frontmatter keeps what Blume's [page schema](/docs/content/frontmatter) accepts plus any key you declare in [`frontmatter.extend`](/docs/content/frontmatter#custom-keys) (or, for notes of that `type`, a content type's `frontmatter`); every other Obsidian property — Dataview fields, Templater dates, `publish`, and Obsidian's own `tags`, `aliases`, and `cssclasses` — is dropped when a note is lowered, so a vault written with the Properties UI builds without frontmatter errors. `aliases` is dropped rather than resolved — alias link targets are not supported yet. A relative Markdown image beside a note (``) is served from the vault, and when the vault lives inside your git repository, vault pages get git-derived ["Last updated" dates](/docs/configuration#last-modified) like any other page. "Edit this page" links resolve through `github.dir`, so a vault that sits beside the docs app in a monorepo still links to its file; a vault outside the repository gets no link.
|
|
81
|
-
|
|
82
|
-
Locale directories and version snapshots inside the vault are read the same way the filesystem source reads them: `fr/Note.md` publishes under `/fr/` with [i18n](/docs/content/i18n) configured, `v1.0/Note.md` under `/v1.0/` with [versions](/docs/content/versioning), and wikilinks to those notes point at the route each one publishes.
|
|
83
|
-
|
|
84
|
-
A link to an `index` note lands on its folder's route rather than a phantom `/index`. **An unresolved wikilink degrades to plain text with a build warning instead of failing the build**, so a vault mid-refactor still publishes. Single-line `%%comments%%` are stripped, a wikilink inside an HTML comment (`<!-- [[Draft]] -->`) is left alone since Obsidian hides it too, and a note with no `title` in its frontmatter is titled by its filename — the same rule Obsidian itself applies. An `index` note is the one exception: it names a route rather than a note, so its title falls through to Blume's usual derivation (first heading, then the humanized segment). Fenced, indented, and inline code passes through verbatim, so a note documenting the syntax survives.
|
|
85
|
-
|
|
86
|
-
Dot-folders are skipped, including Obsidian's own `.obsidian` config directory and `.trash` — which the dev watcher also ignores, so moving a pane in the app or deleting a note to the trash doesn't rebuild your site. Editing a note does. The directories no content scan reads (`node_modules`, `dist`, `.git`, …) are skipped too, so a vault rooted at the project itself doesn't publish dependency READMEs. A note with `#` or `?` in its path is left out with an error, the way a [content file](/docs/content#files-and-routes) is: Astro can't load its copy, so rename it. Symlinks inside the vault are followed, the way the filesystem source follows them, so a shared folder linked into the vault publishes with it. A vault that sits inside the filesystem source's root must be excluded from it (`filesystem({ root: "docs", exclude: ["**/_*", "**/.*", "vault/**"] })` — an `exclude` replaces the default `["**/_*", "**/.*"]` rather than adding to it, so keep those two to leave `_`-prefixed partials and dot-files unpublished); [`blume version <id>`](/docs/cli/version) then leaves it out of the snapshot, since the vault keeps publishing its own notes as current.
|
|
87
|
-
|
|
88
|
-
Not yet lowered: callouts (`> [!note]`) render as plain blockquotes, embeds (`![[image.png]]`) pass through untouched, multi-line `%%comments%%` are left in place, and there is no backlink graph.
|
|
89
|
-
|
|
90
|
-
## Remote MDX
|
|
91
|
-
|
|
92
|
-
The built-in `mdxRemote()` adapter fetches raw `.md`/`.mdx` over HTTP. Enumerate files either from a GitHub repo subtree (`github`) or explicitly against a raw base URL (`url` + `files`):
|
|
93
|
-
|
|
94
|
-
```ts blume.config.ts
|
|
95
|
-
mdxRemote({
|
|
96
|
-
prefix: "sdk",
|
|
97
|
-
url: "https://raw.githubusercontent.com/acme/sdk/main/docs",
|
|
98
|
-
files: ["intro.mdx", "guide.mdx"],
|
|
99
|
-
});
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
A private repo's token is read from the `GITHUB_TOKEN` environment variable — the adapter declares it, so `blume dev` and `blume build` warn when it's unset. It is never inlined into your config or generated output, and it is only ever sent to GitHub's own hosts (`api.github.com`, `raw.githubusercontent.com`), never to a custom `url` base. A public repo works without it.
|
|
103
|
-
|
|
104
|
-
Remote pages are rendered with full MDX-plus-component fidelity: their bodies are materialized into a hidden staging directory and rendered through Astro alongside your local docs, so callouts, tabs, and every other Blume component keep working.
|
|
105
|
-
|
|
106
|
-
### Caching and offline builds
|
|
107
|
-
|
|
108
|
-
Each remote source keeps a snapshot under `.blume/cache/<source>/`. If a fetch fails — a network blip or a CMS outage — Blume serves the last-known-good snapshot with a warning rather than failing the build. Preview and published content get separate snapshots, and so does each set of source options, so a build never falls back to drafts fetched under `--preview`, and editing a source's `query` or `fields` fetches afresh. The cache lives inside `.blume/` and is regenerated, never committed.
|
|
109
|
-
|
|
110
|
-
In dev, a remote source is served from its snapshot when it has one, so restarting the dev server doesn't refetch it. Run `blume sync` to pull the latest content (a running dev server hot-reloads), or `blume sync --force` to drop the snapshots first. Local filesystem sources hot-reload as usual. To poll a remote source for changes instead, set the shared `pollInterval` option (seconds) on it — the dev server re-fetches on that interval and reloads only when the content actually changes. Leave it unset to avoid hitting the API while you work.
|
|
111
|
-
|
|
112
|
-
## GitHub Releases
|
|
113
|
-
|
|
114
|
-
The built-in `githubReleases()` adapter turns a repo's releases into a changelog: each release becomes a `type: changelog` entry, so your release notes _are_ your changelog — nothing to write twice. Combined with the generated [changelog timeline](/docs/advanced/changelog), publishing a GitHub release ships a changelog entry.
|
|
115
|
-
|
|
116
|
-
```ts blume.config.ts
|
|
117
|
-
import { defineConfig } from "blume";
|
|
118
|
-
import { filesystem, githubReleases } from "blume/sources";
|
|
119
|
-
|
|
120
|
-
export default defineConfig({
|
|
121
|
-
content: {
|
|
122
|
-
sources: [
|
|
123
|
-
filesystem({ root: "content" }),
|
|
124
|
-
githubReleases({
|
|
125
|
-
prefix: "changelog",
|
|
126
|
-
owner: "acme",
|
|
127
|
-
repo: "sdk",
|
|
128
|
-
// prereleases: false, // include prereleases (default off)
|
|
129
|
-
// drafts: false, // include drafts (needs a write token)
|
|
130
|
-
// limit: 100, // cap releases, newest-first
|
|
131
|
-
}),
|
|
132
|
-
],
|
|
133
|
-
},
|
|
134
|
-
});
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
Each release maps to the changelog fields automatically: its name (or tag) becomes the title, its published date drives the timeline order, the tag becomes `changelog.version`, and prereleases are tagged `Prerelease` (others `Release`). The notes render as the entry body, with two changes to their links: a link that isn't a web, mail, phone, or relative address (a `javascript:` URL, say) keeps only its label, and a link back to your own [`deployment.site`](/docs/deployment) is rewritten to its root-relative path, so it follows preview deploys and your deployment base. Give the source a `prefix` so its release pages nest under a route like `/changelog/v1-2-0`.
|
|
138
|
-
|
|
139
|
-
A private repo authenticates with the `GITHUB_TOKEN` environment variable — the same token the other GitHub features use, never inlined into your config; the adapter declares it, so a build without it warns. Like every remote source it's cached under `.blume/cache/<source>/` and served offline if the API is unreachable. Because a changelog is supplementary, a fetch failure with no cache (say a CI build without a token) degrades to an empty changelog with a warning rather than failing the build — set `GITHUB_TOKEN` in your CI and deploy environments to populate it.
|
|
140
|
-
|
|
141
|
-
## Sanity
|
|
142
|
-
|
|
143
|
-
The built-in `sanity()` adapter runs a GROQ query and maps each document's fields to frontmatter and its Portable Text body to Markdown. The adapter declares `@sanity/client` as its runtime dependency — an optional peer, so install it only if you use this source.
|
|
144
|
-
|
|
145
|
-
```ts blume.config.ts
|
|
146
|
-
import { defineConfig } from "blume";
|
|
147
|
-
import { filesystem, sanity } from "blume/sources";
|
|
148
|
-
|
|
149
|
-
export default defineConfig({
|
|
150
|
-
content: {
|
|
151
|
-
sources: [
|
|
152
|
-
filesystem({ root: "docs" }),
|
|
153
|
-
sanity({
|
|
154
|
-
prefix: "guides",
|
|
155
|
-
projectId: "abc123",
|
|
156
|
-
dataset: "production",
|
|
157
|
-
query: `*[_type == "guide"]`,
|
|
158
|
-
// Field paths default to title / slug.current / body / _updatedAt
|
|
159
|
-
fields: { slug: "slug.current", body: "content" },
|
|
160
|
-
}),
|
|
161
|
-
],
|
|
162
|
-
},
|
|
163
|
-
});
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
A read token for a private dataset comes from the `SANITY_TOKEN` environment variable, which the adapter declares. Custom Portable Text block types map to Blume components through the engine's `serializers` option, available when you construct `sanitySource` directly and pass it to [`custom()`](#custom-sources). Setting `serializers` writes the source's pages as MDX, so a component or directive a serializer returns renders.
|
|
167
|
-
|
|
168
|
-
## Notion
|
|
169
|
-
|
|
170
|
-
The built-in `notion()` adapter turns a Notion database into a collection: each row becomes a page, its properties become frontmatter, and its block tree becomes MDX. Callouts, toggles, columns, and code blocks map to the matching Blume components, and the text you type in Notion renders as written: a `{`, `<`, or Markdown character in a page is escaped rather than read as MDX, JSX, or formatting. Video blocks become a `<YouTube>` embed when they hold a YouTube link and a `<video>` player otherwise, with the block's caption as a `<Frame>` caption either way. A link to a video page rather than a media file (a Vimeo or Loom URL, say) is reported as a build warning, and its `<video>` player keeps pointing at the page, which it can't play — link to that video from the text instead. The adapter declares `@notionhq/client` (v5 or later) as its runtime dependency — an optional peer; Blume reads the database through its first data source.
|
|
171
|
-
|
|
172
|
-
```ts blume.config.ts
|
|
173
|
-
import { defineConfig } from "blume";
|
|
174
|
-
import { filesystem, notion } from "blume/sources";
|
|
175
|
-
|
|
176
|
-
export default defineConfig({
|
|
177
|
-
content: {
|
|
178
|
-
sources: [
|
|
179
|
-
filesystem({ root: "docs" }),
|
|
180
|
-
notion({
|
|
181
|
-
prefix: "handbook",
|
|
182
|
-
database: "8f2c1e0a4b7d4f3c9e6a5d2b1c0f9e8d", // the id in the database URL
|
|
183
|
-
// Property names default to the title-typed prop / Description / Slug / Order / Status
|
|
184
|
-
// Pages whose Status isn't publishedValue (default "Published") import as drafts
|
|
185
|
-
publishedValue: "Done",
|
|
186
|
-
}),
|
|
187
|
-
],
|
|
188
|
-
},
|
|
189
|
-
});
|
|
190
|
-
```
|
|
191
|
-
|
|
192
|
-
The integration token comes from the `NOTION_TOKEN` environment variable (share the database with your integration); the adapter declares it, so a build without it warns.
|
|
193
|
-
|
|
194
|
-
The `Status` property is a publish gate by default. A page whose Status (a status or select property) holds any value other than `publishedValue`, which defaults to `Published`, imports with `draft: true`, and production builds drop drafts. A page with no Status value is published, and a database without the property publishes every page. Notion's default status options are Not started, In progress, and Done, so a database that uses them has no `Published` value and publishes nothing until you set `publishedValue: "Done"` (or whichever option means published). `properties.status` names a differently named property; to import every page whatever its status, point it at a property the database doesn't have.
|
|
195
|
-
|
|
196
|
-
**Notion image and video URLs are signed and expire**, so the adapter downloads them at build time into the site's assets and rewrites the references — a CMS asset never rots a static build. Only a file the server reports as an image or video is saved (or, when the response doesn't say, one whose URL names an image or video extension); anything else keeps its original URL with a build warning, so nothing but media is ever served from your site's origin. API calls are paced through a small request pool (3 at a time, matching Notion's per-integration rate limit) so databases with hundreds of pages import without tripping `429` responses; set `concurrency` on the source to tune it.
|
|
197
|
-
|
|
198
|
-
## Contentful
|
|
199
|
-
|
|
200
|
-
The built-in `contentful()` adapter reads the entries of one content type through the Content Delivery API and lowers each entry's rich text body to Markdown: headings, marks, links, lists, quotes, tables, and embedded assets map to their Markdown equivalents, and a body held in a Markdown long-text field passes through as written, except that a link that isn't a web, mail, phone, or relative address (a `javascript:` URL, say) keeps only its label, as it does in rich text. Nothing to install — the adapter speaks the REST API directly.
|
|
201
|
-
|
|
202
|
-
```ts blume.config.ts
|
|
203
|
-
import { defineConfig } from "blume";
|
|
204
|
-
import { contentful, filesystem } from "blume/sources";
|
|
205
|
-
|
|
206
|
-
export default defineConfig({
|
|
207
|
-
content: {
|
|
208
|
-
sources: [
|
|
209
|
-
filesystem({ root: "docs" }),
|
|
210
|
-
contentful({
|
|
211
|
-
prefix: "guides",
|
|
212
|
-
space: "abc123",
|
|
213
|
-
contentType: "guide",
|
|
214
|
-
// environment: "master", locale: "en-US"
|
|
215
|
-
// Field ids default to title / description / slug / body, and the
|
|
216
|
-
// date to sys.updatedAt
|
|
217
|
-
fields: { body: "content" },
|
|
218
|
-
// Extra Delivery API query parameters
|
|
219
|
-
params: { "fields.section": "sdk" },
|
|
220
|
-
}),
|
|
221
|
-
],
|
|
222
|
-
},
|
|
223
|
-
});
|
|
224
|
-
```
|
|
225
|
-
|
|
226
|
-
The Delivery API token comes from the `CONTENTFUL_ACCESS_TOKEN` environment variable, which the adapter declares. Under `--preview` the adapter reads drafts through the Preview API with `CONTENTFUL_PREVIEW_TOKEN`. The Preview API rejects delivery tokens, so `--preview` without a preview token fails with a clear error rather than falling back to `CONTENTFUL_ACCESS_TOKEN`. Assets are referenced from Contentful's CDN rather than downloaded — their URLs are stable. An embedded entry maps to a Blume component through the engine's `serializers` option, keyed by content type id, when you construct `contentfulSource` directly and pass it to [`custom()`](#custom-sources); setting `serializers` writes the source's pages as MDX so the returned components render, and an embedded entry without a serializer is noted in a comment. A link to another entry renders as plain text, since there is no route to point at.
|
|
227
|
-
|
|
228
|
-
## Payload
|
|
229
|
-
|
|
230
|
-
The built-in `payload()` adapter reads a collection through the Payload REST API (`/api/<collection>`) and lowers each document's Lexical body to Markdown: paragraphs, headings, bullet, numbered, and check lists, quotes, links, uploads, and horizontal rules. A body held in a text field passes through as Markdown, except that a link that isn't a web, mail, phone, or relative address keeps only its label. Nothing to install.
|
|
231
|
-
|
|
232
|
-
```ts blume.config.ts
|
|
233
|
-
import { defineConfig } from "blume";
|
|
234
|
-
import { filesystem, payload } from "blume/sources";
|
|
235
|
-
|
|
236
|
-
export default defineConfig({
|
|
237
|
-
content: {
|
|
238
|
-
sources: [
|
|
239
|
-
filesystem({ root: "docs" }),
|
|
240
|
-
payload({
|
|
241
|
-
prefix: "handbook",
|
|
242
|
-
url: "https://cms.example.com",
|
|
243
|
-
collection: "docs",
|
|
244
|
-
// Field paths default to title / description / slug / content / updatedAt
|
|
245
|
-
fields: { body: "richText" },
|
|
246
|
-
// Extra query parameters: where[...], sort
|
|
247
|
-
params: { sort: "title" },
|
|
248
|
-
}),
|
|
249
|
-
],
|
|
250
|
-
},
|
|
251
|
-
});
|
|
252
|
-
```
|
|
253
|
-
|
|
254
|
-
The API key comes from the `PAYLOAD_API_KEY` environment variable and is sent as `users API-Key <key>`; set `authCollection` when the key belongs to another auth-enabled collection. Only published documents are imported — `--preview` requests drafts and stages them with `draft: true`. Documents are fetched with `depth: 1` so uploads carry their URLs, and a relative upload path (`/media/x.png`) resolves against `url`. A `block` or `inlineBlock` node maps to a Blume component through the engine's `serializers` option, keyed by `blockType`, when you construct `payloadSource` directly and pass it to [`custom()`](#custom-sources). Setting `serializers` writes the source's pages as MDX so the returned components render; a body held in a Markdown text field stays Markdown.
|
|
255
|
-
|
|
256
|
-
## Strapi
|
|
257
|
-
|
|
258
|
-
The built-in `strapi()` adapter reads a content type through the Strapi REST API (`/api/<pluralApiId>`) and lowers each entry's Blocks body to Markdown: paragraphs, headings, lists, quotes, code blocks, images, and links. A Markdown rich text field passes through as written, except that a link that isn't a web, mail, phone, or relative address keeps only its label. Strapi 5 responses are read as-is, and the Strapi 4 `attributes` envelope is flattened so the same field paths apply. Nothing to install.
|
|
259
|
-
|
|
260
|
-
```ts blume.config.ts
|
|
261
|
-
import { defineConfig } from "blume";
|
|
262
|
-
import { filesystem, strapi } from "blume/sources";
|
|
263
|
-
|
|
264
|
-
export default defineConfig({
|
|
265
|
-
content: {
|
|
266
|
-
sources: [
|
|
267
|
-
filesystem({ root: "docs" }),
|
|
268
|
-
strapi({
|
|
269
|
-
prefix: "guides",
|
|
270
|
-
url: "https://cms.example.com",
|
|
271
|
-
contentType: "guides",
|
|
272
|
-
// locale: "en"
|
|
273
|
-
// Field paths default to title / description / slug / content / updatedAt
|
|
274
|
-
fields: { body: "body" },
|
|
275
|
-
// Extra query parameters: filters[...], sort
|
|
276
|
-
params: { "filters[section][$eq]": "sdk" },
|
|
277
|
-
}),
|
|
278
|
-
],
|
|
279
|
-
},
|
|
280
|
-
});
|
|
281
|
-
```
|
|
282
|
-
|
|
283
|
-
The API token comes from the `STRAPI_API_TOKEN` environment variable, sent as a bearer token. Entries are fetched with `populate=*` so images carry their URLs (set `populate` to narrow it), and a relative upload path (`/uploads/x.png`) resolves against `url`. Only published entries are imported — `--preview` requests drafts (`status=draft` on Strapi 5, `publicationState=preview` on Strapi 4) and stages each document that has never been published with `draft: true`; a published document previews its latest draft.
|
|
284
|
-
|
|
285
|
-
## Preview and sync
|
|
286
|
-
|
|
287
|
-
Two flags control how remote content is fetched and what's included:
|
|
288
|
-
|
|
289
|
-
- **`--preview`** on `blume dev` or `blume build` renders drafts and pulls unpublished CMS content — Sanity switches to its `previewDrafts` perspective, Notion stops filtering by `Status`, Contentful reads through the Preview API, and Payload and Strapi request drafts. Production builds without the flag exclude drafts as usual, so a preview build is a safe way to review unpublished work before it ships.
|
|
290
|
-
- **`blume sync`** re-fetches every remote source and regenerates the runtime. Dev is cache-first — a remote source is fetched once and served from `.blume/cache` on restart (fast and offline-tolerant), so `blume sync` is how you pull the latest CMS content without restarting the dev server (a running server hot-reloads). Add `--force` to drop the cache first, or set `pollInterval` on a source to refresh automatically.
|
|
291
|
-
|
|
292
|
-
```sh
|
|
293
|
-
blume dev --preview # author workflow: see drafts live
|
|
294
|
-
blume build --preview # render a full preview build
|
|
295
|
-
blume sync # refresh remote content now
|
|
296
|
-
blume sync --force # ...ignoring any cached snapshot
|
|
297
|
-
```
|
|
298
|
-
|
|
299
|
-
## Custom sources
|
|
300
|
-
|
|
301
|
-
Any object implementing the `ContentSource` interface can be passed to `custom()`, which is how an adapter with custom serializers — or any backend not built in — plugs in without its SDK touching the core install:
|
|
302
|
-
|
|
303
|
-
```ts blume.config.ts
|
|
304
|
-
import { defineConfig } from "blume";
|
|
305
|
-
import { custom, filesystem } from "blume/sources";
|
|
306
|
-
import { sanitySource } from "blume/sources/sanity.ts";
|
|
307
|
-
|
|
308
|
-
export default defineConfig({
|
|
309
|
-
content: {
|
|
310
|
-
sources: [
|
|
311
|
-
filesystem({ root: "docs" }),
|
|
312
|
-
custom(
|
|
313
|
-
sanitySource({
|
|
314
|
-
name: "guides",
|
|
315
|
-
prefix: "guides",
|
|
316
|
-
projectId: "abc123",
|
|
317
|
-
dataset: "production",
|
|
318
|
-
query: `*[_type == "guide"]`,
|
|
319
|
-
// Map custom Portable Text blocks to Blume components
|
|
320
|
-
serializers: {
|
|
321
|
-
callout: (block) => `<Callout>${block.text}</Callout>`,
|
|
322
|
-
},
|
|
323
|
-
})
|
|
324
|
-
),
|
|
325
|
-
],
|
|
326
|
-
},
|
|
327
|
-
});
|
|
328
|
-
```
|
|
329
|
-
|
|
330
|
-
A source built with one of Blume's engine factories, like `sanitySource` above, is rebuilt on the running command's context, so it reads drafts under `--preview` and keeps its snapshot in `.blume/cache` the way the built-in adapter does. A source of your own can do the same by implementing `withContext(ctx)` and returning itself rebuilt on that context.
|
|
331
|
-
|
|
332
|
-
A source normalizes its native shape (Portable Text, Notion blocks, remote HTML) to Markdown/MDX text, so the same components and markdown features apply no matter where a page comes from. The built-in adapters escape rich text as they lower it, so what an author typed in the CMS — a `{`, a `<b>`, a paragraph starting with `import`, or `©` — renders as written. Links keep only `http(s)`, `mailto:`, `tel:`, and relative targets; any other scheme (`javascript:`, `data:`) renders as the link's text, and SVG images a source downloads are served sandboxed. Release notes from `githubReleases()` and files from `mdxRemote()` are treated as your own content: their raw HTML renders as written, so point them only at repositories you trust. Unlike the built-in adapters, `custom()` carries a live instance rather than plain data, so it declares no runtime dependency or secret of its own — the instance manages those itself.
|
|
333
|
-
|
|
334
|
-
A custom source that reads local files should set `sourcePath` on each entry and `contentRoot` on the source itself. `sourcePath` names the file in diagnostics and resolves relative images beside it; `contentRoot` bounds the git `log` that dates pages, so without it the source's pages get no git-derived ["Last updated" date](/docs/configuration#last-modified).
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|