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
@@ -15,6 +15,8 @@ const uiStringsObject = z.object({
15
15
  .object({
16
16
  addToCursor: z.string().default("Add to Cursor"),
17
17
  addToVscode: z.string().default("Add to VS Code"),
18
+ // A code block's button that opens the assistant with the code attached.
19
+ askAboutCode: z.string().default("Ask about this code"),
18
20
  connectMcp: z.string().default("Connect to MCP"),
19
21
  copied: z.string().default("Copied!"),
20
22
  copyClaudeCode: z.string().default("Copy Claude Code command"),
@@ -47,11 +49,32 @@ const uiStringsObject = z.object({
47
49
  copy: z.string().default("Copy conversation"),
48
50
  empty: z.string().default("Ask a question about the docs."),
49
51
  error: z.string().default("Sorry, something went wrong."),
52
+ // The question sent about an attached code block when the reader
53
+ // writes none.
54
+ explainCode: z.string().default("Explain this code."),
50
55
  label: z.string().default("Ask a question"),
56
+ // The header trigger's tooltip.
57
+ open: z.string().default("Open assistant"),
51
58
  placeholder: z.string().default("Ask a question…"),
59
+ // The answer when the rate limit (`rateLimit`) turns a question away.
60
+ rateLimited: z
61
+ .string()
62
+ .default(
63
+ "You've asked a lot of questions. Try again in a few minutes."
64
+ ),
65
+ // The attached code block's dismiss button.
66
+ removeCode: z.string().default("Remove code"),
52
67
  send: z.string().default("Send"),
68
+ // The link to your support channel (`ai.assistant.support`), and the
69
+ // subject of an email it starts.
70
+ support: z.string().default("Contact support"),
71
+ supportSubject: z.string().default("Question from the docs"),
53
72
  tip: z.string().default("Tip: You can open and close chat with"),
54
73
  title: z.string().default("Assistant"),
74
+ // The answer when the bot check (`ai.assistant.captcha`) fails.
75
+ verifyFailed: z
76
+ .string()
77
+ .default("We couldn't check that you're human. Try again."),
55
78
  you: z.string().default("You"),
56
79
  })
57
80
  .prefault({}),
@@ -70,6 +93,23 @@ const uiStringsObject = z.object({
70
93
  title: z.string().default("Changelog"),
71
94
  })
72
95
  .prefault({}),
96
+ // The built-in cookie consent banner (`consent: native()`) and the footer
97
+ // link that reopens a consent manager's preferences.
98
+ consent: z
99
+ .object({
100
+ accept: z.string().default("Accept"),
101
+ decline: z.string().default("Decline"),
102
+ // The banner's accessible name.
103
+ label: z.string().default("Cookie consent"),
104
+ message: z
105
+ .string()
106
+ .default(
107
+ "We'd like to use cookies to understand how these docs are used."
108
+ ),
109
+ policy: z.string().default("Privacy policy"),
110
+ settings: z.string().default("Cookie settings"),
111
+ })
112
+ .prefault({}),
73
113
  content: z
74
114
  .object({
75
115
  // `<Component>`'s source tab.
@@ -89,7 +129,12 @@ const uiStringsObject = z.object({
89
129
  prop: z.string().default("Prop"),
90
130
  // `<Tabs dropdown>`: the accessible name of the tab picker.
91
131
  selectTab: z.string().default("Select tab"),
92
- // `<Expandable>`'s toggle when it sets no `title`.
132
+ // `<View>`: the accessible name of the page's view picker.
133
+ selectView: z.string().default("Select view"),
134
+ // An expanded `expandable` code block's toggle.
135
+ showLess: z.string().default("Show less"),
136
+ // `<Expandable>`'s toggle when it sets no `title`, and a collapsed
137
+ // `expandable` code block's.
93
138
  showMore: z.string().default("Show more"),
94
139
  stars: z.string().default("Stars"),
95
140
  // An untitled tab's label; `{n}` is replaced with its position.
@@ -102,8 +147,11 @@ const uiStringsObject = z.object({
102
147
  .prefault({}),
103
148
  feedback: z
104
149
  .object({
150
+ // The written comment box after the rating (`feedback.comments`).
151
+ comment: z.string().default("Tell us more (optional)"),
105
152
  no: z.string().default("No"),
106
153
  question: z.string().default("Was this page helpful?"),
154
+ send: z.string().default("Send"),
107
155
  thanks: z.string().default("Thanks for your feedback!"),
108
156
  yes: z.string().default("Yes"),
109
157
  })
@@ -114,6 +162,36 @@ const uiStringsObject = z.object({
114
162
  untranslated: z.string().default("Not translated"),
115
163
  })
116
164
  .prefault({}),
165
+ narration: z
166
+ .object({
167
+ // Spoken, not shown: the cues that introduce a callout of each type, a
168
+ // step (`{n}` is its number), a tab (`{title}` is its label), and a
169
+ // collapsible section. End each with a full stop so it reads as its own
170
+ // sentence.
171
+ cueDanger: z.string().default("Danger."),
172
+ cueInfo: z.string().default("Info."),
173
+ cueNote: z.string().default("Note."),
174
+ cueSection: z.string().default("Expandable section."),
175
+ cueStep: z.string().default("Step {n}."),
176
+ cueSuccess: z.string().default("Success."),
177
+ cueTab: z.string().default("{title} tab."),
178
+ cueTip: z.string().default("Tip."),
179
+ cueWarning: z.string().default("Warning."),
180
+ error: z.string().default("The narration couldn't load."),
181
+ // Resumes following the sentence being read after the reader scrolls away.
182
+ follow: z.string().default("Follow along"),
183
+ label: z.string().default("Listen to this page"),
184
+ // The estimated listening time; `{n}` is whole minutes.
185
+ minutes: z.string().default("{n} min"),
186
+ next: z.string().default("Next sentence"),
187
+ pause: z.string().default("Pause"),
188
+ play: z.string().default("Play"),
189
+ previous: z.string().default("Previous sentence"),
190
+ progress: z.string().default("Narration progress"),
191
+ speed: z.string().default("Playback speed"),
192
+ stop: z.string().default("Stop listening"),
193
+ })
194
+ .prefault({}),
117
195
  nav: z
118
196
  .object({
119
197
  back: z.string().default("Back"),
@@ -128,7 +206,7 @@ const uiStringsObject = z.object({
128
206
  primary: z.string().default("Primary"),
129
207
  sections: z.string().default("Sections"),
130
208
  toggleNavigation: z.string().default("Toggle navigation"),
131
- toggleTheme: z.string().default("Toggle color theme"),
209
+ toggleTheme: z.string().default("Toggle theme"),
132
210
  })
133
211
  .prefault({}),
134
212
  notFound: z
@@ -154,6 +232,8 @@ const uiStringsObject = z.object({
154
232
  next: z.string().default("Next"),
155
233
  pagination: z.string().default("Pagination"),
156
234
  previous: z.string().default("Previous"),
235
+ // The heading over a page's `related` links.
236
+ related: z.string().default("Related pages"),
157
237
  skipToContent: z.string().default("Skip to content"),
158
238
  })
159
239
  .prefault({}),
@@ -7,6 +7,7 @@ import type { FenceState } from "./code-fences.ts";
7
7
  import matter from "./frontmatter.ts";
8
8
  import { INLINE_CODE, MD_IMAGE, targetOffsetIn } from "./sources/normalize.ts";
9
9
  import type { Diagnostic } from "./types.ts";
10
+ import { substituteVariables } from "./variables.ts";
10
11
 
11
12
  /**
12
13
  * Content includes: `<include>./relative.mdx</include>` on a line of its own
@@ -98,6 +99,12 @@ export interface IncludeAttributes {
98
99
  lang?: string;
99
100
  /** Fence meta string in code-block mode (e.g. `title="lib.ts"`). */
100
101
  meta?: string;
102
+ /**
103
+ * Every other attribute with a value: the snippet's own variables.
104
+ * `<include plan="Pro">` makes `{{plan}}` in the included file read `Pro`,
105
+ * ahead of any site-wide variable of the same name.
106
+ */
107
+ props?: Record<string, string>;
101
108
  }
102
109
 
103
110
  /** A parsed include statement. */
@@ -108,8 +115,8 @@ export interface IncludeStatement {
108
115
 
109
116
  /**
110
117
  * Parse one trimmed line as an include statement, or `null` when it isn't
111
- * one. Only `lang` and `meta` are meaningful; unknown attributes parse and
112
- * are ignored so a future attribute degrades gracefully on older versions.
118
+ * one. `lang` and `meta` shape a code include; every other attribute with a
119
+ * value becomes a prop, a variable scoped to the included file.
113
120
  */
114
121
  export const parseIncludeStatement = (
115
122
  line: string
@@ -128,6 +135,9 @@ export const parseIncludeStatement = (
128
135
  if (name === "meta" && value) {
129
136
  attributes.meta = value;
130
137
  }
138
+ if (name && name !== "lang" && name !== "meta" && value !== undefined) {
139
+ attributes.props = { ...attributes.props, [name]: value };
140
+ }
131
141
  }
132
142
  return { attributes, target: match.groups.target ?? "" };
133
143
  };
@@ -519,8 +529,15 @@ const expandStatement = async (
519
529
  lines.shift();
520
530
  origins.shift();
521
531
  }
532
+ // Props replace their `{{name}}` references line by line, so every line
533
+ // keeps its origin; they reach the file's own nested includes too.
534
+ const { props } = statement.attributes;
522
535
  return {
523
- lines: rebaseImages(lines, dirname(path), dirname(filePath)),
536
+ lines: rebaseImages(
537
+ props ? lines.map((text) => substituteVariables(text, props)) : lines,
538
+ dirname(path),
539
+ dirname(filePath)
540
+ ),
524
541
  origins,
525
542
  };
526
543
  };
package/src/core/links.ts CHANGED
@@ -10,6 +10,7 @@ import {
10
10
  import type { LocaleRouting } from "./i18n.ts";
11
11
  import { localizeLinkPath } from "./locale-links.ts";
12
12
  import { gradeExternal, probeAll } from "./probe.ts";
13
+ import { isPatternPath, pathsUnderPattern } from "./redirect-patterns.ts";
13
14
  import type {
14
15
  ContentGraph,
15
16
  Diagnostic,
@@ -87,6 +88,8 @@ interface LinkContext {
87
88
  publicDir: string | null;
88
89
  /** Normalized `redirect.from` paths — valid targets that resolve at runtime. */
89
90
  redirects: Set<string>;
91
+ /** Pattern `redirect.from`s (`/beta/:slug*`), based like `redirects`. */
92
+ redirectPatterns: string[];
90
93
  routes: Set<string>;
91
94
  }
92
95
 
@@ -322,10 +325,16 @@ const checkPathLink = (
322
325
  if (ctx.extraRoutes.has(route)) {
323
326
  return null;
324
327
  }
325
- // A configured `redirect.from` resolves at runtime, so it's a valid target.
328
+ // A configured `redirect.from`, or a path a pattern `from` covers, resolves
329
+ // at runtime, so it's a valid target.
326
330
  // Its destination (and any anchor there) is validated on its own page, so we
327
331
  // don't follow the redirect to check the fragment here.
328
- if (ctx.redirects.has(route)) {
332
+ if (
333
+ ctx.redirects.has(route) ||
334
+ ctx.redirectPatterns.some(
335
+ (from) => pathsUnderPattern(from, [route]).length > 0
336
+ )
337
+ ) {
329
338
  return null;
330
339
  }
331
340
 
@@ -585,6 +594,11 @@ export const validateLinks = async (
585
594
  fileRoutes: buildFileRouteIndex(graph.pages, options.i18n ?? null),
586
595
  i18n: options.i18n ?? null,
587
596
  publicDir: options.publicDir,
597
+ redirectPatterns: (options.redirects ?? []).flatMap((redirect) =>
598
+ isPatternPath(redirect.from)
599
+ ? [withBasePath(basePath, redirect.from)]
600
+ : []
601
+ ),
588
602
  redirects: new Set(
589
603
  (options.redirects ?? []).map((redirect) =>
590
604
  toRoute(withBasePath(basePath, redirect.from))
@@ -0,0 +1,26 @@
1
+ import type { LocalizableLabel } from "./schema.ts";
2
+
3
+ /** A label is either one string for every locale or a per-locale map. */
4
+ const isSingleLabel = (label: LocalizableLabel): label is string =>
5
+ typeof label === "string";
6
+
7
+ /**
8
+ * Resolve a possibly-per-locale label to the string a locale renders: the
9
+ * active locale's entry, else the default locale's, else the map's first
10
+ * entry (which is also what a single-locale site gets).
11
+ */
12
+ export const resolveLocalizable = (
13
+ label: LocalizableLabel,
14
+ locale?: string,
15
+ defaultLocale?: string
16
+ ): string => {
17
+ if (isSingleLabel(label)) {
18
+ return label;
19
+ }
20
+ return (
21
+ (locale === undefined ? undefined : label[locale]) ??
22
+ (defaultLocale === undefined ? undefined : label[defaultLocale]) ??
23
+ Object.values(label)[0] ??
24
+ ""
25
+ );
26
+ };
@@ -3,6 +3,7 @@ import { extname } from "pathe";
3
3
  import { stripBasePath, withBasePath } from "./base-path.ts";
4
4
  import { orderingPrefix, stripOrderingPrefix } from "./ordering-prefix.ts";
5
5
  import type {
6
+ DirectoryMode,
6
7
  FolderMeta,
7
8
  SidebarDisplay,
8
9
  SidebarItemConfig,
@@ -202,6 +203,7 @@ interface MutableGroup {
202
203
  label: string;
203
204
  icon?: string;
204
205
  collapsed?: boolean;
206
+ directory?: DirectoryMode;
205
207
  display?: SidebarDisplay;
206
208
  order: number;
207
209
  /** Whether `order` is the node's position in its folder meta `pages` list. */
@@ -305,9 +307,19 @@ interface FallbackFolderMeta {
305
307
  meta: (path: string) => FolderMeta | undefined;
306
308
  }
307
309
 
310
+ /** A folder meta's own fields onto its group, over what the folder implied. */
311
+ const applyMetaFields = (group: MutableGroup, meta: FolderMeta): void => {
312
+ group.label = meta.title ?? group.label;
313
+ group.icon = meta.icon ?? group.icon;
314
+ group.order = meta.order ?? group.order;
315
+ group.collapsed = meta.collapsed ?? group.collapsed;
316
+ group.directory = meta.directory ?? group.directory;
317
+ };
318
+
308
319
  /**
309
- * Apply folder meta (title/order/icon/collapsed/display and explicit page
310
- * order), plus the index-frontmatter display sugar collected per folder path.
320
+ * Apply folder meta (title/order/icon/collapsed/display/directory and
321
+ * explicit page order), plus the index-frontmatter display sugar collected
322
+ * per folder path.
311
323
  */
312
324
  const applyFolderMeta = (
313
325
  group: MutableGroup,
@@ -346,10 +358,7 @@ const applyFolderMeta = (
346
358
  (mirrored ? fallback.indexDisplay.get(group.path) : undefined) ??
347
359
  meta?.display;
348
360
  if (meta) {
349
- group.label = meta.title ?? group.label;
350
- group.icon = meta.icon ?? group.icon;
351
- group.order = meta.order ?? group.order;
352
- group.collapsed = meta.collapsed ?? group.collapsed;
361
+ applyMetaFields(group, meta);
353
362
  }
354
363
 
355
364
  for (const child of group.children) {
@@ -630,7 +639,15 @@ const hoistTabSections = (
630
639
  }
631
640
  };
632
641
 
633
- const toNavNode = (node: MutableNode, display: SidebarDisplay): NavNode => {
642
+ /**
643
+ * A group's directory mode: its own, else the nearest one set above it (see
644
+ * `directory` in `schema.ts`).
645
+ */
646
+ const toNavNode = (
647
+ node: MutableNode,
648
+ display: SidebarDisplay,
649
+ inheritedDirectory?: DirectoryMode
650
+ ): NavNode => {
634
651
  if (node.kind === "page") {
635
652
  return {
636
653
  badge: node.badge,
@@ -643,9 +660,13 @@ const toNavNode = (node: MutableNode, display: SidebarDisplay): NavNode => {
643
660
  route: node.route,
644
661
  };
645
662
  }
663
+ const directory = node.directory ?? inheritedDirectory;
646
664
  return {
647
- children: node.children.map((child) => toNavNode(child, display)),
665
+ children: node.children.map((child) =>
666
+ toNavNode(child, display, directory)
667
+ ),
648
668
  collapsed: node.collapsed,
669
+ directory,
649
670
  display: node.display ?? display,
650
671
  icon: node.icon,
651
672
  kind: "group",
@@ -805,7 +826,9 @@ const buildFileSystemSidebar = (
805
826
  sortNodes(root.children, diagnostics);
806
827
  hoistPages(root.children, display, true);
807
828
  hoistTabSections(root.children, tabPaths, display);
808
- return root.children.map((child) => toNavNode(child, display));
829
+ return root.children.map((child) =>
830
+ toNavNode(child, display, root.directory)
831
+ );
809
832
  };
810
833
 
811
834
  /**
@@ -932,16 +955,24 @@ const buildConfigSidebar = (
932
955
  items: SidebarItemConfig[],
933
956
  byRoute: Map<string, PageRecord>,
934
957
  display: SidebarDisplay,
935
- basePath: string
958
+ basePath: string,
959
+ inheritedDirectory?: DirectoryMode
936
960
  ): NavNode[] => {
937
961
  const nodes: NavNode[] = [];
938
962
  for (const item of items) {
939
963
  if (!isPageRef(item) && item.items) {
964
+ const directory = item.directory ?? inheritedDirectory;
940
965
  nodes.push({
941
966
  badge: item.badge,
942
- children: buildConfigSidebar(item.items, byRoute, display, basePath),
967
+ children: buildConfigSidebar(
968
+ item.items,
969
+ byRoute,
970
+ display,
971
+ basePath,
972
+ directory
973
+ ),
943
974
  collapsed: item.collapsed,
944
- directory: item.directory,
975
+ directory,
945
976
  display: item.display ?? display,
946
977
  icon: item.icon,
947
978
  kind: "group",
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Page layout modes (`mode` frontmatter), Mintlify's names for what a page
3
+ * shows around its content:
4
+ *
5
+ * - `default`: the sidebar, the page's title and table of contents, and the
6
+ * page-end links, around the prose column.
7
+ * - `wide`: no table of contents; the content takes its width.
8
+ * - `center`: no sidebar or table of contents; a wider column, centered.
9
+ * For changelogs and other pages read top to bottom.
10
+ * - `custom`: only the header. No sidebar, title, breadcrumbs, table of
11
+ * contents, page-end links, or site footer, and the content spans the
12
+ * page, for a landing page written in MDX.
13
+ * - `frame`: `custom`, keeping the sidebar.
14
+ *
15
+ * Mintlify's `assistant` mode, a full-page chat, has no equivalent.
16
+ */
17
+ export const PAGE_MODES = [
18
+ "default",
19
+ "wide",
20
+ "center",
21
+ "custom",
22
+ "frame",
23
+ ] as const;
24
+
25
+ export type PageMode = (typeof PAGE_MODES)[number];
26
+
27
+ /** What a page shows around its content, and how wide the content runs. */
28
+ export interface PageModeLayout {
29
+ /** The page's own chrome: title, description, breadcrumbs, page-end links, and the site footer. */
30
+ chrome: boolean;
31
+ sidebar: boolean;
32
+ toc: boolean;
33
+ /** `content` is the prose measure, `center` a wider centered column, `full` the whole column. */
34
+ width: "content" | "center" | "full";
35
+ }
36
+
37
+ /** The layout a mode gives a page. */
38
+ export const pageModeLayout = (mode: PageMode = "default"): PageModeLayout => {
39
+ switch (mode) {
40
+ case "wide": {
41
+ return { chrome: true, sidebar: true, toc: false, width: "full" };
42
+ }
43
+ case "center": {
44
+ return { chrome: true, sidebar: false, toc: false, width: "center" };
45
+ }
46
+ case "custom": {
47
+ return { chrome: false, sidebar: false, toc: false, width: "full" };
48
+ }
49
+ case "frame": {
50
+ return { chrome: false, sidebar: true, toc: false, width: "full" };
51
+ }
52
+ default: {
53
+ return { chrome: true, sidebar: true, toc: true, width: "content" };
54
+ }
55
+ }
56
+ };
@@ -1,5 +1,6 @@
1
1
  import { isAbsolute, relative } from "pathe";
2
2
 
3
+ import { withBasePath } from "./base-path.ts";
3
4
  import { CHANGELOG_INDEX_ROUTE, hasChangelogIndex } from "./changelog-index.ts";
4
5
  import { loadConfig } from "./config.ts";
5
6
  import { customStaticRoutes, discoverPages } from "./custom-pages.ts";
@@ -18,10 +19,15 @@ import { buildManifest } from "./manifest.ts";
18
19
  import { discoverFolderMeta } from "./meta.ts";
19
20
  import type { FolderMetaSource } from "./meta.ts";
20
21
  import { resolveProjectContext } from "./project.ts";
22
+ import { pathsUnderPattern } from "./redirect-patterns.ts";
21
23
  import type { ResolvedConfig } from "./schema.ts";
22
24
  import { normalizeEntry, strippedLineOffset } from "./sources/normalize.ts";
23
25
  import { resolveDocsCollection, resolveSources } from "./sources/resolve.ts";
24
- import type { ContentSource, SourceLoadResult } from "./sources/types.ts";
26
+ import type {
27
+ ContentSource,
28
+ SourceEntry,
29
+ SourceLoadResult,
30
+ } from "./sources/types.ts";
25
31
  import type {
26
32
  BlumeManifest,
27
33
  ContentGraph,
@@ -30,6 +36,12 @@ import type {
30
36
  PageRecord,
31
37
  ProjectContext,
32
38
  } from "./types.ts";
39
+ import {
40
+ hasVariables,
41
+ substituteVariables,
42
+ undefinedVariables,
43
+ } from "./variables.ts";
44
+ import type { ContentVariables } from "./variables.ts";
33
45
  import { versionsDiagnostics } from "./versions.ts";
34
46
 
35
47
  /** Build mode: drafts are kept in `dev` and dropped in `build`. */
@@ -246,6 +258,85 @@ const isBannerShorthand = (
246
258
  banner: NonNullable<ResolvedConfig["banner"]>
247
259
  ): banner is string => typeof banner === "string";
248
260
 
261
+ /**
262
+ * Report an entry's undefined content variables at their source lines, then
263
+ * replace the defined ones in the text the scan extracts from: the expanded
264
+ * text when includes were spliced (its `origins` map lines back to the file
265
+ * each came from), else the body.
266
+ */
267
+ const substituteEntryVariables = (
268
+ entry: SourceEntry,
269
+ variables: ContentVariables
270
+ ): Diagnostic[] => {
271
+ const { expanded } = entry;
272
+ const text = expanded?.text ?? entry.body.text;
273
+ const offset = expanded ? 0 : strippedLineOffset(entry.raw, entry.body.text);
274
+ const diagnostics = undefinedVariables(text, variables).map(
275
+ ({ line, name }): Diagnostic => {
276
+ const origin = expanded?.origins[line - 1];
277
+ return {
278
+ code: "BLUME_UNDEFINED_VARIABLE",
279
+ file: origin?.file ?? entry.sourcePath,
280
+ line: origin?.line ?? line + offset,
281
+ message: `{{${name}}} isn't a defined variable, so it would show as written.`,
282
+ severity: "error",
283
+ suggestion: `Define "${name}" under variables in blume.config.ts, or put it in inline code to show it as written.`,
284
+ };
285
+ }
286
+ );
287
+ if (expanded) {
288
+ expanded.text = substituteVariables(expanded.text, variables);
289
+ } else {
290
+ entry.body.text = substituteVariables(entry.body.text, variables);
291
+ }
292
+ return diagnostics;
293
+ };
294
+
295
+ /** {@link substituteEntryVariables} over every loaded entry, when any are defined. */
296
+ const substituteLoadedVariables = (
297
+ loaded: readonly SourceLoadResult[],
298
+ variables: ContentVariables
299
+ ): Diagnostic[] =>
300
+ hasVariables(variables)
301
+ ? loaded.flatMap(({ entries }) =>
302
+ entries.flatMap((entry) => substituteEntryVariables(entry, variables))
303
+ )
304
+ : [];
305
+
306
+ /**
307
+ * A pattern redirect that also matches a page. Hosts disagree on which
308
+ * answers (Vercel and Cloudflare apply the rule ahead of the page, Netlify
309
+ * and the Node server serve the page), so the pattern is rejected rather than
310
+ * let the answer depend on the host. `pages` are the served page paths, which
311
+ * carry `basePath`, as the based `from` does.
312
+ */
313
+ const redirectPageDiagnostics = (
314
+ config: ResolvedConfig,
315
+ pages: readonly string[],
316
+ configFile: string | null
317
+ ): Diagnostic[] =>
318
+ config.redirects.flatMap((redirect) => {
319
+ const matched = pathsUnderPattern(
320
+ withBasePath(config.basePath, redirect.from),
321
+ pages
322
+ );
323
+ const [first] = matched;
324
+ if (first === undefined) {
325
+ return [];
326
+ }
327
+ const more = matched.length > 1 ? ` and ${matched.length - 1} more` : "";
328
+ return [
329
+ {
330
+ code: "BLUME_REDIRECT_MATCHES_PAGE",
331
+ file: configFile ?? undefined,
332
+ message: `The redirect from ${redirect.from} also matches the page ${first}${more}, which some hosts would redirect and others would serve.`,
333
+ severity: "error",
334
+ suggestion:
335
+ "Narrow the pattern to the paths that moved, or move the pages out from under it.",
336
+ } satisfies Diagnostic,
337
+ ];
338
+ });
339
+
249
340
  /** The configured banner link target, when the banner has a link. */
250
341
  const bannerLinkHref = (
251
342
  banner: ResolvedConfig["banner"]
@@ -352,6 +443,14 @@ export const scanProject = async (
352
443
  })
353
444
  );
354
445
 
446
+ // Content variables: an undefined name in prose is an error at its source
447
+ // line, and every defined one is replaced in the text headings, links, and
448
+ // components are extracted from, so they match the rendered page.
449
+ const variableDiagnostics = substituteLoadedVariables(
450
+ loaded,
451
+ config.variables
452
+ );
453
+
355
454
  // Folder meta contributed by the sources themselves (the OpenAPI source
356
455
  // labels each tag directory with the spec's own tag name). It applies to
357
456
  // every locale, so it merges into the shared map — beneath user-authored
@@ -424,14 +523,15 @@ export const scanProject = async (
424
523
  const customPages = context.pagesRoot
425
524
  ? await discoverPages(context.pagesRoot)
426
525
  : [];
526
+ const extraRoutes = new Set([
527
+ ...customStaticRoutes(customPages),
528
+ ...(hasChangelogIndex(pages, config) ? [CHANGELOG_INDEX_ROUTE] : []),
529
+ ]);
427
530
  const graph = buildContentGraph(pages, {
428
531
  bannerHref: bannerLinkHref(config.banner),
429
532
  basePath: config.basePath,
430
533
  brandHref: logoHref(config.logo),
431
- extraRoutes: new Set([
432
- ...customStaticRoutes(customPages),
433
- ...(hasChangelogIndex(pages, config) ? [CHANGELOG_INDEX_ROUTE] : []),
434
- ]),
534
+ extraRoutes,
435
535
  folderMeta: folderMeta.meta,
436
536
  i18n: config.i18n,
437
537
  navigation: config.navigation,
@@ -453,6 +553,7 @@ export const scanProject = async (
453
553
  diagnostics: [
454
554
  ...contentDiagnostics,
455
555
  ...includeDiagnostics,
556
+ ...variableDiagnostics,
456
557
  ...folderMeta.diagnostics,
457
558
  ...entryIdDiagnostics(
458
559
  pages,
@@ -462,6 +563,11 @@ export const scanProject = async (
462
563
  ...i18nWarnings,
463
564
  ...versionWarnings,
464
565
  ...lastModifiedWarnings,
566
+ ...redirectPageDiagnostics(
567
+ config,
568
+ [...manifest.routes.map((route) => route.path), ...extraRoutes],
569
+ context.configFile
570
+ ),
465
571
  ],
466
572
  droppedPages,
467
573
  graph,