blume 2.0.2 → 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 (601) hide show
  1. package/AGENTS.md +1 -0
  2. package/CHANGELOG.md +113 -0
  3. package/README.md +1 -1
  4. package/dist/cli/{chunk-6k4ftwze.js → chunk-0gz36akr.js} +18 -2
  5. package/dist/cli/{chunk-6k4ftwze.js.map → chunk-0gz36akr.js.map} +3 -3
  6. package/dist/cli/{chunk-zp79m0ts.js → chunk-1n228xqb.js} +2 -2
  7. package/dist/cli/{chunk-ddndchfr.js → chunk-2f3ndkpw.js} +57 -29
  8. package/dist/cli/chunk-2f3ndkpw.js.map +14 -0
  9. package/dist/cli/{chunk-fsmrqk8a.js → chunk-2f7n4qzt.js} +8 -2
  10. package/dist/cli/{chunk-fsmrqk8a.js.map → chunk-2f7n4qzt.js.map} +3 -3
  11. package/dist/cli/{chunk-ppzjqwx2.js → chunk-2n4ps1b3.js} +17 -18
  12. package/dist/cli/{chunk-ppzjqwx2.js.map → chunk-2n4ps1b3.js.map} +3 -3
  13. package/dist/cli/{chunk-5r8g91qn.js → chunk-43zcf4gz.js} +398 -159
  14. package/dist/cli/chunk-43zcf4gz.js.map +36 -0
  15. package/dist/cli/chunk-4ejxbzee.js +39 -0
  16. package/dist/cli/chunk-4ejxbzee.js.map +10 -0
  17. package/dist/cli/{chunk-hr8ne106.js → chunk-4h8z9vv5.js} +296 -77
  18. package/dist/cli/chunk-4h8z9vv5.js.map +16 -0
  19. package/dist/cli/{chunk-k7pj68a8.js → chunk-5cg2rzgf.js} +18 -16
  20. package/dist/cli/{chunk-k7pj68a8.js.map → chunk-5cg2rzgf.js.map} +2 -2
  21. package/dist/cli/{chunk-h2ez8dzb.js → chunk-5ffywsmb.js} +4 -4
  22. package/dist/cli/{chunk-g698a744.js → chunk-5wj2qwn2.js} +5 -5
  23. package/dist/cli/chunk-6bm0xpgd.js +18 -0
  24. package/dist/cli/chunk-6bm0xpgd.js.map +10 -0
  25. package/dist/cli/{chunk-3yce002v.js → chunk-713mngdq.js} +15 -8
  26. package/dist/cli/chunk-713mngdq.js.map +14 -0
  27. package/dist/cli/{chunk-91ws1n6j.js → chunk-9geymqns.js} +17 -15
  28. package/dist/cli/{chunk-91ws1n6j.js.map → chunk-9geymqns.js.map} +2 -2
  29. package/dist/cli/{chunk-6dtt0zfn.js → chunk-9xr4dgee.js} +27 -18
  30. package/dist/cli/chunk-9xr4dgee.js.map +10 -0
  31. package/dist/cli/{chunk-sqw4ekg1.js → chunk-amrb6hgh.js} +2 -2
  32. package/dist/cli/chunk-cm89scdv.js +443 -0
  33. package/dist/cli/chunk-cm89scdv.js.map +11 -0
  34. package/dist/cli/{chunk-wdrt2k2v.js → chunk-cmtcnh9f.js} +90 -26
  35. package/dist/cli/chunk-cmtcnh9f.js.map +10 -0
  36. package/dist/cli/chunk-cy5bm0j0.js +104 -0
  37. package/dist/cli/chunk-cy5bm0j0.js.map +11 -0
  38. package/dist/cli/{chunk-7mbqtmgb.js → chunk-e7vvk5ya.js} +21 -12
  39. package/dist/cli/chunk-e7vvk5ya.js.map +10 -0
  40. package/dist/cli/{chunk-qkb5a8sa.js → chunk-g2z9x01v.js} +6 -98
  41. package/dist/cli/chunk-g2z9x01v.js.map +10 -0
  42. package/dist/cli/{chunk-273ygyr4.js → chunk-ga0pf4aj.js} +7 -7
  43. package/dist/cli/{chunk-273ygyr4.js.map → chunk-ga0pf4aj.js.map} +3 -3
  44. package/dist/cli/{chunk-h7k3nq3v.js → chunk-gp84za0h.js} +2 -2
  45. package/dist/cli/{chunk-hqp2ajnh.js → chunk-h2a48yhd.js} +195 -73
  46. package/dist/cli/chunk-h2a48yhd.js.map +36 -0
  47. package/dist/cli/{chunk-pbg5a4s3.js → chunk-jnwj5kjj.js} +71 -47
  48. package/dist/cli/chunk-jnwj5kjj.js.map +19 -0
  49. package/dist/cli/chunk-kwrg63tm.js +31 -0
  50. package/dist/cli/chunk-kwrg63tm.js.map +10 -0
  51. package/dist/cli/{chunk-8g8ytmgx.js → chunk-nd1821sk.js} +21 -19
  52. package/dist/cli/{chunk-8g8ytmgx.js.map → chunk-nd1821sk.js.map} +2 -2
  53. package/dist/cli/{chunk-w4bxdvsa.js → chunk-ntv4qrt7.js} +17 -15
  54. package/dist/cli/{chunk-w4bxdvsa.js.map → chunk-ntv4qrt7.js.map} +2 -2
  55. package/dist/cli/{chunk-bfwp9vp6.js → chunk-p804f3pe.js} +7 -7
  56. package/dist/cli/{chunk-4e9b9ra6.js → chunk-py62fewh.js} +6 -6
  57. package/dist/cli/{chunk-4e9b9ra6.js.map → chunk-py62fewh.js.map} +1 -1
  58. package/dist/cli/chunk-q3wxxq39.js +126 -0
  59. package/dist/cli/chunk-q3wxxq39.js.map +11 -0
  60. package/dist/cli/{chunk-j85scx15.js → chunk-rekqz4qg.js} +142 -46
  61. package/dist/cli/chunk-rekqz4qg.js.map +10 -0
  62. package/dist/cli/{chunk-00gs3wqs.js → chunk-ta27feev.js} +1 -1
  63. package/dist/cli/{chunk-v6ya5kcb.js → chunk-xkz5xbtv.js} +4031 -1132
  64. package/dist/cli/chunk-xkz5xbtv.js.map +213 -0
  65. package/dist/cli/{chunk-bbnwccaz.js → chunk-y4ykbark.js} +2 -2
  66. package/dist/cli/{chunk-n1yg3tj3.js → chunk-y5h4r2y0.js} +6 -6
  67. package/dist/cli/{chunk-n1yg3tj3.js.map → chunk-y5h4r2y0.js.map} +3 -3
  68. package/dist/cli/{chunk-nfcyttvj.js → chunk-yj17vs39.js} +9 -9
  69. package/dist/cli/{chunk-nfcyttvj.js.map → chunk-yj17vs39.js.map} +1 -1
  70. package/dist/cli/{chunk-7vtckvaw.js → chunk-yxmk4pec.js} +16 -14
  71. package/dist/cli/{chunk-7vtckvaw.js.map → chunk-yxmk4pec.js.map} +2 -2
  72. package/dist/cli/{chunk-gs7r695n.js → chunk-zccbjcj0.js} +22 -8
  73. package/dist/cli/{chunk-gs7r695n.js.map → chunk-zccbjcj0.js.map} +3 -3
  74. package/dist/cli/{chunk-xh43dwgw.js → chunk-zf748avv.js} +415 -225
  75. package/dist/cli/chunk-zf748avv.js.map +37 -0
  76. package/dist/cli/{chunk-d1v5rhy0.js → chunk-zhvd6adm.js} +16 -14
  77. package/dist/cli/{chunk-d1v5rhy0.js.map → chunk-zhvd6adm.js.map} +2 -2
  78. package/dist/cli/index.js +28 -20
  79. package/dist/cli/index.js.map +5 -5
  80. package/dist/types/ai/agent-surface.d.ts +8 -7
  81. package/dist/types/ai/ai-catalog.d.ts +7 -0
  82. package/dist/types/ai/ask-context.d.ts +22 -0
  83. package/dist/types/ai/ask.d.ts +271 -58
  84. package/dist/types/ai/index.d.ts +2 -2
  85. package/dist/types/ai/link-headers.d.ts +8 -1
  86. package/dist/types/ai/openapi-components.d.ts +5 -2
  87. package/dist/types/ai/relative-links.d.ts +9 -7
  88. package/dist/types/ai/site-skill.d.ts +26 -0
  89. package/dist/types/ai/skills.d.ts +20 -1
  90. package/dist/types/ai/tar.d.ts +1 -3
  91. package/dist/types/analytics/index.d.ts +2 -0
  92. package/dist/types/analytics/one-dollar-stats.d.ts +48 -0
  93. package/dist/types/analytics/schema.d.ts +14 -0
  94. package/dist/types/astro/integration.d.ts +11 -2
  95. package/dist/types/captcha/hcaptcha.d.ts +27 -0
  96. package/dist/types/captcha/index.d.ts +28 -0
  97. package/dist/types/captcha/schema.d.ts +29 -0
  98. package/dist/types/captcha/turnstile.d.ts +28 -0
  99. package/dist/types/components/content/api-field.d.ts +59 -0
  100. package/dist/types/components/content/api-page.d.ts +95 -0
  101. package/dist/types/components/layout/nav-utils.d.ts +26 -0
  102. package/dist/types/components/openapi/helpers.d.ts +137 -0
  103. package/dist/types/components/openapi/security.d.ts +85 -0
  104. package/dist/types/consent/ethyca.d.ts +48 -0
  105. package/dist/types/consent/index.d.ts +37 -0
  106. package/dist/types/consent/native.d.ts +28 -0
  107. package/dist/types/consent/osano.d.ts +42 -0
  108. package/dist/types/consent/schema.d.ts +110 -0
  109. package/dist/types/core/base-path.d.ts +21 -7
  110. package/dist/types/core/config-input.d.ts +196 -27
  111. package/dist/types/core/data.d.ts +49 -4
  112. package/dist/types/core/directive-diagnostics.d.ts +12 -0
  113. package/dist/types/core/footer.d.ts +8 -0
  114. package/dist/types/core/heading-markers.d.ts +5 -7
  115. package/dist/types/core/i18n-ui.d.ts +86 -0
  116. package/dist/types/core/i18n.d.ts +2 -0
  117. package/dist/types/core/includes.d.ts +8 -2
  118. package/dist/types/core/last-modified.d.ts +10 -0
  119. package/dist/types/core/localizable.d.ts +7 -0
  120. package/dist/types/core/meta.d.ts +8 -0
  121. package/dist/types/core/page-modes.d.ts +29 -0
  122. package/dist/types/core/redirect-patterns.d.ts +144 -0
  123. package/dist/types/core/related.d.ts +40 -0
  124. package/dist/types/core/schema.d.ts +558 -56
  125. package/dist/types/core/sources/lower.d.ts +29 -16
  126. package/dist/types/core/sources/normalize.d.ts +13 -1
  127. package/dist/types/core/sources/read.d.ts +5 -0
  128. package/dist/types/core/sources/watch.d.ts +12 -5
  129. package/dist/types/core/standard-schema.d.ts +5 -0
  130. package/dist/types/core/types.d.ts +1 -1
  131. package/dist/types/core/variables.d.ts +42 -0
  132. package/dist/types/deploy/artifacts.d.ts +6 -4
  133. package/dist/types/deploy/headers.d.ts +5 -0
  134. package/dist/types/deploy/node-headers.d.ts +13 -2
  135. package/dist/types/deploy/platforms/cloudflare.d.ts +5 -0
  136. package/dist/types/deploy/platforms/netlify.d.ts +7 -0
  137. package/dist/types/deploy/platforms/types.d.ts +8 -0
  138. package/dist/types/deploy/platforms/vercel.d.ts +6 -0
  139. package/dist/types/deploy/redirects.d.ts +61 -22
  140. package/dist/types/deploy/vercel-negotiation.d.ts +9 -0
  141. package/dist/types/markdown/code-title.d.ts +17 -0
  142. package/dist/types/markdown/directives.d.ts +62 -0
  143. package/dist/types/markdown/features.d.ts +24 -2
  144. package/dist/types/markdown/mdast.d.ts +63 -0
  145. package/dist/types/narration/build.d.ts +70 -0
  146. package/dist/types/narration/provider.d.ts +26 -0
  147. package/dist/types/narration/script.d.ts +154 -0
  148. package/dist/types/openapi/asyncapi.d.ts +4 -2
  149. package/dist/types/openapi/model.d.ts +12 -6
  150. package/dist/types/openapi/overlay.d.ts +54 -0
  151. package/dist/types/openapi/parse.d.ts +13 -0
  152. package/dist/types/openapi/references.d.ts +14 -4
  153. package/dist/types/ratelimit/cloudflare.d.ts +39 -0
  154. package/dist/types/ratelimit/index.d.ts +27 -0
  155. package/dist/types/ratelimit/memory.d.ts +36 -0
  156. package/dist/types/ratelimit/schema.d.ts +111 -0
  157. package/dist/types/ratelimit/upstash.d.ts +21 -0
  158. package/dist/types/ratelimit/wrangler.d.ts +23 -0
  159. package/dist/types/reference/asyncapi.d.ts +10 -10
  160. package/dist/types/reference/graphql.d.ts +13 -10
  161. package/dist/types/reference/openapi.d.ts +33 -12
  162. package/dist/types/reference/options.d.ts +45 -5
  163. package/dist/types/reference/scalar.d.ts +13 -0
  164. package/dist/types/reference/schema.d.ts +60 -36
  165. package/dist/types/search/documents.d.ts +12 -1
  166. package/dist/types/search/orama-index.d.ts +4 -0
  167. package/dist/types/seo/metatags.d.ts +20 -0
  168. package/docs/02-deployment.mdx +44 -6
  169. package/docs/03-upgrading.mdx +4 -4
  170. package/docs/04-migrating.mdx +1 -1
  171. package/docs/08-faq.mdx +1 -1
  172. package/docs/advanced/changelog.mdx +2 -2
  173. package/docs/advanced/custom-pages.mdx +3 -3
  174. package/docs/advanced/skills.mdx +14 -0
  175. package/docs/cli/audit.mdx +38 -2
  176. package/docs/cli/evals.mdx +3 -3
  177. package/docs/cli/index.mdx +1 -0
  178. package/docs/cli/translate.mdx +3 -3
  179. package/docs/cli/version.mdx +1 -1
  180. package/docs/configuration/analytics.mdx +22 -1
  181. package/docs/configuration/assistant.mdx +188 -36
  182. package/docs/configuration/consent.mdx +100 -0
  183. package/docs/configuration/customization.mdx +6 -4
  184. package/docs/configuration/index.mdx +63 -5
  185. package/docs/configuration/meta.ts +3 -0
  186. package/docs/configuration/narration.mdx +119 -0
  187. package/docs/configuration/rate-limiting.mdx +69 -0
  188. package/docs/configuration/search.mdx +30 -2
  189. package/docs/configuration/theming.mdx +3 -1
  190. package/docs/content/components.mdx +107 -1
  191. package/docs/content/frontmatter.mdx +77 -1
  192. package/docs/content/i18n.mdx +40 -3
  193. package/docs/content/includes.mdx +16 -0
  194. package/docs/content/index.mdx +4 -2
  195. package/docs/content/islands.mdx +1 -1
  196. package/docs/content/meta.mdx +4 -1
  197. package/docs/content/meta.ts +1 -0
  198. package/docs/content/navigation.mdx +28 -6
  199. package/docs/content/sources/contentful.mdx +32 -0
  200. package/docs/content/sources/custom.mdx +39 -0
  201. package/docs/content/sources/github-releases.mdx +31 -0
  202. package/docs/content/sources/index.mdx +91 -0
  203. package/docs/content/sources/meta.ts +16 -0
  204. package/docs/content/sources/notion.mdx +32 -0
  205. package/docs/content/sources/obsidian.mdx +37 -0
  206. package/docs/content/sources/payload.mdx +30 -0
  207. package/docs/content/sources/remote-mdx.mdx +40 -0
  208. package/docs/content/sources/sanity.mdx +29 -0
  209. package/docs/content/sources/strapi.mdx +31 -0
  210. package/docs/content/syntax.mdx +81 -2
  211. package/docs/content/variables.mdx +41 -0
  212. package/docs/content/versioning.mdx +1 -1
  213. package/docs/discoverability/agent-discovery.mdx +30 -3
  214. package/docs/discoverability/index.mdx +3 -2
  215. package/docs/discoverability/llms-txt.mdx +1 -1
  216. package/docs/discoverability/markdown.mdx +2 -2
  217. package/docs/discoverability/metadata.mdx +17 -0
  218. package/docs/discoverability/open-graph.mdx +5 -3
  219. package/docs/discoverability/rss.mdx +1 -1
  220. package/docs/references/api-pages.mdx +74 -0
  221. package/docs/references/asyncapi.mdx +2 -2
  222. package/docs/references/graphql.mdx +3 -3
  223. package/docs/references/meta.ts +1 -1
  224. package/docs/references/openapi.mdx +106 -5
  225. package/docs/references/scalar.mdx +3 -1
  226. package/package.json +36 -1
  227. package/skills/blume/SKILL.md +3 -0
  228. package/skills/blume-migrate/SKILL.md +5 -5
  229. package/skills/blume-migrate/references/docusaurus.md +3 -3
  230. package/skills/blume-migrate/references/fumadocs.md +1 -1
  231. package/skills/blume-migrate/references/mintlify.md +37 -19
  232. package/skills/blume-migrate/references/nextra.md +3 -3
  233. package/skills/blume-migrate/references/starlight.md +2 -1
  234. package/skills/blume-migrate/scripts/mintlify-codemod.mjs +67 -16
  235. package/skills/blume-write-skill/SKILL.md +82 -0
  236. package/src/ai/agent-readability.ts +4 -3
  237. package/src/ai/agent-surface.ts +9 -7
  238. package/src/ai/ai-catalog.ts +12 -5
  239. package/src/ai/api/handlers.ts +2 -2
  240. package/src/ai/api/spec.ts +2 -2
  241. package/src/ai/api-catalog.ts +6 -2
  242. package/src/ai/ask-context.ts +73 -20
  243. package/src/ai/ask-data.ts +6 -0
  244. package/src/ai/ask-tools.ts +142 -0
  245. package/src/ai/ask.ts +354 -76
  246. package/src/ai/changelog-markdown.ts +2 -2
  247. package/src/ai/component-markdown.ts +75 -0
  248. package/src/ai/cors.ts +2 -0
  249. package/src/ai/index.ts +12 -0
  250. package/src/ai/link-headers.ts +14 -5
  251. package/src/ai/llms.ts +24 -9
  252. package/src/ai/markdown.ts +2 -1
  253. package/src/ai/mcp/data.ts +6 -0
  254. package/src/ai/mcp/discovery.ts +2 -2
  255. package/src/ai/mcp/query.ts +2 -2
  256. package/src/ai/mcp/server.ts +2 -2
  257. package/src/ai/openapi-components.ts +31 -9
  258. package/src/ai/relative-links.ts +42 -26
  259. package/src/ai/serializers.ts +2 -1
  260. package/src/ai/site-skill.ts +274 -0
  261. package/src/ai/skills.ts +49 -3
  262. package/src/ai/tar.ts +139 -12
  263. package/src/ai/write-skill.ts +97 -0
  264. package/src/analytics/head.ts +4 -0
  265. package/src/analytics/index.ts +5 -0
  266. package/src/analytics/one-dollar-stats.ts +86 -0
  267. package/src/analytics/schema.ts +2 -0
  268. package/src/astro/generate.ts +133 -19
  269. package/src/astro/include-hmr.ts +31 -12
  270. package/src/astro/include-refresh.ts +19 -8
  271. package/src/astro/index.ts +1 -0
  272. package/src/astro/integration.ts +143 -14
  273. package/src/astro/module-types.ts +6 -0
  274. package/src/astro/runtime-modules.ts +28 -13
  275. package/src/astro/templates.ts +364 -67
  276. package/src/astro/variables.ts +31 -0
  277. package/src/audit/catalog.ts +19 -0
  278. package/src/audit/checks/contrast.ts +183 -0
  279. package/src/audit/checks/i18n.ts +2 -3
  280. package/src/audit/checks/indexability.ts +2 -3
  281. package/src/audit/checks/links.ts +13 -13
  282. package/src/audit/checks/sitemap.ts +2 -3
  283. package/src/audit/graph.ts +3 -1
  284. package/src/audit/redirects.ts +46 -15
  285. package/src/audit/run.ts +14 -9
  286. package/src/audit/snapshot.ts +5 -0
  287. package/src/audit/types.ts +2 -1
  288. package/src/blume-modules.d.ts +7 -0
  289. package/src/captcha/client.ts +152 -0
  290. package/src/captcha/hcaptcha.ts +36 -0
  291. package/src/captcha/index.ts +28 -0
  292. package/src/captcha/schema.ts +32 -0
  293. package/src/captcha/turnstile.ts +38 -0
  294. package/src/captcha/verify.ts +71 -0
  295. package/src/cli/args.ts +32 -0
  296. package/src/cli/command-meta.ts +5 -0
  297. package/src/cli/commands/audit.ts +4 -1
  298. package/src/cli/commands/doctor.ts +25 -17
  299. package/src/cli/commands/eval.ts +19 -11
  300. package/src/cli/commands/skill.ts +96 -0
  301. package/src/cli/commands/translate.ts +9 -12
  302. package/src/cli/commands/validate.ts +3 -1
  303. package/src/cli/dev-lock.ts +157 -37
  304. package/src/cli/index.ts +5 -0
  305. package/src/cli/report-format.ts +11 -6
  306. package/src/cli/required-secrets.ts +71 -29
  307. package/src/components/BlumePage.astro +2 -0
  308. package/src/components/colors.ts +19 -0
  309. package/src/components/content/ApiEndpoint.astro +18 -0
  310. package/src/components/content/ApiField.astro +79 -0
  311. package/src/components/content/ApiPlayground.astro +62 -0
  312. package/src/components/content/ApiRail.astro +19 -0
  313. package/src/components/content/Badge.astro +5 -3
  314. package/src/components/content/Color.astro +1 -0
  315. package/src/components/content/Component.astro +2 -2
  316. package/src/components/content/FileTree.astro +1 -0
  317. package/src/components/content/GithubInfo.astro +1 -0
  318. package/src/components/content/Panel.astro +1 -0
  319. package/src/components/content/ParamField.astro +25 -0
  320. package/src/components/content/RequestExample.astro +17 -0
  321. package/src/components/content/ResponseExample.astro +17 -0
  322. package/src/components/content/ResponseField.astro +32 -0
  323. package/src/components/content/Step.astro +1 -0
  324. package/src/components/content/Tab.astro +0 -1
  325. package/src/components/content/Tabs.astro +4 -0
  326. package/src/components/content/Tile.astro +1 -0
  327. package/src/components/content/Tooltip.astro +1 -0
  328. package/src/components/content/Tree.astro +1 -0
  329. package/src/components/content/TypeTable.astro +4 -1
  330. package/src/components/content/View.astro +21 -0
  331. package/src/components/content/ViewSwitcher.astro +168 -0
  332. package/src/components/content/api-field.ts +100 -0
  333. package/src/components/content/api-page.ts +224 -0
  334. package/src/components/content/badge-color.ts +5 -2
  335. package/src/components/content/base-href.ts +13 -36
  336. package/src/components/content/view-switcher.ts +71 -0
  337. package/src/components/index.ts +2 -0
  338. package/src/components/islands/Assistant.astro +10 -1
  339. package/src/components/islands/assistant.tsx +125 -18
  340. package/src/components/islands/base-path.ts +47 -10
  341. package/src/components/islands/code-question.ts +62 -0
  342. package/src/components/islands/hooks.ts +145 -40
  343. package/src/components/islands/support-link.ts +68 -0
  344. package/src/components/islands/webmcp.ts +4 -2
  345. package/src/components/layout/Analytics.astro +11 -2
  346. package/src/components/layout/Banner.astro +14 -10
  347. package/src/components/layout/Breadcrumbs.astro +11 -4
  348. package/src/components/layout/ConsentBanner.astro +62 -0
  349. package/src/components/layout/ConsentHead.astro +38 -0
  350. package/src/components/layout/Directory.astro +94 -0
  351. package/src/components/layout/DiscoveryLinks.astro +9 -5
  352. package/src/components/layout/DrawerSwitchers.astro +51 -0
  353. package/src/components/layout/Empty.astro +2 -2
  354. package/src/components/layout/Header.astro +124 -82
  355. package/src/components/layout/LanguageSwitcher.astro +50 -17
  356. package/src/components/layout/NarrationPlayer.astro +1085 -0
  357. package/src/components/layout/NavSelector.astro +35 -9
  358. package/src/components/layout/NavTabMenu.astro +3 -3
  359. package/src/components/layout/NavTree.astro +16 -6
  360. package/src/components/layout/PageActions.astro +4 -3
  361. package/src/components/layout/PageFeedback.astro +54 -49
  362. package/src/components/layout/PageLayout.astro +78 -9
  363. package/src/components/layout/Pagination.astro +3 -3
  364. package/src/components/layout/ReferenceLayout.astro +17 -6
  365. package/src/components/layout/RelatedPages.astro +42 -0
  366. package/src/components/layout/RootLayout.astro +313 -50
  367. package/src/components/layout/Search.astro +64 -30
  368. package/src/components/layout/SiteFooter.astro +124 -0
  369. package/src/components/layout/SiteMetatags.astro +16 -0
  370. package/src/components/layout/VersionBanner.astro +2 -2
  371. package/src/components/layout/analytics-client.ts +12 -0
  372. package/src/components/layout/consent/native.ts +79 -0
  373. package/src/components/layout/consent/types.ts +37 -0
  374. package/src/components/layout/footer-links.ts +53 -0
  375. package/src/components/layout/footer-socials.ts +87 -0
  376. package/src/components/layout/locale-redirect.ts +107 -0
  377. package/src/components/layout/nav-utils.ts +83 -0
  378. package/src/components/layout/page-feedback.ts +96 -0
  379. package/src/components/layout/search/analytics.ts +98 -0
  380. package/src/components/layout/search/flexsearch.ts +17 -5
  381. package/src/components/layout/search/orama-cloud.ts +26 -14
  382. package/src/components/layout/search/types.ts +20 -0
  383. package/src/components/layout/search/typesense.ts +5 -1
  384. package/src/components/layout/toc-active.ts +41 -0
  385. package/src/components/layout/toc-element.ts +8 -14
  386. package/src/components/openapi/ApiTagOperations.astro +2 -2
  387. package/src/components/openapi/AsyncApiOperation.astro +7 -4
  388. package/src/components/openapi/GraphqlChip.astro +2 -2
  389. package/src/components/openapi/Operation.astro +163 -13
  390. package/src/components/openapi/Playground.astro +4 -4
  391. package/src/components/openapi/RequestBody.astro +6 -4
  392. package/src/components/openapi/RequestPanel.astro +63 -8
  393. package/src/components/openapi/Responses.astro +6 -4
  394. package/src/components/openapi/SchemaTable.astro +7 -0
  395. package/src/components/openapi/async-snippets.ts +4 -4
  396. package/src/components/openapi/async.ts +38 -6
  397. package/src/components/openapi/code-samples.ts +97 -0
  398. package/src/components/openapi/helpers.ts +19 -9
  399. package/src/components/openapi/message-composer.ts +6 -1
  400. package/src/components/openapi/message.ts +17 -2
  401. package/src/components/openapi/operation-model.ts +81 -11
  402. package/src/components/openapi/panel.ts +4 -2
  403. package/src/components/openapi/param-style.ts +181 -0
  404. package/src/components/openapi/playground-client.ts +73 -16
  405. package/src/components/openapi/request.ts +190 -26
  406. package/src/components/openapi/schema-tree.ts +30 -24
  407. package/src/components/openapi/security.ts +6 -3
  408. package/src/components/openapi/snippets.ts +953 -13
  409. package/src/components/openapi/ws-client.ts +18 -2
  410. package/src/components/props.ts +6 -0
  411. package/src/consent/client.ts +95 -0
  412. package/src/consent/clients.ts +19 -0
  413. package/src/consent/ethyca.ts +71 -0
  414. package/src/consent/head.ts +24 -0
  415. package/src/consent/index.ts +37 -0
  416. package/src/consent/init.ts +29 -0
  417. package/src/consent/native.ts +36 -0
  418. package/src/consent/osano.ts +59 -0
  419. package/src/consent/schema.ts +30 -0
  420. package/src/core/base-path.ts +62 -16
  421. package/src/core/builtin-tags.ts +5 -0
  422. package/src/core/client-address.ts +19 -0
  423. package/src/core/config-input.ts +203 -27
  424. package/src/core/data.ts +44 -4
  425. package/src/core/diagnostics.ts +5 -2
  426. package/src/core/directive-diagnostics.ts +99 -0
  427. package/src/core/footer.ts +26 -0
  428. package/src/core/frontmatter.ts +21 -18
  429. package/src/core/graph.ts +39 -36
  430. package/src/core/heading-markers.ts +5 -18
  431. package/src/core/i18n-ui.ts +82 -2
  432. package/src/core/i18n.ts +31 -3
  433. package/src/core/includes.ts +20 -3
  434. package/src/core/last-modified.ts +25 -3
  435. package/src/core/links.ts +16 -2
  436. package/src/core/locale-links.ts +5 -1
  437. package/src/core/localizable.ts +26 -0
  438. package/src/core/meta.ts +33 -19
  439. package/src/core/navigation.ts +71 -20
  440. package/src/core/page-modes.ts +56 -0
  441. package/src/core/project-graph.ts +138 -10
  442. package/src/core/redirect-patterns.ts +491 -0
  443. package/src/core/related.ts +131 -0
  444. package/src/core/schema.ts +342 -68
  445. package/src/core/sources/contentful-rich-text.ts +27 -20
  446. package/src/core/sources/filesystem.ts +20 -2
  447. package/src/core/sources/github-releases.ts +25 -6
  448. package/src/core/sources/lexical.ts +23 -18
  449. package/src/core/sources/lower.ts +201 -34
  450. package/src/core/sources/mdx-remote.ts +51 -16
  451. package/src/core/sources/normalize.ts +327 -91
  452. package/src/core/sources/notion.ts +52 -25
  453. package/src/core/sources/obsidian.ts +23 -6
  454. package/src/core/sources/portable-text.ts +38 -21
  455. package/src/core/sources/read.ts +8 -3
  456. package/src/core/sources/strapi-blocks.ts +20 -16
  457. package/src/core/sources/watch.ts +20 -7
  458. package/src/core/standard-schema.ts +10 -6
  459. package/src/core/types.ts +1 -1
  460. package/src/core/ui-packs/ar.ts +43 -0
  461. package/src/core/ui-packs/bg.ts +45 -0
  462. package/src/core/ui-packs/bn.ts +43 -0
  463. package/src/core/ui-packs/ca.ts +46 -0
  464. package/src/core/ui-packs/cs.ts +44 -0
  465. package/src/core/ui-packs/da.ts +45 -0
  466. package/src/core/ui-packs/de.ts +46 -0
  467. package/src/core/ui-packs/el.ts +45 -0
  468. package/src/core/ui-packs/es.ts +46 -0
  469. package/src/core/ui-packs/fa.ts +45 -0
  470. package/src/core/ui-packs/fi.ts +45 -0
  471. package/src/core/ui-packs/fr.ts +46 -0
  472. package/src/core/ui-packs/he.ts +43 -0
  473. package/src/core/ui-packs/hi.ts +44 -0
  474. package/src/core/ui-packs/hr.ts +45 -0
  475. package/src/core/ui-packs/hu.ts +44 -0
  476. package/src/core/ui-packs/id.ts +45 -0
  477. package/src/core/ui-packs/it.ts +45 -0
  478. package/src/core/ui-packs/ja.ts +45 -0
  479. package/src/core/ui-packs/ko.ts +44 -0
  480. package/src/core/ui-packs/nl.ts +46 -0
  481. package/src/core/ui-packs/no.ts +44 -0
  482. package/src/core/ui-packs/pl.ts +45 -0
  483. package/src/core/ui-packs/pt-br.ts +46 -0
  484. package/src/core/ui-packs/pt.ts +45 -0
  485. package/src/core/ui-packs/ro.ts +45 -0
  486. package/src/core/ui-packs/ru.ts +45 -0
  487. package/src/core/ui-packs/sk.ts +44 -0
  488. package/src/core/ui-packs/sr.ts +45 -0
  489. package/src/core/ui-packs/sv.ts +44 -0
  490. package/src/core/ui-packs/th.ts +43 -0
  491. package/src/core/ui-packs/tr.ts +45 -0
  492. package/src/core/ui-packs/uk.ts +45 -0
  493. package/src/core/ui-packs/vi.ts +45 -0
  494. package/src/core/ui-packs/zh-tw.ts +43 -0
  495. package/src/core/ui-packs/zh.ts +43 -0
  496. package/src/core/variables.ts +103 -0
  497. package/src/core/version-cut.ts +52 -15
  498. package/src/deploy/artifacts.ts +104 -14
  499. package/src/deploy/cloudflare-negotiation.ts +39 -17
  500. package/src/deploy/headers.ts +10 -5
  501. package/src/deploy/node-headers.ts +64 -10
  502. package/src/deploy/platforms/cloudflare.ts +44 -3
  503. package/src/deploy/platforms/netlify.ts +34 -1
  504. package/src/deploy/platforms/node.ts +1 -0
  505. package/src/deploy/platforms/static.ts +1 -0
  506. package/src/deploy/platforms/types.ts +8 -0
  507. package/src/deploy/platforms/vercel.ts +38 -2
  508. package/src/deploy/redirects.ts +139 -36
  509. package/src/deploy/robots.ts +2 -2
  510. package/src/deploy/rss.ts +4 -3
  511. package/src/deploy/sitemap.ts +7 -5
  512. package/src/deploy/vercel-negotiation.ts +36 -0
  513. package/src/markdown/api-rail.ts +259 -0
  514. package/src/markdown/base-links.ts +34 -36
  515. package/src/markdown/code-title.ts +35 -9
  516. package/src/markdown/directives.ts +242 -36
  517. package/src/markdown/features.ts +20 -2
  518. package/src/markdown/fence-meta.ts +2 -0
  519. package/src/markdown/include.ts +11 -1
  520. package/src/markdown/index.ts +35 -11
  521. package/src/markdown/math.ts +4 -4
  522. package/src/markdown/mdast.ts +5 -2
  523. package/src/markdown/relative-links.ts +12 -4
  524. package/src/markdown/variables.ts +56 -0
  525. package/src/markdown/views.ts +82 -0
  526. package/src/narration/build.ts +412 -0
  527. package/src/narration/player.ts +249 -0
  528. package/src/narration/provider.ts +29 -0
  529. package/src/narration/script.ts +592 -0
  530. package/src/og/card.ts +136 -11
  531. package/src/og/derive.ts +41 -31
  532. package/src/og/index.ts +1 -0
  533. package/src/openapi/asyncapi.ts +4 -2
  534. package/src/openapi/model.ts +77 -36
  535. package/src/openapi/overlay.ts +256 -0
  536. package/src/openapi/parse.ts +55 -2
  537. package/src/openapi/proxy.ts +63 -10
  538. package/src/openapi/references.ts +21 -5
  539. package/src/openapi/render-mdx.ts +46 -3
  540. package/src/openapi/scalar.ts +40 -6
  541. package/src/openapi/source.ts +1 -1
  542. package/src/ratelimit/cloudflare.ts +54 -0
  543. package/src/ratelimit/index.ts +30 -0
  544. package/src/ratelimit/memory.ts +45 -0
  545. package/src/ratelimit/runtime.ts +229 -0
  546. package/src/ratelimit/schema.ts +28 -0
  547. package/src/ratelimit/upstash.ts +30 -0
  548. package/src/ratelimit/wrangler.ts +70 -0
  549. package/src/reference/asyncapi.ts +2 -2
  550. package/src/reference/graphql.ts +5 -2
  551. package/src/reference/openapi.ts +21 -7
  552. package/src/reference/options.ts +51 -7
  553. package/src/reference/scalar.ts +33 -8
  554. package/src/registry/eject.ts +201 -28
  555. package/src/registry/registry.ts +7 -0
  556. package/src/search/adapters/version-scope.ts +30 -0
  557. package/src/search/documents.ts +93 -28
  558. package/src/search/orama-index.ts +28 -3
  559. package/src/search/pagefind-ranking.ts +39 -0
  560. package/src/search/popular.ts +2 -1
  561. package/src/search/sync/algolia.ts +15 -4
  562. package/src/search/sync/orama-cloud.ts +3 -0
  563. package/src/search/sync/typesense.ts +5 -0
  564. package/src/seo/jsonld.ts +7 -3
  565. package/src/seo/metatags.ts +67 -0
  566. package/src/theme/chrome-icons.ts +1 -0
  567. package/src/theme/color.ts +500 -0
  568. package/src/theme/entry.ts +56 -3
  569. package/src/theme/palette.ts +121 -45
  570. package/src/translate/meta.ts +68 -24
  571. package/src/translate/prompts.ts +1 -0
  572. package/src/translate/run.ts +3 -3
  573. package/src/translate/validate.ts +12 -3
  574. package/src/translate/work-list.ts +56 -17
  575. package/src/upgrade/upgrade.ts +4 -171
  576. package/dist/cli/chunk-3yce002v.js.map +0 -14
  577. package/dist/cli/chunk-5r8g91qn.js.map +0 -34
  578. package/dist/cli/chunk-6dtt0zfn.js.map +0 -10
  579. package/dist/cli/chunk-7mbqtmgb.js.map +0 -10
  580. package/dist/cli/chunk-ddndchfr.js.map +0 -14
  581. package/dist/cli/chunk-esh98wmb.js +0 -23
  582. package/dist/cli/chunk-esh98wmb.js.map +0 -10
  583. package/dist/cli/chunk-hqp2ajnh.js.map +0 -35
  584. package/dist/cli/chunk-hr8ne106.js.map +0 -13
  585. package/dist/cli/chunk-j85scx15.js.map +0 -10
  586. package/dist/cli/chunk-mqc662a6.js +0 -75
  587. package/dist/cli/chunk-mqc662a6.js.map +0 -11
  588. package/dist/cli/chunk-pbg5a4s3.js.map +0 -19
  589. package/dist/cli/chunk-qkb5a8sa.js.map +0 -10
  590. package/dist/cli/chunk-v6ya5kcb.js.map +0 -189
  591. package/dist/cli/chunk-wdrt2k2v.js.map +0 -10
  592. package/dist/cli/chunk-xh43dwgw.js.map +0 -36
  593. package/docs/content/sources.mdx +0 -338
  594. /package/dist/cli/{chunk-zp79m0ts.js.map → chunk-1n228xqb.js.map} +0 -0
  595. /package/dist/cli/{chunk-h2ez8dzb.js.map → chunk-5ffywsmb.js.map} +0 -0
  596. /package/dist/cli/{chunk-g698a744.js.map → chunk-5wj2qwn2.js.map} +0 -0
  597. /package/dist/cli/{chunk-sqw4ekg1.js.map → chunk-amrb6hgh.js.map} +0 -0
  598. /package/dist/cli/{chunk-h7k3nq3v.js.map → chunk-gp84za0h.js.map} +0 -0
  599. /package/dist/cli/{chunk-bfwp9vp6.js.map → chunk-p804f3pe.js.map} +0 -0
  600. /package/dist/cli/{chunk-00gs3wqs.js.map → chunk-ta27feev.js.map} +0 -0
  601. /package/dist/cli/{chunk-bbnwccaz.js.map → chunk-y4ykbark.js.map} +0 -0
@@ -3,15 +3,30 @@ import { toString as mdastToString } from "mdast-util-to-string";
3
3
  import { jsxAttribute, jsxFlowElement } from "./mdast.ts";
4
4
  import type { MdastNode, MdastVisitorContext } from "./mdast.ts";
5
5
 
6
- interface DirectiveNode extends MdastNode {
6
+ export interface DirectiveNode extends MdastNode {
7
7
  attributes?: Record<string, string | null | undefined> | null;
8
8
  // Satteri gives an empty container directive (`:::note\n:::`) `children: null`.
9
9
  children?: MdastNode[] | null;
10
10
  name: string;
11
+ position?: {
12
+ start?: { offset?: number };
13
+ end?: { offset?: number };
14
+ };
15
+ }
16
+
17
+ /**
18
+ * The visitor-context slice the directive visitors use. `source` is the page
19
+ * the directive offsets index into; without it the literal fallback rebuilds
20
+ * a directive from its node instead. `parent` walks up to an enclosing
21
+ * container directive, which renders the directives inside it itself.
22
+ */
23
+ interface DirectiveVisitorContext extends MdastVisitorContext {
24
+ parent?: (node: MdastNode) => MdastNode | undefined;
25
+ source?: string;
11
26
  }
12
27
 
13
28
  /** Directive names that map directly onto a Callout type. */
14
- const CALLOUT_TYPES = new Set([
29
+ export const CALLOUT_TYPES: ReadonlySet<string> = new Set([
15
30
  "danger",
16
31
  "info",
17
32
  "note",
@@ -25,7 +40,7 @@ interface CalloutAliases {
25
40
  [alias: string]: string;
26
41
  }
27
42
 
28
- const ALIASES: CalloutAliases = {
43
+ export const CALLOUT_ALIASES: Readonly<CalloutAliases> = {
29
44
  caution: "warning",
30
45
  error: "danger",
31
46
  important: "note",
@@ -38,48 +53,239 @@ export const calloutTypeFor = (name: string): string | null => {
38
53
  if (CALLOUT_TYPES.has(lower)) {
39
54
  return lower;
40
55
  }
41
- return ALIASES[lower] ?? null;
56
+ return CALLOUT_ALIASES[lower] ?? null;
57
+ };
58
+
59
+ /** The markers that open a text (`:name`) and a leaf (`::name`) directive. */
60
+ const LITERAL_MARKERS = new Map([
61
+ ["leafDirective", "::"],
62
+ ["textDirective", ":"],
63
+ ]);
64
+
65
+ /** A directive's `[label]` rebuilt from its label's text, or nothing. */
66
+ const labelSource = (label: MdastNode[]): string =>
67
+ label.length > 0
68
+ ? `[${mdastToString(label, { includeImageAlt: false })}]`
69
+ : "";
70
+
71
+ /** A directive's `{attributes}` rebuilt from its node, or nothing. */
72
+ const attributeSource = (node: DirectiveNode): string => {
73
+ const attributes = Object.entries(node.attributes ?? {}).map(
74
+ ([key, value]) => (value ? `${key}="${value}"` : key)
75
+ );
76
+ return attributes.length > 0 ? `{${attributes.join(" ")}}` : "";
77
+ };
78
+
79
+ /** The source a directive node spans, when its offsets point into `source`. */
80
+ const spannedSource = (node: DirectiveNode, source: string): string | null => {
81
+ const start = node.position?.start?.offset;
82
+ const end = node.position?.end?.offset;
83
+ return start !== undefined && end !== undefined
84
+ ? source.slice(start, end)
85
+ : null;
42
86
  };
43
87
 
44
88
  /**
45
- * Satteri MDAST plugin mapping container directives (`:::note`, `:::warning`,
46
- * `:::tip`, …) onto Blume's `<Callout>` component. The title comes from a
47
- * `[label]` or a `{title="…"}` attribute; the body becomes the callout content.
48
- * Directive names that are not callouts are left untouched.
89
+ * A text or leaf directive exactly as the author wrote it. The slice by
90
+ * offsets is the exact text — `[label]` and `{attrs}` included — and is
91
+ * trusted only when it opens with the directive's own marker and name;
92
+ * content an `<include>` spliced in carries no offsets into this page, so it
93
+ * is rebuilt from the node instead.
49
94
  */
50
- export const directiveToCalloutPlugin = () => ({
51
- containerDirective(node: DirectiveNode, ctx: MdastVisitorContext) {
52
- const type = calloutTypeFor(node.name);
53
- if (type === null) {
54
- return;
95
+ export const directiveSource = (
96
+ node: DirectiveNode,
97
+ marker: string,
98
+ source: string
99
+ ): string => {
100
+ const opening = `${marker}${node.name}`;
101
+ const slice = spannedSource(node, source);
102
+ if (slice?.startsWith(opening)) {
103
+ return slice;
104
+ }
105
+ return `${opening}${labelSource(node.children ?? [])}${attributeSource(node)}`;
106
+ };
107
+
108
+ /**
109
+ * The node a text or leaf directive renders as: its literal source, as text
110
+ * in place of a text directive and as a paragraph in place of a leaf one.
111
+ */
112
+ const literalDirective = (
113
+ node: DirectiveNode,
114
+ marker: string,
115
+ source: string
116
+ ): MdastNode => {
117
+ const text = { type: "text", value: directiveSource(node, marker, source) };
118
+ return marker === ":" ? text : { children: [text], type: "paragraph" };
119
+ };
120
+
121
+ const FENCE = /^:{3,}/u;
122
+
123
+ // The line that closes a container: a colon fence, after whatever quote or
124
+ // list indentation the container sits in.
125
+ const CLOSING_FENCE = /\n[\t >]*(?<fence>:{3,})\s*$/u;
126
+
127
+ /** A paragraph holding one line of literal text. */
128
+ const literalLine = (value: string): MdastNode => ({
129
+ children: [{ type: "text", value }],
130
+ type: "paragraph",
131
+ });
132
+
133
+ /**
134
+ * The fence lines of a container directive as the author wrote them — the
135
+ * opening one with its `[label]` and `{attrs}` — or, without offsets into
136
+ * this page (an `<include>`), rebuilt from the node. A container left
137
+ * unclosed runs to the end of its parent and has no closing line.
138
+ */
139
+ const containerFences = (
140
+ node: DirectiveNode,
141
+ label: MdastNode | undefined,
142
+ source: string
143
+ ): string[] => {
144
+ const slice = spannedSource(node, source) ?? "";
145
+ const fence = FENCE.exec(slice)?.[0];
146
+ if (fence === undefined || !slice.startsWith(node.name, fence.length)) {
147
+ const labelText = labelSource(label ? [label] : []);
148
+ return [`:::${node.name}${labelText}${attributeSource(node)}`, ":::"];
149
+ }
150
+ const [opening = ""] = slice.split("\n", 1);
151
+ const closing = CLOSING_FENCE.exec(slice.slice(opening.length))?.groups
152
+ ?.fence;
153
+ // A shorter fence can't close the container; it is the body's last line.
154
+ return closing !== undefined && closing.length >= fence.length
155
+ ? [opening.trimEnd(), closing]
156
+ : [opening.trimEnd()];
157
+ };
158
+
159
+ /**
160
+ * What a container directive renders as. A callout name becomes a
161
+ * `<Callout>`: the title comes from a `[label]` or a `{title="…"}` attribute,
162
+ * and the body becomes the callout content. Blume has no other container, and
163
+ * dropping one would drop its body with it, so any other name — `:::details`,
164
+ * a `:::warnig` typo — renders its body between its fence lines, as written.
165
+ * The directives inside render here too, since Satteri reaches them only
166
+ * after this replacement has taken their original.
167
+ */
168
+ const renderContainer = (
169
+ node: DirectiveNode,
170
+ source: string
171
+ ): MdastNode | MdastNode[] => {
172
+ const children = (node.children ?? []).flatMap((child) =>
173
+ // oxlint-disable-next-line no-use-before-define -- mutual recursion: a container's body holds directives, containers included
174
+ literalize(child, source)
175
+ );
176
+
177
+ // A leading `:::name[Label]` parses to a paragraph flagged `directiveLabel`.
178
+ // SAFETY: Satteri stamps `directiveLabel` on that paragraph's `data`; any
179
+ // other node reads undefined and fails the check.
180
+ const labelIndex = children.findIndex(
181
+ (child) =>
182
+ child.type === "paragraph" &&
183
+ (child.data as { directiveLabel?: boolean } | undefined)?.directiveLabel
184
+ );
185
+ const [label] = labelIndex === -1 ? [] : children.splice(labelIndex, 1);
186
+
187
+ const type = calloutTypeFor(node.name);
188
+ if (type === null) {
189
+ const [opening = "", closing] = containerFences(node, label, source);
190
+ return [
191
+ literalLine(opening),
192
+ ...children,
193
+ ...(closing === undefined ? [] : [literalLine(closing)]),
194
+ ];
195
+ }
196
+
197
+ // Flatten the label's phrasing children so `:::note[Read **this**]` yields
198
+ // `Read this`; image alt is excluded (an image is not label text), matching
199
+ // the historical child-values-only behavior.
200
+ const title =
201
+ node.attributes?.title ??
202
+ (label ? mdastToString(label, { includeImageAlt: false }) : undefined);
203
+ const attributes = [jsxAttribute("type", type)];
204
+ if (title) {
205
+ attributes.push(jsxAttribute("title", title));
206
+ }
207
+ return jsxFlowElement("Callout", attributes, children);
208
+ };
209
+
210
+ /**
211
+ * `node` with every directive in it rendered: text and leaf directives as
212
+ * their literal source, containers as {@link renderContainer}.
213
+ */
214
+ const literalize = (node: MdastNode, source: string): MdastNode[] => {
215
+ if (node.type === "containerDirective") {
216
+ // SAFETY: a `containerDirective` carries a `name` plus optional
217
+ // attributes, children, and position — the `DirectiveNode` shape.
218
+ return [renderContainer(node as DirectiveNode, source)].flat();
219
+ }
220
+ const marker = LITERAL_MARKERS.get(node.type);
221
+ if (marker) {
222
+ // SAFETY: only Satteri's `textDirective`/`leafDirective` nodes have a
223
+ // marker, and they carry a `name` plus optional attributes, children,
224
+ // and position — the `DirectiveNode` shape.
225
+ return [literalDirective(node as DirectiveNode, marker, source)];
226
+ }
227
+ // SAFETY: a parent's `children` is always a node list; leaves carry none.
228
+ const children = node.children as MdastNode[] | null | undefined;
229
+ if (!children) {
230
+ return [node];
231
+ }
232
+ return [
233
+ {
234
+ ...node,
235
+ children: children.flatMap((child) => literalize(child, source)),
236
+ },
237
+ ];
238
+ };
239
+
240
+ /** Whether a container directive (already rendered, body and all) holds `node`. */
241
+ const insideContainer = (node: MdastNode, ctx: DirectiveVisitorContext) => {
242
+ for (
243
+ let parent = ctx.parent?.(node);
244
+ parent !== undefined;
245
+ parent = ctx.parent?.(parent)
246
+ ) {
247
+ if (parent.type === "containerDirective") {
248
+ return true;
55
249
  }
250
+ }
251
+ return false;
252
+ };
56
253
 
57
- const children = [...(node.children ?? [])];
58
- let title = node.attributes?.title ?? undefined;
59
-
60
- // A leading `:::name[Label]` parses to a paragraph flagged `directiveLabel`.
61
- // SAFETY: Satteri stamps `directiveLabel` on that paragraph's `data`; any
62
- // other node reads undefined and fails the check.
63
- const labelIndex = children.findIndex(
64
- (child) =>
65
- child.type === "paragraph" &&
66
- (child.data as { directiveLabel?: boolean } | undefined)?.directiveLabel
67
- );
68
- if (labelIndex !== -1) {
69
- const [label] = children.splice(labelIndex, 1);
70
- if (label) {
71
- // Flatten the label's phrasing children so `:::note[Read **this**]`
72
- // yields `Read this`; image alt is excluded (an image is not label
73
- // text), matching the historical child-values-only behavior.
74
- title ??= mdastToString(label, { includeImageAlt: false }) || undefined;
75
- }
254
+ /** The text and leaf visitor: render the directive as its literal source. */
255
+ const renderLiteral =
256
+ (marker: string) => (node: DirectiveNode, ctx: DirectiveVisitorContext) => {
257
+ // A container's body was already rendered into its replacement; a
258
+ // transform queued on the replaced original would be dropped with a
259
+ // warning.
260
+ if (insideContainer(node, ctx)) {
261
+ return;
76
262
  }
263
+ ctx.replaceNode(node, literalDirective(node, marker, ctx.source ?? ""));
264
+ };
77
265
 
78
- const attributes = [jsxAttribute("type", type)];
79
- if (title) {
80
- attributes.push(jsxAttribute("title", title));
266
+ /**
267
+ * Satteri MDAST plugin for directives. Container directives (`:::note`,
268
+ * `:::warning`, `:::tip`, …) map onto Blume's `<Callout>` component, and any
269
+ * other container renders its body between its literal fence lines (see
270
+ * {@link renderContainer}).
271
+ *
272
+ * Blume handles no text (`:name`) or leaf (`::name`) directives, and prose is
273
+ * full of text that parses as one — `16:9`, `10:30am`, `og:image`,
274
+ * `pets:read` — so both render as the literal source the author wrote instead
275
+ * of vanishing.
276
+ */
277
+ export const directiveToCalloutPlugin = () => ({
278
+ containerDirective(node: DirectiveNode, ctx: DirectiveVisitorContext) {
279
+ // A container inside another was rendered with its enclosing one.
280
+ if (insideContainer(node, ctx)) {
281
+ return;
81
282
  }
82
- ctx.replaceNode(node, jsxFlowElement("Callout", attributes, children));
283
+ ctx.replaceNode(node, renderContainer(node, ctx.source ?? ""));
83
284
  },
285
+ leafDirective: renderLiteral("::"),
84
286
  name: "blume-directive-callout",
287
+ // Positions are opt-in since satteri 0.10; the literal fallback slices
288
+ // directives out of the source by offset.
289
+ options: { position: true },
290
+ textDirective: renderLiteral(":"),
85
291
  });
@@ -11,11 +11,29 @@ export const MARKDOWN_FEATURES = {
11
11
 
12
12
  /**
13
13
  * The `.mdx` feature set: Markdown's plus `:::` directives (→ `<Callout>`) and
14
- * block-only math — `singleDollarTextMath: false` keeps a bare `$` (currency,
15
- * shell, code) as literal text and only parses `$$…$$`.
14
+ * math — `singleDollarTextMath: false` keeps a bare `$` (currency, shell,
15
+ * code) as literal text and only parses `$$…$$`, as a block on its own lines
16
+ * or inline within a sentence.
16
17
  */
17
18
  export const MDX_FEATURES = {
18
19
  ...MARKDOWN_FEATURES,
19
20
  directive: true,
20
21
  math: { singleDollarTextMath: false },
21
22
  } satisfies Features;
23
+
24
+ /**
25
+ * The feature sets for a page body, once its front matter is off. Astro reads
26
+ * a page's front matter itself and hands the renderer the body alone, and the
27
+ * search extractor strips it first too — so front matter parsing is off here:
28
+ * left on, a body that opens with a `---` rule read everything up to the next
29
+ * `---` line as front matter and dropped it from the page.
30
+ */
31
+ export const MARKDOWN_BODY_FEATURES = {
32
+ ...MARKDOWN_FEATURES,
33
+ frontmatter: false,
34
+ } satisfies Features;
35
+
36
+ export const MDX_BODY_FEATURES = {
37
+ ...MDX_FEATURES,
38
+ frontmatter: false,
39
+ } satisfies Features;
@@ -41,9 +41,11 @@ export const metaTokens = (meta: string | null | undefined): string[] =>
41
41
  * a block title.
42
42
  */
43
43
  export const RESERVED_META_KEYWORDS: ReadonlySet<string> = new Set([
44
+ "expandable",
44
45
  "lineNumbers",
45
46
  "ts2js",
46
47
  "twoslash",
48
+ "wrap",
47
49
  ]);
48
50
 
49
51
  /** A Shiki `{1,3-5}` line-range token. */
@@ -9,6 +9,8 @@ import {
9
9
  hasIncludeStatements,
10
10
  matchIncludeStatement,
11
11
  } from "../core/includes.ts";
12
+ import { substituteVariables } from "../core/variables.ts";
13
+ import type { ContentVariables } from "../core/variables.ts";
12
14
  import type { MdastNode, MdastValue } from "./mdast.ts";
13
15
 
14
16
  /**
@@ -83,6 +85,8 @@ const errorBlock = (message: string): string =>
83
85
  export interface IncludePluginOptions {
84
86
  /** The docs content root; bounds include resolution when set. */
85
87
  contentRoot?: string;
88
+ /** Content variables, substituted in each included file before it's parsed. */
89
+ variables?: ContentVariables;
86
90
  }
87
91
 
88
92
  /** A plain string attribute value; expression values don't carry a path. */
@@ -104,6 +108,10 @@ const statementFromJsx = (
104
108
  if (attr.name === "meta" && attr.value) {
105
109
  attributes.meta = attr.value;
106
110
  }
111
+ // Every other string attribute is a prop, as in the string-level parser.
112
+ if (attr.name && attr.name !== "lang" && attr.name !== "meta") {
113
+ attributes.props = { ...attributes.props, [attr.name]: attr.value };
114
+ }
107
115
  }
108
116
  return { attributes, target: ctx.textContent(node).trim() };
109
117
  };
@@ -147,7 +155,9 @@ export const includePlugin = (options: IncludePluginOptions = {}) => {
147
155
  for (const nested of expanded.errors) {
148
156
  ctx.report({ message: nested.message, node, severity: "warning" });
149
157
  }
150
- return expanded.text;
158
+ // Substituted here, before the spliced text is parsed: in an MDX page it
159
+ // parses as MDX, where `{{name}}` would be a JavaScript expression.
160
+ return substituteVariables(expanded.text, options.variables);
151
161
  };
152
162
 
153
163
  /**
@@ -9,11 +9,14 @@ import {
9
9
  import { escape as escapeHtml } from "html-escaper";
10
10
  import { codeToHtml } from "shiki";
11
11
 
12
+ import { hasVariables } from "../core/variables.ts";
13
+ import type { ContentVariables } from "../core/variables.ts";
14
+ import { apiRailPlugin } from "./api-rail.ts";
12
15
  import { baseLinksPlugin } from "./base-links.ts";
13
16
  import { codeTitleTransformer } from "./code-title.ts";
14
17
  import { directiveToCalloutPlugin } from "./directives.ts";
15
18
  import { externalLinksPlugin } from "./external-links.ts";
16
- import { MARKDOWN_FEATURES, MDX_FEATURES } from "./features.ts";
19
+ import { MARKDOWN_BODY_FEATURES, MDX_BODY_FEATURES } from "./features.ts";
17
20
  import { headingAnchorPlugin } from "./heading-anchors.ts";
18
21
  import { includePlugin } from "./include.ts";
19
22
  import { inlineCodeHighlightPlugin } from "./inline-code.ts";
@@ -26,6 +29,8 @@ import { tableWrapPlugin } from "./table-wrap.ts";
26
29
  import { DEFAULT_CODE_THEMES } from "./themes.ts";
27
30
  import type { CodeThemes } from "./themes.ts";
28
31
  import { ts2jsPlugin } from "./ts2js.ts";
32
+ import { variablesPlugin } from "./variables.ts";
33
+ import { viewsPlugin } from "./views.ts";
29
34
 
30
35
  export type { CodeTheme, CodeThemes } from "./themes.ts";
31
36
 
@@ -277,6 +282,12 @@ export interface BlumeMarkdownOptions {
277
282
  * `basePath` link isn't double-prefixed (see `withComposedBasePath`).
278
283
  */
279
284
  deployBase?: string;
285
+ /**
286
+ * `variables`: `{{name}}` references replaced in `.md` pages and in every
287
+ * included file (`.mdx` pages are substituted before parsing, by the
288
+ * `variablesVitePlugin`).
289
+ */
290
+ variables?: ContentVariables;
280
291
  /**
281
292
  * The docs content root, bounding `<include>` target resolution (and
282
293
  * anchoring `/`-leading include paths). When unset, relative includes still
@@ -295,9 +306,9 @@ export interface BlumeMarkdownOptions {
295
306
 
296
307
  /**
297
308
  * MDAST plugins that apply to both `.md` and `.mdx`: relative page links
298
- * rewritten to the root-relative route they mean, then the base-path link
299
- * rewrite (added only when a `basePath` or `deployBase` is configured), which
300
- * layers `deployment.base` over the rewritten routes.
309
+ * rewritten to the served URL of the route they mean, then the base-path link
310
+ * rewrite for root-relative links (added only when a `basePath` or
311
+ * `deployBase` is configured).
301
312
  */
302
313
  const blumeSharedMdastPlugins = (
303
314
  options: BlumeMarkdownOptions
@@ -306,6 +317,7 @@ const blumeSharedMdastPlugins = (
306
317
  relativeLinksPlugin({
307
318
  contentRoot: options.contentRoot,
308
319
  dataFile: options.dataFile,
320
+ deployBase: options.deployBase,
309
321
  })
310
322
  ),
311
323
  ...(options.basePath || options.deployBase
@@ -325,15 +337,23 @@ const blumeSharedMdastPlugins = (
325
337
  * (callouts, mermaid, math, base links) like inline content.
326
338
  */
327
339
  const blumeIncludePlugin = (options: BlumeMarkdownOptions): MdastPlugin =>
328
- asMdastPlugin(includePlugin({ contentRoot: options.contentRoot }));
340
+ asMdastPlugin(
341
+ includePlugin({
342
+ contentRoot: options.contentRoot,
343
+ variables: options.variables,
344
+ })
345
+ );
329
346
 
330
347
  /** Sätteri processor for plain `.md`, with Blume's curated feature set. */
331
348
  export const blumeMarkdownProcessor = (options: BlumeMarkdownOptions = {}) =>
332
349
  satteri({
333
- features: { ...MARKDOWN_FEATURES },
350
+ features: { ...MARKDOWN_BODY_FEATURES },
334
351
  hastPlugins: blumeHastPlugins(options),
335
352
  mdastPlugins: [
336
353
  blumeIncludePlugin(options),
354
+ ...(hasVariables(options.variables)
355
+ ? [asMdastPlugin(variablesPlugin(options.variables))]
356
+ : []),
337
357
  ...blumeSharedMdastPlugins(options),
338
358
  ],
339
359
  });
@@ -344,13 +364,15 @@ export type BlumeMdxOptions = BlumeMarkdownOptions;
344
364
  * Sätteri MDX processor: Blume's feature set plus the MDAST plugins that target
345
365
  * components — `package-install` → package-manager tabs, ` ```ts ts2js ` →
346
366
  * TypeScript/JavaScript tabs, `:::note` → `<Callout>`, ` ```mermaid ` → a
347
- * `<blume-mermaid>` element, and block math
348
- * (`$$…$$`) → the `<Math>` component. Used as the `processor` for
367
+ * `<blume-mermaid>` element, block math
368
+ * (`$$…$$`) → the `<Math>` component, and top-level `<RequestExample>` and
369
+ * `<ResponseExample>` → the page's `<ApiRail>`. Used as the `processor` for
349
370
  * `@astrojs/mdx` so these apply to `.mdx` only (plain `.md` uses
350
371
  * {@link blumeMarkdownProcessor}).
351
372
  *
352
- * Math is always on but block-only: `singleDollarTextMath: false` keeps a bare
353
- * `$` (currency, shell, code) as literal text and only parses `$$…$$`. The
373
+ * Math is always on and written `$$…$$`, as a block or inline:
374
+ * `singleDollarTextMath: false` keeps a bare `$` (currency, shell, code) as
375
+ * literal text. The
354
376
  * generated runtime imports the `<Math>` component (and KaTeX's stylesheet) only
355
377
  * when content actually uses `$$`, so a math-free site ships no KaTeX CSS.
356
378
  *
@@ -359,7 +381,7 @@ export type BlumeMdxOptions = BlumeMarkdownOptions;
359
381
  */
360
382
  export const blumeMdxProcessor = (options: BlumeMdxOptions = {}) =>
361
383
  satteri({
362
- features: { ...MDX_FEATURES },
384
+ features: { ...MDX_BODY_FEATURES },
363
385
  hastPlugins: blumeHastPlugins(options),
364
386
  mdastPlugins: [
365
387
  blumeIncludePlugin(options),
@@ -369,5 +391,7 @@ export const blumeMdxProcessor = (options: BlumeMdxOptions = {}) =>
369
391
  asMdastPlugin(mermaidPlugin()),
370
392
  asMdastPlugin(mathPlugin()),
371
393
  ...blumeSharedMdastPlugins(options),
394
+ asMdastPlugin(apiRailPlugin()),
395
+ asMdastPlugin(viewsPlugin()),
372
396
  ],
373
397
  });
@@ -7,10 +7,10 @@ interface MathNode extends MdastNode {
7
7
 
8
8
  /**
9
9
  * Satteri MDAST plugin that turns math nodes into Blume's `<Math>` component,
10
- * which renders them with KaTeX at build time. Block math (`$$…$$`) becomes a
11
- * block element. Blume runs the parser block-only (`singleDollarTextMath:
12
- * false`), so a bare `$` stays literal and no `inlineMath` nodes are produced;
13
- * the `inlineMath` visitor remains as a harmless safety net.
10
+ * which renders them with KaTeX at build time. `$$…$$` on its own lines is
11
+ * block math and becomes a display element; `$$…$$` inside a sentence is
12
+ * inline math and stays inline. The parser runs with `singleDollarTextMath:
13
+ * false`, so a single `$` (currency, a shell variable) never opens math.
14
14
  */
15
15
  export const mathPlugin = () => ({
16
16
  inlineMath(node: MathNode, ctx: MdastVisitorContext) {
@@ -19,9 +19,12 @@ export type MdastValue =
19
19
  | MdastValue[]
20
20
  | { [key: string]: MdastValue };
21
21
 
22
- /** The visitor context Blume's plugins use to mutate the tree. */
22
+ /**
23
+ * The visitor context Blume's plugins use to mutate the tree. A list of
24
+ * nodes takes the replaced node's place in order.
25
+ */
23
26
  export interface MdastVisitorContext {
24
- replaceNode: (node: MdastNode, replacement: MdastNode) => void;
27
+ replaceNode: (node: MdastNode, replacement: MdastNode | MdastNode[]) => void;
25
28
  }
26
29
 
27
30
  /** Any MDAST node, keyed loosely since we build a small subset by hand. */
@@ -2,6 +2,7 @@ import { fileURLToPath } from "node:url";
2
2
 
3
3
  import { dirname, normalize, relative, resolve } from "pathe";
4
4
 
5
+ import { mountBasePath } from "../core/base-path.ts";
5
6
  import { isIndexFileName, resolveRelativeHref } from "../core/links.ts";
6
7
  import type { RelativeLinkBase } from "../core/links.ts";
7
8
  import type { MdastNode, MdastValue } from "./mdast.ts";
@@ -130,6 +131,8 @@ export interface RelativeLinksPluginOptions {
130
131
  * to publish the snapshot, the plugin reads the file eject writes instead.
131
132
  */
132
133
  dataFile?: string;
134
+ /** Astro's `deployment.base` subdirectory (`""` or `/seg`). */
135
+ deployBase?: string;
133
136
  }
134
137
 
135
138
  /**
@@ -145,9 +148,11 @@ export interface RelativeLinksPluginOptions {
145
148
  * entries (remote sources) by the longest trailing path that names one. A file
146
149
  * the snapshot doesn't know keeps its links as written.
147
150
  *
148
- * Runs before the base-links plugin, whose `basePath` layering is idempotent,
149
- * so a rewritten route (which already carries `basePath`) only gains the
150
- * `deployment.base` prefix.
151
+ * A rewritten route already carries `basePath`, and the `deployment.base`
152
+ * prefix goes on here, unconditionally: the route is one Blume serves, so
153
+ * `guides/setup.md` under base `/guides` is linked at `/guides/guides/setup`.
154
+ * The base-links plugin that runs next is idempotent per layer, so it leaves
155
+ * the based route alone.
151
156
  */
152
157
  export const relativeLinksPlugin = (
153
158
  options: RelativeLinksPluginOptions = {}
@@ -155,6 +160,7 @@ export const relativeLinksPlugin = (
155
160
  const contentRoot = options.contentRoot
156
161
  ? resolve(options.contentRoot)
157
162
  : undefined;
163
+ const deployBase = options.deployBase ?? "";
158
164
  const readSnapshot = routeSnapshotReader(options.dataFile);
159
165
 
160
166
  // Parsed once per published snapshot: the CLI republishes on regeneration,
@@ -245,7 +251,9 @@ export const relativeLinksPlugin = (
245
251
  const next = resolveRelativeHref(url, page, page.routeOf, (route) =>
246
252
  page.routes.has(route)
247
253
  );
248
- return next === url ? undefined : next;
254
+ return next === undefined || next === url
255
+ ? undefined
256
+ : mountBasePath(deployBase, next);
249
257
  };
250
258
 
251
259
  const rewrite = (node: UrlNode, ctx: RelativeLinksContext): void => {
@@ -0,0 +1,56 @@
1
+ import { substituteVariables } from "../core/variables.ts";
2
+ import type { ContentVariables } from "../core/variables.ts";
3
+ import type { MdastNode, MdastValue } from "./mdast.ts";
4
+
5
+ /**
6
+ * The Markdown half of content variables (see `core/variables.ts`): a Sätteri
7
+ * MDAST plugin that replaces `{{name}}` in a plain `.md` page's text, code,
8
+ * raw HTML, and link and image destinations. A `.md` page has no expressions,
9
+ * so every reference survives parsing as text and can be replaced here; MDX
10
+ * pages are substituted before they're parsed instead (`astro/variables.ts`).
11
+ * Nodes change through `setProperty`, not by mutating the node object.
12
+ */
13
+
14
+ interface VariableContext {
15
+ setProperty: (node: MdastNode, key: string, value: string) => void;
16
+ }
17
+
18
+ const isText = (value: MdastValue): value is string =>
19
+ typeof value === "string";
20
+
21
+ /** The node properties that can hold a reference, per node type. */
22
+ const FIELDS = {
23
+ code: ["value"],
24
+ definition: ["url", "title"],
25
+ html: ["value"],
26
+ image: ["url", "title", "alt"],
27
+ inlineCode: ["value"],
28
+ link: ["url", "title"],
29
+ text: ["value"],
30
+ } as const;
31
+
32
+ export const variablesPlugin = (variables: ContentVariables) => {
33
+ const replaceIn =
34
+ (keys: readonly string[]) =>
35
+ (node: MdastNode, ctx: VariableContext): void => {
36
+ for (const key of keys) {
37
+ const value = node[key];
38
+ if (value !== undefined && isText(value)) {
39
+ const next = substituteVariables(value, variables);
40
+ if (next !== value) {
41
+ ctx.setProperty(node, key, next);
42
+ }
43
+ }
44
+ }
45
+ };
46
+ return {
47
+ code: replaceIn(FIELDS.code),
48
+ definition: replaceIn(FIELDS.definition),
49
+ html: replaceIn(FIELDS.html),
50
+ image: replaceIn(FIELDS.image),
51
+ inlineCode: replaceIn(FIELDS.inlineCode),
52
+ link: replaceIn(FIELDS.link),
53
+ name: "blume-variables",
54
+ text: replaceIn(FIELDS.text),
55
+ };
56
+ };