blume 2.0.3 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (455) hide show
  1. package/AGENTS.md +1 -0
  2. package/CHANGELOG.md +48 -0
  3. package/README.md +1 -1
  4. package/dist/cli/{chunk-1d7ve1dm.js → chunk-0gz36akr.js} +18 -2
  5. package/dist/cli/{chunk-1d7ve1dm.js.map → chunk-0gz36akr.js.map} +3 -3
  6. package/dist/cli/{chunk-2eytanqx.js → chunk-1n228xqb.js} +2 -2
  7. package/dist/cli/{chunk-yfyb25rh.js → chunk-2f3ndkpw.js} +24 -8
  8. package/dist/cli/chunk-2f3ndkpw.js.map +14 -0
  9. package/dist/cli/{chunk-r20tn01b.js → chunk-2f7n4qzt.js} +8 -2
  10. package/dist/cli/{chunk-r20tn01b.js.map → chunk-2f7n4qzt.js.map} +3 -3
  11. package/dist/cli/{chunk-35d4wj9f.js → chunk-2n4ps1b3.js} +7 -7
  12. package/dist/cli/{chunk-j85vccga.js → chunk-43zcf4gz.js} +97 -57
  13. package/dist/cli/{chunk-j85vccga.js.map → chunk-43zcf4gz.js.map} +6 -6
  14. package/dist/cli/{chunk-e04dxsz1.js → chunk-4ejxbzee.js} +1 -1
  15. package/dist/cli/{chunk-5m5nmvyq.js → chunk-4h8z9vv5.js} +233 -39
  16. package/dist/cli/chunk-4h8z9vv5.js.map +16 -0
  17. package/dist/cli/{chunk-5f86nr5m.js → chunk-5cg2rzgf.js} +18 -16
  18. package/dist/cli/{chunk-5f86nr5m.js.map → chunk-5cg2rzgf.js.map} +2 -2
  19. package/dist/cli/{chunk-tzmab476.js → chunk-5ffywsmb.js} +4 -4
  20. package/dist/cli/{chunk-2z928egk.js → chunk-5wj2qwn2.js} +5 -5
  21. package/dist/cli/chunk-6bm0xpgd.js +18 -0
  22. package/dist/cli/chunk-6bm0xpgd.js.map +10 -0
  23. package/dist/cli/{chunk-vg9r4eb9.js → chunk-713mngdq.js} +12 -5
  24. package/dist/cli/{chunk-vg9r4eb9.js.map → chunk-713mngdq.js.map} +3 -3
  25. package/dist/cli/{chunk-5xvm6tfj.js → chunk-9geymqns.js} +17 -15
  26. package/dist/cli/{chunk-5xvm6tfj.js.map → chunk-9geymqns.js.map} +2 -2
  27. package/dist/cli/{chunk-q58y5e6a.js → chunk-9xr4dgee.js} +26 -17
  28. package/dist/cli/chunk-9xr4dgee.js.map +10 -0
  29. package/dist/cli/{chunk-b07cmahc.js → chunk-amrb6hgh.js} +2 -2
  30. package/dist/cli/chunk-cm89scdv.js +443 -0
  31. package/dist/cli/chunk-cm89scdv.js.map +11 -0
  32. package/dist/cli/{chunk-akbpwfxc.js → chunk-cmtcnh9f.js} +2 -2
  33. package/dist/cli/chunk-cy5bm0j0.js +104 -0
  34. package/dist/cli/chunk-cy5bm0j0.js.map +11 -0
  35. package/dist/cli/{chunk-crgn1q09.js → chunk-e7vvk5ya.js} +4 -4
  36. package/dist/cli/{chunk-pehfxfta.js → chunk-g2z9x01v.js} +6 -98
  37. package/dist/cli/chunk-g2z9x01v.js.map +10 -0
  38. package/dist/cli/{chunk-tkacnehg.js → chunk-gp84za0h.js} +2 -2
  39. package/dist/cli/{chunk-wrr3j9w9.js → chunk-h2a48yhd.js} +186 -63
  40. package/dist/cli/chunk-h2a48yhd.js.map +36 -0
  41. package/dist/cli/{chunk-hm3vjy5s.js → chunk-jnwj5kjj.js} +14 -9
  42. package/dist/cli/{chunk-hm3vjy5s.js.map → chunk-jnwj5kjj.js.map} +4 -4
  43. package/dist/cli/chunk-kwrg63tm.js +31 -0
  44. package/dist/cli/chunk-kwrg63tm.js.map +10 -0
  45. package/dist/cli/{chunk-2hsdwb9n.js → chunk-nd1821sk.js} +18 -16
  46. package/dist/cli/{chunk-2hsdwb9n.js.map → chunk-nd1821sk.js.map} +2 -2
  47. package/dist/cli/{chunk-6dsbexzp.js → chunk-ntv4qrt7.js} +17 -15
  48. package/dist/cli/{chunk-6dsbexzp.js.map → chunk-ntv4qrt7.js.map} +2 -2
  49. package/dist/cli/{chunk-r9rcc4w7.js → chunk-p804f3pe.js} +7 -7
  50. package/dist/cli/{chunk-ey84smr6.js → chunk-py62fewh.js} +6 -6
  51. package/dist/cli/{chunk-ey84smr6.js.map → chunk-py62fewh.js.map} +1 -1
  52. package/dist/cli/chunk-q3wxxq39.js +126 -0
  53. package/dist/cli/chunk-q3wxxq39.js.map +11 -0
  54. package/dist/cli/{chunk-9t7a85s3.js → chunk-rekqz4qg.js} +23 -13
  55. package/dist/cli/chunk-rekqz4qg.js.map +10 -0
  56. package/dist/cli/{chunk-8ktnccpt.js → chunk-ta27feev.js} +1 -1
  57. package/dist/cli/{chunk-pv29h0wf.js → chunk-xkz5xbtv.js} +3476 -822
  58. package/dist/cli/chunk-xkz5xbtv.js.map +213 -0
  59. package/dist/cli/{chunk-a58773jm.js → chunk-y4ykbark.js} +2 -2
  60. package/dist/cli/{chunk-p3v96n38.js → chunk-y5h4r2y0.js} +4 -4
  61. package/dist/cli/{chunk-acanzt5p.js → chunk-yj17vs39.js} +6 -6
  62. package/dist/cli/{chunk-p73c0m7w.js → chunk-yxmk4pec.js} +16 -14
  63. package/dist/cli/{chunk-p73c0m7w.js.map → chunk-yxmk4pec.js.map} +2 -2
  64. package/dist/cli/{chunk-3em5wd2y.js → chunk-zccbjcj0.js} +4 -3
  65. package/dist/cli/{chunk-3em5wd2y.js.map → chunk-zccbjcj0.js.map} +2 -2
  66. package/dist/cli/{chunk-c8chx29p.js → chunk-zf748avv.js} +384 -207
  67. package/dist/cli/chunk-zf748avv.js.map +37 -0
  68. package/dist/cli/{chunk-g4hq16wv.js → chunk-zhvd6adm.js} +16 -14
  69. package/dist/cli/{chunk-g4hq16wv.js.map → chunk-zhvd6adm.js.map} +2 -2
  70. package/dist/cli/index.js +26 -20
  71. package/dist/cli/index.js.map +5 -5
  72. package/dist/types/ai/agent-surface.d.ts +8 -7
  73. package/dist/types/ai/ai-catalog.d.ts +7 -0
  74. package/dist/types/ai/ask-context.d.ts +22 -0
  75. package/dist/types/ai/ask.d.ts +271 -58
  76. package/dist/types/ai/index.d.ts +2 -2
  77. package/dist/types/ai/site-skill.d.ts +26 -0
  78. package/dist/types/ai/skills.d.ts +16 -0
  79. package/dist/types/astro/integration.d.ts +8 -0
  80. package/dist/types/captcha/hcaptcha.d.ts +27 -0
  81. package/dist/types/captcha/index.d.ts +28 -0
  82. package/dist/types/captcha/schema.d.ts +29 -0
  83. package/dist/types/captcha/turnstile.d.ts +28 -0
  84. package/dist/types/components/content/api-field.d.ts +59 -0
  85. package/dist/types/components/content/api-page.d.ts +95 -0
  86. package/dist/types/components/layout/nav-utils.d.ts +26 -0
  87. package/dist/types/components/openapi/helpers.d.ts +137 -0
  88. package/dist/types/components/openapi/security.d.ts +85 -0
  89. package/dist/types/consent/ethyca.d.ts +48 -0
  90. package/dist/types/consent/index.d.ts +37 -0
  91. package/dist/types/consent/native.d.ts +28 -0
  92. package/dist/types/consent/osano.d.ts +42 -0
  93. package/dist/types/consent/schema.d.ts +110 -0
  94. package/dist/types/core/config-input.d.ts +187 -20
  95. package/dist/types/core/data.d.ts +49 -4
  96. package/dist/types/core/footer.d.ts +8 -0
  97. package/dist/types/core/i18n-ui.d.ts +86 -0
  98. package/dist/types/core/includes.d.ts +8 -2
  99. package/dist/types/core/localizable.d.ts +7 -0
  100. package/dist/types/core/page-modes.d.ts +29 -0
  101. package/dist/types/core/redirect-patterns.d.ts +144 -0
  102. package/dist/types/core/related.d.ts +40 -0
  103. package/dist/types/core/schema.d.ts +551 -56
  104. package/dist/types/core/sources/read.d.ts +5 -0
  105. package/dist/types/core/types.d.ts +1 -1
  106. package/dist/types/core/variables.d.ts +42 -0
  107. package/dist/types/deploy/node-headers.d.ts +13 -2
  108. package/dist/types/deploy/platforms/cloudflare.d.ts +5 -0
  109. package/dist/types/deploy/platforms/netlify.d.ts +7 -0
  110. package/dist/types/deploy/platforms/vercel.d.ts +6 -0
  111. package/dist/types/deploy/redirects.d.ts +39 -11
  112. package/dist/types/deploy/vercel-negotiation.d.ts +9 -0
  113. package/dist/types/markdown/code-title.d.ts +17 -0
  114. package/dist/types/markdown/features.d.ts +3 -2
  115. package/dist/types/narration/build.d.ts +70 -0
  116. package/dist/types/narration/provider.d.ts +26 -0
  117. package/dist/types/narration/script.d.ts +154 -0
  118. package/dist/types/openapi/model.d.ts +12 -6
  119. package/dist/types/openapi/overlay.d.ts +54 -0
  120. package/dist/types/openapi/parse.d.ts +13 -0
  121. package/dist/types/openapi/references.d.ts +14 -4
  122. package/dist/types/ratelimit/cloudflare.d.ts +39 -0
  123. package/dist/types/ratelimit/index.d.ts +27 -0
  124. package/dist/types/ratelimit/memory.d.ts +36 -0
  125. package/dist/types/ratelimit/schema.d.ts +111 -0
  126. package/dist/types/ratelimit/upstash.d.ts +21 -0
  127. package/dist/types/ratelimit/wrangler.d.ts +23 -0
  128. package/dist/types/reference/asyncapi.d.ts +10 -10
  129. package/dist/types/reference/graphql.d.ts +13 -10
  130. package/dist/types/reference/openapi.d.ts +33 -12
  131. package/dist/types/reference/options.d.ts +45 -5
  132. package/dist/types/reference/scalar.d.ts +13 -0
  133. package/dist/types/reference/schema.d.ts +60 -36
  134. package/dist/types/search/documents.d.ts +12 -1
  135. package/dist/types/search/orama-index.d.ts +4 -0
  136. package/dist/types/seo/metatags.d.ts +20 -0
  137. package/docs/02-deployment.mdx +41 -5
  138. package/docs/03-upgrading.mdx +4 -4
  139. package/docs/04-migrating.mdx +1 -1
  140. package/docs/advanced/changelog.mdx +2 -2
  141. package/docs/advanced/skills.mdx +14 -0
  142. package/docs/cli/audit.mdx +21 -1
  143. package/docs/cli/index.mdx +1 -0
  144. package/docs/configuration/analytics.mdx +3 -1
  145. package/docs/configuration/assistant.mdx +188 -36
  146. package/docs/configuration/consent.mdx +100 -0
  147. package/docs/configuration/customization.mdx +4 -4
  148. package/docs/configuration/index.mdx +63 -5
  149. package/docs/configuration/meta.ts +3 -0
  150. package/docs/configuration/narration.mdx +119 -0
  151. package/docs/configuration/rate-limiting.mdx +69 -0
  152. package/docs/configuration/search.mdx +29 -1
  153. package/docs/configuration/theming.mdx +3 -1
  154. package/docs/content/components.mdx +106 -0
  155. package/docs/content/frontmatter.mdx +76 -0
  156. package/docs/content/i18n.mdx +38 -3
  157. package/docs/content/includes.mdx +16 -0
  158. package/docs/content/meta.mdx +1 -0
  159. package/docs/content/meta.ts +1 -0
  160. package/docs/content/navigation.mdx +28 -6
  161. package/docs/content/sources/contentful.mdx +32 -0
  162. package/docs/content/sources/custom.mdx +39 -0
  163. package/docs/content/sources/github-releases.mdx +31 -0
  164. package/docs/content/sources/index.mdx +91 -0
  165. package/docs/content/sources/meta.ts +16 -0
  166. package/docs/content/sources/notion.mdx +32 -0
  167. package/docs/content/sources/obsidian.mdx +37 -0
  168. package/docs/content/sources/payload.mdx +30 -0
  169. package/docs/content/sources/remote-mdx.mdx +40 -0
  170. package/docs/content/sources/sanity.mdx +29 -0
  171. package/docs/content/sources/strapi.mdx +31 -0
  172. package/docs/content/syntax.mdx +67 -2
  173. package/docs/content/variables.mdx +41 -0
  174. package/docs/discoverability/agent-discovery.mdx +29 -2
  175. package/docs/discoverability/index.mdx +1 -1
  176. package/docs/discoverability/llms-txt.mdx +1 -1
  177. package/docs/discoverability/metadata.mdx +17 -0
  178. package/docs/references/api-pages.mdx +74 -0
  179. package/docs/references/asyncapi.mdx +1 -1
  180. package/docs/references/graphql.mdx +2 -2
  181. package/docs/references/meta.ts +1 -1
  182. package/docs/references/openapi.mdx +102 -3
  183. package/docs/references/scalar.mdx +3 -1
  184. package/package.json +36 -1
  185. package/skills/blume/SKILL.md +3 -0
  186. package/skills/blume-migrate/SKILL.md +5 -5
  187. package/skills/blume-migrate/references/docusaurus.md +3 -3
  188. package/skills/blume-migrate/references/mintlify.md +37 -19
  189. package/skills/blume-migrate/references/nextra.md +2 -2
  190. package/skills/blume-migrate/references/starlight.md +2 -1
  191. package/skills/blume-migrate/scripts/mintlify-codemod.mjs +60 -14
  192. package/skills/blume-write-skill/SKILL.md +82 -0
  193. package/src/ai/agent-readability.ts +2 -1
  194. package/src/ai/agent-surface.ts +9 -7
  195. package/src/ai/ai-catalog.ts +6 -3
  196. package/src/ai/ask-context.ts +73 -20
  197. package/src/ai/ask-data.ts +6 -0
  198. package/src/ai/ask-tools.ts +142 -0
  199. package/src/ai/ask.ts +354 -76
  200. package/src/ai/component-markdown.ts +75 -0
  201. package/src/ai/cors.ts +2 -0
  202. package/src/ai/index.ts +12 -0
  203. package/src/ai/llms.ts +6 -2
  204. package/src/ai/markdown.ts +2 -1
  205. package/src/ai/mcp/data.ts +6 -0
  206. package/src/ai/openapi-components.ts +9 -3
  207. package/src/ai/site-skill.ts +274 -0
  208. package/src/ai/skills.ts +35 -2
  209. package/src/ai/write-skill.ts +97 -0
  210. package/src/astro/generate.ts +132 -18
  211. package/src/astro/index.ts +1 -0
  212. package/src/astro/integration.ts +35 -0
  213. package/src/astro/module-types.ts +6 -0
  214. package/src/astro/templates.ts +283 -30
  215. package/src/astro/variables.ts +31 -0
  216. package/src/audit/catalog.ts +19 -0
  217. package/src/audit/checks/contrast.ts +183 -0
  218. package/src/audit/checks/i18n.ts +2 -3
  219. package/src/audit/checks/indexability.ts +2 -3
  220. package/src/audit/checks/links.ts +5 -12
  221. package/src/audit/checks/sitemap.ts +2 -3
  222. package/src/audit/redirects.ts +46 -15
  223. package/src/audit/run.ts +2 -0
  224. package/src/audit/types.ts +2 -1
  225. package/src/blume-modules.d.ts +7 -0
  226. package/src/captcha/client.ts +152 -0
  227. package/src/captcha/hcaptcha.ts +36 -0
  228. package/src/captcha/index.ts +28 -0
  229. package/src/captcha/schema.ts +32 -0
  230. package/src/captcha/turnstile.ts +38 -0
  231. package/src/captcha/verify.ts +71 -0
  232. package/src/cli/command-meta.ts +5 -0
  233. package/src/cli/commands/doctor.ts +22 -16
  234. package/src/cli/commands/skill.ts +96 -0
  235. package/src/cli/index.ts +5 -0
  236. package/src/cli/required-secrets.ts +71 -29
  237. package/src/components/BlumePage.astro +2 -0
  238. package/src/components/content/ApiEndpoint.astro +18 -0
  239. package/src/components/content/ApiField.astro +79 -0
  240. package/src/components/content/ApiPlayground.astro +62 -0
  241. package/src/components/content/ApiRail.astro +19 -0
  242. package/src/components/content/Color.astro +1 -0
  243. package/src/components/content/FileTree.astro +1 -0
  244. package/src/components/content/GithubInfo.astro +1 -0
  245. package/src/components/content/Panel.astro +1 -0
  246. package/src/components/content/ParamField.astro +25 -0
  247. package/src/components/content/RequestExample.astro +17 -0
  248. package/src/components/content/ResponseExample.astro +17 -0
  249. package/src/components/content/ResponseField.astro +32 -0
  250. package/src/components/content/Step.astro +1 -0
  251. package/src/components/content/Tile.astro +1 -0
  252. package/src/components/content/Tooltip.astro +1 -0
  253. package/src/components/content/Tree.astro +1 -0
  254. package/src/components/content/TypeTable.astro +4 -1
  255. package/src/components/content/View.astro +21 -0
  256. package/src/components/content/ViewSwitcher.astro +168 -0
  257. package/src/components/content/api-field.ts +100 -0
  258. package/src/components/content/api-page.ts +224 -0
  259. package/src/components/content/view-switcher.ts +71 -0
  260. package/src/components/index.ts +2 -0
  261. package/src/components/islands/Assistant.astro +10 -1
  262. package/src/components/islands/assistant.tsx +104 -14
  263. package/src/components/islands/code-question.ts +62 -0
  264. package/src/components/islands/hooks.ts +110 -21
  265. package/src/components/islands/support-link.ts +68 -0
  266. package/src/components/layout/Analytics.astro +11 -2
  267. package/src/components/layout/Banner.astro +14 -10
  268. package/src/components/layout/Breadcrumbs.astro +6 -2
  269. package/src/components/layout/ConsentBanner.astro +62 -0
  270. package/src/components/layout/ConsentHead.astro +38 -0
  271. package/src/components/layout/Directory.astro +94 -0
  272. package/src/components/layout/DrawerSwitchers.astro +51 -0
  273. package/src/components/layout/Empty.astro +2 -2
  274. package/src/components/layout/Header.astro +122 -80
  275. package/src/components/layout/LanguageSwitcher.astro +48 -15
  276. package/src/components/layout/NarrationPlayer.astro +1085 -0
  277. package/src/components/layout/NavSelector.astro +33 -7
  278. package/src/components/layout/PageFeedback.astro +54 -49
  279. package/src/components/layout/PageLayout.astro +71 -3
  280. package/src/components/layout/ReferenceLayout.astro +17 -6
  281. package/src/components/layout/RelatedPages.astro +42 -0
  282. package/src/components/layout/RootLayout.astro +308 -45
  283. package/src/components/layout/Search.astro +31 -17
  284. package/src/components/layout/SiteFooter.astro +124 -0
  285. package/src/components/layout/SiteMetatags.astro +16 -0
  286. package/src/components/layout/consent/native.ts +79 -0
  287. package/src/components/layout/consent/types.ts +37 -0
  288. package/src/components/layout/footer-links.ts +53 -0
  289. package/src/components/layout/footer-socials.ts +87 -0
  290. package/src/components/layout/locale-redirect.ts +107 -0
  291. package/src/components/layout/nav-utils.ts +83 -0
  292. package/src/components/layout/page-feedback.ts +96 -0
  293. package/src/components/layout/search/analytics.ts +98 -0
  294. package/src/components/layout/search/flexsearch.ts +17 -5
  295. package/src/components/layout/search/orama-cloud.ts +26 -14
  296. package/src/components/layout/search/types.ts +20 -0
  297. package/src/components/layout/search/typesense.ts +5 -1
  298. package/src/components/openapi/Operation.astro +161 -13
  299. package/src/components/openapi/RequestBody.astro +6 -4
  300. package/src/components/openapi/RequestPanel.astro +59 -7
  301. package/src/components/openapi/Responses.astro +6 -4
  302. package/src/components/openapi/async-snippets.ts +4 -4
  303. package/src/components/openapi/code-samples.ts +97 -0
  304. package/src/components/openapi/security.ts +6 -3
  305. package/src/components/openapi/snippets.ts +869 -8
  306. package/src/components/props.ts +6 -0
  307. package/src/consent/client.ts +95 -0
  308. package/src/consent/clients.ts +19 -0
  309. package/src/consent/ethyca.ts +71 -0
  310. package/src/consent/head.ts +24 -0
  311. package/src/consent/index.ts +37 -0
  312. package/src/consent/init.ts +29 -0
  313. package/src/consent/native.ts +36 -0
  314. package/src/consent/osano.ts +59 -0
  315. package/src/consent/schema.ts +30 -0
  316. package/src/core/builtin-tags.ts +5 -0
  317. package/src/core/client-address.ts +19 -0
  318. package/src/core/config-input.ts +194 -20
  319. package/src/core/data.ts +44 -4
  320. package/src/core/diagnostics.ts +3 -2
  321. package/src/core/footer.ts +26 -0
  322. package/src/core/graph.ts +39 -36
  323. package/src/core/i18n-ui.ts +82 -2
  324. package/src/core/includes.ts +20 -3
  325. package/src/core/links.ts +16 -2
  326. package/src/core/localizable.ts +26 -0
  327. package/src/core/navigation.ts +43 -12
  328. package/src/core/page-modes.ts +56 -0
  329. package/src/core/project-graph.ts +111 -5
  330. package/src/core/redirect-patterns.ts +491 -0
  331. package/src/core/related.ts +131 -0
  332. package/src/core/schema.ts +318 -64
  333. package/src/core/sources/normalize.ts +2 -1
  334. package/src/core/sources/read.ts +8 -3
  335. package/src/core/types.ts +1 -1
  336. package/src/core/ui-packs/ar.ts +43 -0
  337. package/src/core/ui-packs/bg.ts +45 -0
  338. package/src/core/ui-packs/bn.ts +43 -0
  339. package/src/core/ui-packs/ca.ts +46 -0
  340. package/src/core/ui-packs/cs.ts +44 -0
  341. package/src/core/ui-packs/da.ts +45 -0
  342. package/src/core/ui-packs/de.ts +46 -0
  343. package/src/core/ui-packs/el.ts +45 -0
  344. package/src/core/ui-packs/es.ts +46 -0
  345. package/src/core/ui-packs/fa.ts +45 -0
  346. package/src/core/ui-packs/fi.ts +45 -0
  347. package/src/core/ui-packs/fr.ts +46 -0
  348. package/src/core/ui-packs/he.ts +43 -0
  349. package/src/core/ui-packs/hi.ts +44 -0
  350. package/src/core/ui-packs/hr.ts +45 -0
  351. package/src/core/ui-packs/hu.ts +44 -0
  352. package/src/core/ui-packs/id.ts +45 -0
  353. package/src/core/ui-packs/it.ts +45 -0
  354. package/src/core/ui-packs/ja.ts +45 -0
  355. package/src/core/ui-packs/ko.ts +44 -0
  356. package/src/core/ui-packs/nl.ts +46 -0
  357. package/src/core/ui-packs/no.ts +44 -0
  358. package/src/core/ui-packs/pl.ts +45 -0
  359. package/src/core/ui-packs/pt-br.ts +46 -0
  360. package/src/core/ui-packs/pt.ts +45 -0
  361. package/src/core/ui-packs/ro.ts +45 -0
  362. package/src/core/ui-packs/ru.ts +45 -0
  363. package/src/core/ui-packs/sk.ts +44 -0
  364. package/src/core/ui-packs/sr.ts +45 -0
  365. package/src/core/ui-packs/sv.ts +44 -0
  366. package/src/core/ui-packs/th.ts +43 -0
  367. package/src/core/ui-packs/tr.ts +45 -0
  368. package/src/core/ui-packs/uk.ts +45 -0
  369. package/src/core/ui-packs/vi.ts +45 -0
  370. package/src/core/ui-packs/zh-tw.ts +43 -0
  371. package/src/core/ui-packs/zh.ts +43 -0
  372. package/src/core/variables.ts +103 -0
  373. package/src/deploy/artifacts.ts +77 -8
  374. package/src/deploy/cloudflare-negotiation.ts +35 -5
  375. package/src/deploy/headers.ts +2 -1
  376. package/src/deploy/node-headers.ts +63 -9
  377. package/src/deploy/platforms/cloudflare.ts +32 -0
  378. package/src/deploy/platforms/netlify.ts +33 -1
  379. package/src/deploy/platforms/vercel.ts +37 -2
  380. package/src/deploy/redirects.ts +91 -24
  381. package/src/deploy/vercel-negotiation.ts +36 -0
  382. package/src/markdown/api-rail.ts +259 -0
  383. package/src/markdown/code-title.ts +35 -9
  384. package/src/markdown/features.ts +3 -2
  385. package/src/markdown/fence-meta.ts +2 -0
  386. package/src/markdown/include.ts +11 -1
  387. package/src/markdown/index.ts +28 -5
  388. package/src/markdown/math.ts +4 -4
  389. package/src/markdown/variables.ts +56 -0
  390. package/src/markdown/views.ts +82 -0
  391. package/src/narration/build.ts +412 -0
  392. package/src/narration/player.ts +249 -0
  393. package/src/narration/provider.ts +29 -0
  394. package/src/narration/script.ts +592 -0
  395. package/src/og/card.ts +7 -6
  396. package/src/openapi/model.ts +57 -24
  397. package/src/openapi/overlay.ts +256 -0
  398. package/src/openapi/parse.ts +55 -2
  399. package/src/openapi/references.ts +21 -5
  400. package/src/openapi/render-mdx.ts +5 -1
  401. package/src/openapi/scalar.ts +40 -6
  402. package/src/openapi/source.ts +1 -1
  403. package/src/ratelimit/cloudflare.ts +54 -0
  404. package/src/ratelimit/index.ts +30 -0
  405. package/src/ratelimit/memory.ts +45 -0
  406. package/src/ratelimit/runtime.ts +229 -0
  407. package/src/ratelimit/schema.ts +28 -0
  408. package/src/ratelimit/upstash.ts +30 -0
  409. package/src/ratelimit/wrangler.ts +70 -0
  410. package/src/reference/asyncapi.ts +2 -2
  411. package/src/reference/graphql.ts +5 -2
  412. package/src/reference/openapi.ts +21 -7
  413. package/src/reference/options.ts +51 -7
  414. package/src/reference/scalar.ts +33 -8
  415. package/src/registry/eject.ts +20 -4
  416. package/src/registry/registry.ts +7 -0
  417. package/src/search/documents.ts +26 -2
  418. package/src/search/orama-index.ts +28 -3
  419. package/src/search/pagefind-ranking.ts +39 -0
  420. package/src/search/sync/algolia.ts +15 -4
  421. package/src/search/sync/orama-cloud.ts +3 -0
  422. package/src/search/sync/typesense.ts +5 -0
  423. package/src/seo/metatags.ts +67 -0
  424. package/src/theme/chrome-icons.ts +1 -0
  425. package/src/theme/color.ts +500 -0
  426. package/src/theme/entry.ts +56 -3
  427. package/src/theme/palette.ts +121 -45
  428. package/src/translate/prompts.ts +1 -0
  429. package/src/translate/validate.ts +8 -2
  430. package/src/upgrade/upgrade.ts +4 -171
  431. package/dist/cli/chunk-364znk6q.js +0 -75
  432. package/dist/cli/chunk-364znk6q.js.map +0 -11
  433. package/dist/cli/chunk-5m5nmvyq.js.map +0 -13
  434. package/dist/cli/chunk-9t7a85s3.js.map +0 -10
  435. package/dist/cli/chunk-c8chx29p.js.map +0 -36
  436. package/dist/cli/chunk-pehfxfta.js.map +0 -10
  437. package/dist/cli/chunk-pv29h0wf.js.map +0 -190
  438. package/dist/cli/chunk-q58y5e6a.js.map +0 -10
  439. package/dist/cli/chunk-wrr3j9w9.js.map +0 -35
  440. package/dist/cli/chunk-yfyb25rh.js.map +0 -14
  441. package/docs/content/sources.mdx +0 -334
  442. /package/dist/cli/{chunk-2eytanqx.js.map → chunk-1n228xqb.js.map} +0 -0
  443. /package/dist/cli/{chunk-35d4wj9f.js.map → chunk-2n4ps1b3.js.map} +0 -0
  444. /package/dist/cli/{chunk-e04dxsz1.js.map → chunk-4ejxbzee.js.map} +0 -0
  445. /package/dist/cli/{chunk-tzmab476.js.map → chunk-5ffywsmb.js.map} +0 -0
  446. /package/dist/cli/{chunk-2z928egk.js.map → chunk-5wj2qwn2.js.map} +0 -0
  447. /package/dist/cli/{chunk-b07cmahc.js.map → chunk-amrb6hgh.js.map} +0 -0
  448. /package/dist/cli/{chunk-akbpwfxc.js.map → chunk-cmtcnh9f.js.map} +0 -0
  449. /package/dist/cli/{chunk-crgn1q09.js.map → chunk-e7vvk5ya.js.map} +0 -0
  450. /package/dist/cli/{chunk-tkacnehg.js.map → chunk-gp84za0h.js.map} +0 -0
  451. /package/dist/cli/{chunk-r9rcc4w7.js.map → chunk-p804f3pe.js.map} +0 -0
  452. /package/dist/cli/{chunk-8ktnccpt.js.map → chunk-ta27feev.js.map} +0 -0
  453. /package/dist/cli/{chunk-a58773jm.js.map → chunk-y4ykbark.js.map} +0 -0
  454. /package/dist/cli/{chunk-p3v96n38.js.map → chunk-y5h4r2y0.js.map} +0 -0
  455. /package/dist/cli/{chunk-acanzt5p.js.map → chunk-yj17vs39.js.map} +0 -0
@@ -49,6 +49,23 @@ Every page accepts the following frontmatter. All fields are optional.
49
49
  default: "false",
50
50
  description: "Shorthand for seo.noindex.",
51
51
  },
52
+ narration: {
53
+ type: "boolean",
54
+ default: "true",
55
+ description:
56
+ "Set to false to leave this page without the Listen to this page player when narration is on.",
57
+ },
58
+ related: {
59
+ type: "(string | { [title]: string })[] | false",
60
+ description:
61
+ "Pages to suggest at the foot of this one, up to ten: root-relative paths, absolute URLs, or { Title: link } to name a link. See Related pages below.",
62
+ },
63
+ pagination: {
64
+ type: "boolean",
65
+ default: "true",
66
+ description:
67
+ "Set to false to drop the previous and next links at the foot of the page. The page stays in the order, so its neighbors still link to it.",
68
+ },
52
69
  icon: {
53
70
  type: "string",
54
71
  description:
@@ -59,9 +76,64 @@ Every page accepts the following frontmatter. All fields are optional.
59
76
  description:
60
77
  'Pin the page\'s "last updated" date (ISO or YAML date); overrides the git-derived date.',
61
78
  },
79
+ mode: {
80
+ type: '"default" | "wide" | "center" | "custom" | "frame"',
81
+ default: '"default"',
82
+ description: "What the page shows around its content. See Layout below.",
83
+ },
84
+ api: {
85
+ type: "string",
86
+ description:
87
+ "An endpoint this page documents by hand, as an HTTP method and a path or URL (POST /v1/users). See Hand-written API pages.",
88
+ },
89
+ authMethod: {
90
+ type: '"bearer" | "basic" | "key" | "none"',
91
+ description:
92
+ "How an api page's endpoint authenticates, over the site's api.auth.",
93
+ },
94
+ playground: {
95
+ type: '"interactive" | "simple" | "none"',
96
+ default: '"interactive"',
97
+ description:
98
+ "What an api page shows: the Try it panel and samples, the samples only, or neither.",
99
+ },
62
100
  }}
63
101
  />
64
102
 
103
+ ## Layout
104
+
105
+ `mode` sets what a page shows around its content:
106
+
107
+ | Mode | Sidebar | Title and table of contents | Page-end links and footer | Content width |
108
+ | --- | --- | --- | --- | --- |
109
+ | `default` | Yes | Yes | Yes | The prose measure |
110
+ | `wide` | Yes | Title only | Yes | The whole column |
111
+ | `center` | No | Title only | Yes | A wider column, centered |
112
+ | `frame` | Yes | No | No | The whole column |
113
+ | `custom` | No | No | No | The whole page |
114
+
115
+ `wide` suits a page of wide tables or diagrams, and `center` release notes or an announcement read top to bottom. `custom` keeps only the header, for a landing page written in MDX, and `frame` keeps the sidebar too, for a tool or dashboard that sits in the docs' navigation. Neither renders the page's title or description, so write your own heading. Every mode stays in search and keeps its Markdown copy.
116
+
117
+ ```yaml lineNumbers
118
+ title: Welcome
119
+ mode: custom
120
+ ```
121
+
122
+ The values match Mintlify's; its `assistant` mode, a full-page chat, has no equivalent.
123
+
124
+ ## Related pages
125
+
126
+ `related` lists pages to suggest at the foot of this one, as cards under a **Related pages** heading:
127
+
128
+ ```yaml lineNumbers
129
+ related:
130
+ - /guides/deployment
131
+ - Search setup: /configuration/search
132
+ - https://astro.build
133
+ ```
134
+
135
+ A root-relative path names another page, which the card shows with that page's title and description; on a translated page it links to the translation when there is one. A `Title: link` entry sets the card's title itself, and an absolute URL links off the site under its host name. Paths are checked like any link on the page, so [`blume validate`](/docs/cli/validate) reports one that matches no page. The key matches Mintlify's, so migrated pages keep it as written.
136
+
65
137
  ## Sidebar
66
138
 
67
139
  ```yaml lineNumbers
@@ -99,8 +171,12 @@ seo:
99
171
  search:
100
172
  exclude: false
101
173
  tags: [api]
174
+ keywords: [install, setup]
175
+ boost: 3
102
176
  ```
103
177
 
178
+ `exclude` keeps the page out of search, and `tags` groups it under a filter in the search dialog. `keywords` are extra terms the page is found by, beyond its own text. `boost` multiplies the page's relevance: above 1 ranks it higher, below 1 lower. See [Ranking](/docs/configuration/search#ranking).
179
+
104
180
  ## AI
105
181
 
106
182
  ```yaml lineNumbers
@@ -85,7 +85,23 @@ i18n: {
85
85
 
86
86
  Each language gets its own sidebar, built from that locale's files — so translations can diverge in structure, ordering, or labels. Folder [`meta.ts`](/docs/content/meta) files resolve per locale, too: under the default `dir` parser, put a `meta.ts` under `fr/guides/` to order the French group independently. Until a locale has one (or a shared `meta.$.ts`), its group mirrors the [fallback](#fallbacks) locale's — that folder's `meta.ts` and the `sidebar.display` its index page sets — so pages that fall back keep the same titles, order, and collapsible groups. Under the `dot` parser translations sit next to the originals, so a folder's `meta.ts` applies to every locale. Everything else about [navigation](/docs/content/navigation) works the same, per language.
87
87
 
88
- Header tabs are configured, not derived from content, so their labels localize in `blume.config.ts`: a tab `label` accepts a per-locale map (`{ en: "Docs", fr: "Documentation" }`) alongside the plain-string form, falling back to the default locale's entry for locales you haven't filled in. See [Tabs](/docs/content/navigation#tabs). Tab paths, header links, and the header logo's link move into the reader's locale too, whenever that locale serves the route, so the header stays inside one language. A route only the default locale serves — a [custom page](/docs/advanced/custom-pages) or the generated [changelog](/docs/advanced/changelog) index — keeps its own path instead of pointing at a localized URL that would 404.
88
+ Header tabs, header links, and the footer are configured, not derived from content, so their labels localize in `blume.config.ts`. Every configured label accepts a per-locale map (`{ en: "Docs", fr: "Documentation" }`) alongside the plain-string form, falling back to the default locale's entry for locales you haven't filled in: tab and dropdown labels ([Tabs](/docs/content/navigation#tabs)), [featured links](/docs/content/navigation#featured-links), [header actions](/docs/content/navigation#header-actions) and the call to action, [footer](/docs/configuration#footer) links, and the [banner](/docs/configuration#banner)'s text and link text.
89
+
90
+ ```ts blume.config.ts lineNumbers
91
+ navigation: {
92
+ cta: {
93
+ href: "https://acme.dev/signup",
94
+ label: { en: "Start free", fr: "Essai gratuit" },
95
+ },
96
+ },
97
+ footer: {
98
+ links: [
99
+ { label: { en: "Pricing", fr: "Tarifs" }, href: "https://acme.dev/pricing" },
100
+ ],
101
+ },
102
+ ```
103
+
104
+ Tab paths, header and footer links, and the header logo's link move into the reader's locale too, whenever that locale serves the route, so the chrome stays inside one language. A route only the default locale serves — a [custom page](/docs/advanced/custom-pages) or the generated [changelog](/docs/advanced/changelog) index — keeps its own path instead of pointing at a localized URL that would 404.
89
105
 
90
106
  ## Fallbacks
91
107
 
@@ -100,7 +116,7 @@ i18n: {
100
116
 
101
117
  Fallback pages point their canonical link at the page they copy, are left out of the search index, `llms.txt`, and the MCP and JSON API page lists, and aren't advertised as real translations in `hreflang`, so untranslated content doesn't compete for ranking. They still appear in that locale's sidebar, so navigation stays complete — a reader can reach every page in any language.
102
118
 
103
- Release notes from a [`githubReleases()`](/docs/content/sources#github-releases) source are the exception: they're published in one language, so they're never copied to other locales' URLs and show no language switcher.
119
+ Release notes from a [`githubReleases()`](/docs/content/sources/github-releases) source are the exception: they're published in one language, so they're never copied to other locales' URLs and show no language switcher.
104
120
 
105
121
  :::tip
106
122
  Start by translating your most important pages — the homepage, quickstart, and top guides — and let the rest fall back. You can fill in translations over time without breaking any links.
@@ -124,7 +140,26 @@ Blume validates each result's structure — frontmatter, code fences, links —
124
140
 
125
141
  ## The language switcher
126
142
 
127
- When i18n is on, a language switcher appears in the header automatically, generated from your `locales`. For each page it links the matching translation in every language; where a translation is missing it links the fallback page and marks it as not translated. There's nothing to configure. Pages from a [GitHub Releases](/docs/content/sources#github-releases) source are the exception: release notes exist in one language only, so those pages render without a switcher.
143
+ When i18n is on, a language switcher appears in the header automatically, generated from your `locales`. For each page it links the matching translation in every language; where a translation is missing it links the fallback page and marks it as not translated. There's nothing to configure. Pages from a [GitHub Releases](/docs/content/sources/github-releases) source are the exception: release notes exist in one language only, so those pages render without a switcher.
144
+
145
+ ## Routing by browser language
146
+
147
+ To send visitors to their own language, turn on `routeByBrowserLanguage`:
148
+
149
+ ```ts blume.config.ts lineNumbers
150
+ i18n: {
151
+ defaultLocale: "en",
152
+ locales: [
153
+ { code: "en", label: "English" },
154
+ { code: "fr", label: "Français" },
155
+ ],
156
+ routeByBrowserLanguage: true,
157
+ }
158
+ ```
159
+
160
+ A visitor who lands on the default language's home page from outside the site then goes to the home page in their browser's preferred language, when the site has it. Blume tries each language the browser prefers, in order, matching an exact code first and then the base language (`fr-CA` finds `fr`). If the default language comes first, the visitor stays. Once a reader picks a language with the switcher, Blume remembers the pick and stops routing them. Only the home page routes; a link to any other page opens as written.
161
+
162
+ The redirect runs in the browser before the page paints, so it works on any host, static builds included. It's off by default because search engines crawl in one language: with it on, keep the [`hreflang` alternates](#seo) Blume emits so each language's pages are still indexed.
128
163
 
129
164
  ## Translated UI
130
165
 
@@ -41,6 +41,22 @@ Relative image references inside a partial keep working: they're rebased onto th
41
41
 
42
42
  Editing a partial while `blume dev` is running reloads every page that includes it.
43
43
 
44
+ ## Props
45
+
46
+ Pass values into a partial as attributes, and read them in the partial with `{{name}}`, the same syntax as [variables](/docs/content/variables). One partial then serves every page that needs a variation of it:
47
+
48
+ ```mdx docs/_snippets/upgrade.mdx
49
+ Upgrade to the **{{plan}}** plan to use {{feature}}.
50
+ ```
51
+
52
+ ```mdx docs/sso.mdx
53
+ <include plan="Enterprise" feature="SSO">
54
+ ./_snippets/upgrade.mdx
55
+ </include>
56
+ ```
57
+
58
+ A prop applies to the included file and to anything it includes in turn, and takes precedence over a site-wide variable with the same name. Every attribute except `lang` and `meta` is a prop; attribute values are plain text.
59
+
44
60
  ## Including code files
45
61
 
46
62
  A target that isn't `.md`/`.mdx` is embedded as a fenced code block, with the language inferred from the extension. Use `lang` to override the language (or to show a Markdown file as source rather than splicing it), and `meta` to pass a fence meta string such as a title:
@@ -34,6 +34,7 @@ Blume only reads `meta.ts` files from folders your content covers: one under a f
34
34
  | `order` | `number` | Position among sibling groups and pages. Lower numbers sort first. |
35
35
  | `collapsed` | `boolean` | Under the [`group` display mode](/docs/content/navigation#display-modes), whether the group starts collapsed. |
36
36
  | `display` | `"flat" \| "group" \| "page"` | Render mode for this group; overrides the global [`navigation.sidebar.display`](/docs/content/navigation#display-modes). |
37
+ | `directory` | `"card" \| "accordion" \| "none"` | List the group's pages below its `index` page's content. Nested folders inherit it. See [directory listings](/docs/content/navigation#directory-listings). |
37
38
  | `pages` | `string[]` | Explicit order for the group's children, by slug. |
38
39
 
39
40
  The `pages` array lists children by slug — the folder or file name with its numeric prefix and any parentheses stripped (so `01-quickstart.mdx` is `"quickstart"`). Each listed child takes its position in the array as its order (`0`, `1`, `2`, …). Children you leave out still appear, sorted by their own order: an `index` page stays first, a child with a `sidebar.order`, a numeric prefix, or its own `meta.ts` `order` sorts by that number among the listed ones, and a child with none of these goes after them. List every child when the array should be the whole order.
@@ -8,6 +8,7 @@ export default defineMeta({
8
8
  "frontmatter",
9
9
  "syntax",
10
10
  "includes",
11
+ "variables",
11
12
  "components",
12
13
  "islands",
13
14
  "sources",
@@ -106,6 +106,24 @@ A group's `display` applies to that group only — nested subgroups resolve thei
106
106
 
107
107
  A group in an [explicit sidebar](#explicit-sidebar) overrides the global mode with its own `display`, exactly as before.
108
108
 
109
+ ## Directory listings
110
+
111
+ A group's own page can list the group's other pages below its content, so a section's landing page doubles as its table of contents. Set `directory` in the folder's [`meta.ts`](/docs/content/meta):
112
+
113
+ ```ts guides/meta.ts lineNumbers
114
+ import { defineMeta } from "blume";
115
+
116
+ export default defineMeta({
117
+ directory: "card",
118
+ });
119
+ ```
120
+
121
+ - **`card`** shows each page and subgroup as a card with its icon and description.
122
+ - **`accordion`** lists the pages as rows, and each subgroup as a section that opens to its pages.
123
+ - **`none`** lists nothing. This is the default.
124
+
125
+ Only a group with its own page has somewhere to show the listing: a folder's `index` page, or an explicit group's `root`. The setting carries down to nested folders, so one `directory` on the content root's `meta.ts` gives every section a listing, and a folder can set its own value (`none` included) to override what it inherits. An [explicit sidebar](#explicit-sidebar) sets `directory` on its groups the same way.
126
+
109
127
  ## Ordering
110
128
 
111
129
  When the sidebar is generated, order is resolved highest priority first:
@@ -234,6 +252,8 @@ Each item takes a `label`, a `path`, and optional `icon`, `description`, and `ta
234
252
 
235
253
  With [versioning](/docs/content/versioning) configured, Blume renders a version selector automatically — declaring your own `kind: "version"` selector here replaces the automatic one, so hand-rolled setups keep working.
236
254
 
255
+ On screens narrower than the desktop sidebar, selectors, the version selector, and the [language switcher](/docs/content/i18n#the-language-switcher) move out of the header and into the top of the mobile navigation drawer, where each spans the drawer's width.
256
+
237
257
  ## Featured links
238
258
 
239
259
  Pin links to the top of the sidebar, above every section — a blog, a changelog, a contact or support page that should always be one click away. Unlike the generated tree, featured links are **not scoped by tab**: they show on every route, on every breakpoint.
@@ -267,7 +287,7 @@ navigation: {
267
287
  }
268
288
  ```
269
289
 
270
- Each item is a page route (a string), a group (`label` + `items`), or a link (`label` + `href`). Groups can nest, override the global [`display` mode](#display-modes), and start `collapsed`.
290
+ Each item is a page route (a string), a group (`label` + `items`), or a link (`label` + `href`). Groups can nest, override the global [`display` mode](#display-modes), start `collapsed`, and link a `root` page, which can list the group's pages with a [`directory`](#directory-listings).
271
291
 
272
292
  Blume warns about an item it can't render as written: a route that matches no page (the item is left out), a `root` that matches no page (its link would 404), or an item with no route, `href`, `root`, or `items` (left out).
273
293
 
@@ -282,23 +302,25 @@ navigation: {
282
302
  }
283
303
  ```
284
304
 
305
+ Like tab labels, a `label` here can be a map of locale code to text on a [multilingual](/docs/content/i18n#per-locale-navigation) site.
306
+
285
307
  `cta` is singular on purpose — a docs header has room for exactly one thing a reader is being asked to do, and a row of buttons asks for nothing. Secondary links belong in `actions`, or in [`featured`](#featured-links) if they should sit with the sidebar instead.
286
308
 
287
309
  An `http(s)` or protocol-relative href opens in a new tab; a route stays in the tab, and is validated against your pages at build time like a `featured` link — so a page served by another app on the same host (`/signup` on the product, say) should be written as an absolute URL. `actions` are hidden below the `sm` breakpoint, where the header has room for the logo and the navigation toggle and nothing else. `cta` hides there too, except on a page with no navigation toggle — a landing page on `PageLayout` with no tabs — where it stays, since nothing else can surface it on a phone.
288
310
 
289
311
  ## Repository link
290
312
 
291
- When you set [`github`](/docs/configuration) in your config, Blume shows a GitHub icon in the header — beside the theme toggle — that links to your repository. It's on by default; hide it with `navigation.repo`:
313
+ When you set [`github`](/docs/configuration) in your config, Blume shows a GitHub icon in the [footer](/docs/configuration#footer), ahead of your social profiles, that links to your repository. It's on by default; hide it with `navigation.repo`:
292
314
 
293
315
  ```ts blume.config.ts lineNumbers
294
316
  navigation: {
295
- repo: false, // hide the header GitHub link (default: true)
317
+ repo: false, // hide the footer's GitHub link (default: true)
296
318
  }
297
319
  ```
298
320
 
299
321
  The link only appears when `github` is configured, so projects without a repo are unaffected either way.
300
322
 
301
- `repo` also takes an absolute URL, which points the header mark anywhere on GitHub:
323
+ `repo` also takes an absolute URL, which points the GitHub mark anywhere on GitHub:
302
324
 
303
325
  ```ts blume.config.ts lineNumbers
304
326
  navigation: {
@@ -306,14 +328,14 @@ navigation: {
306
328
  }
307
329
  ```
308
330
 
309
- That's for a project whose docs repo is private. `github` drives the per-page edit link, the header mark and the [agent manifest](/docs/discoverability/agent-discovery)'s repository together, so such a project has to leave `github` unset — and a URL is what lets it still show a mark pointing somewhere public. The icon stays the GitHub mark, so a link to another host belongs in [`actions`](#header-actions).
331
+ That's for a project whose docs repo is private. `github` drives the per-page edit link, the GitHub mark and the [agent manifest](/docs/discoverability/agent-discovery)'s repository together, so such a project has to leave `github` unset — and a URL is what lets it still show a mark pointing somewhere public. A `footer.socials.github` URL takes the mark's place, and a link to another host belongs in the footer's `links` or [`actions`](#header-actions).
310
332
 
311
333
  ## Breadcrumbs and pagination
312
334
 
313
335
  These come for free from the sidebar tree — no configuration:
314
336
 
315
337
  - **Breadcrumbs** show the current page's parent group above the title.
316
- - **Previous and next** links at the foot of each page follow sidebar order, skipping hidden pages.
338
+ - **Previous and next** links at the foot of each page follow sidebar order, skipping hidden pages. Set [`pagination: false`](/docs/content/frontmatter) in a page's frontmatter to leave them off that page.
317
339
 
318
340
  ## On this page
319
341
 
@@ -0,0 +1,32 @@
1
+ ---
2
+ title: Contentful
3
+ description: Read a Contentful content type through the Delivery API with the contentful() source, lowering rich text bodies to Markdown with nothing to install.
4
+ ---
5
+
6
+ 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.
7
+
8
+ ```ts blume.config.ts
9
+ import { defineConfig } from "blume";
10
+ import { contentful, filesystem } from "blume/sources";
11
+
12
+ export default defineConfig({
13
+ content: {
14
+ sources: [
15
+ filesystem({ root: "docs" }),
16
+ contentful({
17
+ prefix: "guides",
18
+ space: "abc123",
19
+ contentType: "guide",
20
+ // environment: "master", locale: "en-US"
21
+ // Field ids default to title / description / slug / body, and the
22
+ // date to sys.updatedAt
23
+ fields: { body: "content" },
24
+ // Extra Delivery API query parameters
25
+ params: { "fields.section": "sdk" },
26
+ }),
27
+ ],
28
+ },
29
+ });
30
+ ```
31
+
32
+ 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()`](/docs/content/sources/custom); 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.
@@ -0,0 +1,39 @@
1
+ ---
2
+ title: Custom sources
3
+ description: Plug any backend into Blume with custom() — pass an object that implements the ContentSource interface, or a built-in engine with custom serializers.
4
+ ---
5
+
6
+ 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:
7
+
8
+ ```ts blume.config.ts
9
+ import { defineConfig } from "blume";
10
+ import { custom, filesystem } from "blume/sources";
11
+ import { sanitySource } from "blume/sources/sanity.ts";
12
+
13
+ export default defineConfig({
14
+ content: {
15
+ sources: [
16
+ filesystem({ root: "docs" }),
17
+ custom(
18
+ sanitySource({
19
+ name: "guides",
20
+ prefix: "guides",
21
+ projectId: "abc123",
22
+ dataset: "production",
23
+ query: `*[_type == "guide"]`,
24
+ // Map custom Portable Text blocks to Blume components
25
+ serializers: {
26
+ callout: (block) => `<Callout>${block.text}</Callout>`,
27
+ },
28
+ })
29
+ ),
30
+ ],
31
+ },
32
+ });
33
+ ```
34
+
35
+ 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.
36
+
37
+ `custom()` takes a `ContentSource` instance rather than an options object, so there is nothing to pass the [shared options](/docs/content/sources#adapters) `prefix` or `pollInterval` to. The source sets its own `prefix` property, and re-fetching is whatever its `watch` method implements. It carries a live instance rather than plain data, so it declares no runtime dependency or secret of its own — the instance manages those itself.
38
+
39
+ 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).
@@ -0,0 +1,31 @@
1
+ ---
2
+ title: GitHub Releases
3
+ description: Turn a repository's GitHub releases into changelog entries with the githubReleases() source, so your release notes are your changelog.
4
+ ---
5
+
6
+ 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.
7
+
8
+ ```ts blume.config.ts
9
+ import { defineConfig } from "blume";
10
+ import { filesystem, githubReleases } from "blume/sources";
11
+
12
+ export default defineConfig({
13
+ content: {
14
+ sources: [
15
+ filesystem({ root: "content" }),
16
+ githubReleases({
17
+ prefix: "changelog",
18
+ owner: "acme",
19
+ repo: "sdk",
20
+ // prereleases: false, // include prereleases (default off)
21
+ // drafts: false, // include drafts (needs a write token)
22
+ // limit: 100, // cap releases, newest-first
23
+ }),
24
+ ],
25
+ },
26
+ });
27
+ ```
28
+
29
+ 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`.
30
+
31
+ 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](/docs/content/sources#caching-and-offline-builds) 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.
@@ -0,0 +1,91 @@
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
+ sidebar:
5
+ hidden: true
6
+ ---
7
+
8
+ 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.
9
+
10
+ ## The default
11
+
12
+ 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.
13
+
14
+ ```ts blume.config.ts
15
+ import { defineConfig } from "blume";
16
+
17
+ export default defineConfig({
18
+ content: { root: "docs" },
19
+ });
20
+ ```
21
+
22
+ ## Adapters
23
+
24
+ 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.
25
+
26
+ ```ts blume.config.ts
27
+ import { defineConfig } from "blume";
28
+ import { filesystem, mdxRemote } from "blume/sources";
29
+
30
+ export default defineConfig({
31
+ content: {
32
+ sources: [
33
+ // Local docs at the site root
34
+ filesystem({ root: "docs" }),
35
+
36
+ // Remote MDX from a GitHub repo, mounted under /sdk
37
+ mdxRemote({
38
+ prefix: "sdk",
39
+ github: { owner: "acme", repo: "sdk", ref: "main", path: "docs" },
40
+ }),
41
+ ],
42
+ },
43
+ });
44
+ ```
45
+
46
+ Beyond `filesystem()`, each built-in adapter has its own page, and [`custom()`](/docs/content/sources/custom) plugs in any other backend:
47
+
48
+ | Adapter | Reads | Runtime dependency | Environment variable |
49
+ | --- | --- | --- | --- |
50
+ | [`obsidian()`](/docs/content/sources/obsidian) | An Obsidian vault, in place | — | — |
51
+ | [`mdxRemote()`](/docs/content/sources/remote-mdx) | `.md`/`.mdx` from a GitHub repo or a raw URL | — | `GITHUB_TOKEN` (private repos) |
52
+ | [`githubReleases()`](/docs/content/sources/github-releases) | A repo's releases, as changelog entries | — | `GITHUB_TOKEN` (private repos) |
53
+ | [`sanity()`](/docs/content/sources/sanity) | A GROQ query over a Sanity dataset | `@sanity/client` | `SANITY_TOKEN` (private datasets) |
54
+ | [`notion()`](/docs/content/sources/notion) | A Notion database | `@notionhq/client` | `NOTION_TOKEN` |
55
+ | [`contentful()`](/docs/content/sources/contentful) | One Contentful content type | — | `CONTENTFUL_ACCESS_TOKEN` |
56
+ | [`payload()`](/docs/content/sources/payload) | A Payload collection | — | `PAYLOAD_API_KEY` |
57
+ | [`strapi()`](/docs/content/sources/strapi) | A Strapi content type | — | `STRAPI_API_TOKEN` |
58
+ | [`custom()`](/docs/content/sources/custom) | Any `ContentSource` you implement | — | — |
59
+
60
+ Every adapter that takes an options object accepts two shared options:
61
+
62
+ - **`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`.
63
+ - **`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.
64
+
65
+ 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.
66
+
67
+ 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.
68
+
69
+ ## Caching and offline builds
70
+
71
+ 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.
72
+
73
+ 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.
74
+
75
+ ## Preview and sync
76
+
77
+ Two flags control how remote content is fetched and what's included:
78
+
79
+ - **`--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.
80
+ - **`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.
81
+
82
+ ```sh
83
+ blume dev --preview # author workflow: see drafts live
84
+ blume build --preview # render a full preview build
85
+ blume sync # refresh remote content now
86
+ blume sync --force # ...ignoring any cached snapshot
87
+ ```
88
+
89
+ ## Rendering and escaping
90
+
91
+ 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 `&copy;` — 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.
@@ -0,0 +1,16 @@
1
+ import { defineMeta } from "blume";
2
+
3
+ export default defineMeta({
4
+ pages: [
5
+ "obsidian",
6
+ "remote-mdx",
7
+ "github-releases",
8
+ "sanity",
9
+ "notion",
10
+ "contentful",
11
+ "payload",
12
+ "strapi",
13
+ "custom",
14
+ ],
15
+ title: "Content sources",
16
+ });
@@ -0,0 +1,32 @@
1
+ ---
2
+ title: Notion
3
+ description: Import a Notion database with the notion() source — rows become pages, properties become frontmatter, and blocks become Blume components.
4
+ ---
5
+
6
+ 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.
7
+
8
+ ```ts blume.config.ts
9
+ import { defineConfig } from "blume";
10
+ import { filesystem, notion } from "blume/sources";
11
+
12
+ export default defineConfig({
13
+ content: {
14
+ sources: [
15
+ filesystem({ root: "docs" }),
16
+ notion({
17
+ prefix: "handbook",
18
+ database: "8f2c1e0a4b7d4f3c9e6a5d2b1c0f9e8d", // the id in the database URL
19
+ // Property names default to the title-typed prop / Description / Slug / Order / Status
20
+ // Pages whose Status isn't publishedValue (default "Published") import as drafts
21
+ publishedValue: "Done",
22
+ }),
23
+ ],
24
+ },
25
+ });
26
+ ```
27
+
28
+ 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.
29
+
30
+ 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.
31
+
32
+ **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.
@@ -0,0 +1,37 @@
1
+ ---
2
+ title: Obsidian
3
+ description: Publish an Obsidian vault in place with the obsidian() source — wikilinks, properties, and vault images resolve at build time, with no export step.
4
+ ---
5
+
6
+ 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.
7
+
8
+ ```ts blume.config.ts
9
+ import { defineConfig } from "blume";
10
+ import { filesystem, obsidian } from "blume/sources";
11
+
12
+ export default defineConfig({
13
+ content: {
14
+ sources: [
15
+ filesystem({ root: "docs" }),
16
+ obsidian({
17
+ prefix: "notes",
18
+ vault: "vault",
19
+ // Vault folder names to skip at any depth, on top of dot-folders
20
+ exclude: ["Templates", "Daily"],
21
+ }),
22
+ ],
23
+ },
24
+ });
25
+ ```
26
+
27
+ `[[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.
28
+
29
+ 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 (`![chart](./chart.png)`) 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.
30
+
31
+ 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.
32
+
33
+ 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.
34
+
35
+ 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.
36
+
37
+ 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.
@@ -0,0 +1,30 @@
1
+ ---
2
+ title: Payload
3
+ description: Read a Payload collection through its REST API with the payload() source, lowering Lexical rich text bodies to Markdown with nothing to install.
4
+ ---
5
+
6
+ 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.
7
+
8
+ ```ts blume.config.ts
9
+ import { defineConfig } from "blume";
10
+ import { filesystem, payload } from "blume/sources";
11
+
12
+ export default defineConfig({
13
+ content: {
14
+ sources: [
15
+ filesystem({ root: "docs" }),
16
+ payload({
17
+ prefix: "handbook",
18
+ url: "https://cms.example.com",
19
+ collection: "docs",
20
+ // Field paths default to title / description / slug / content / updatedAt
21
+ fields: { body: "richText" },
22
+ // Extra query parameters: where[...], sort
23
+ params: { sort: "title" },
24
+ }),
25
+ ],
26
+ },
27
+ });
28
+ ```
29
+
30
+ 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()`](/docs/content/sources/custom). Setting `serializers` writes the source's pages as MDX so the returned components render; a body held in a Markdown text field stays Markdown.