blume 2.1.3 → 2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (397) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +134 -0
  3. package/README.md +4 -4
  4. package/dist/cli/{chunk-y4msrwm9.js → chunk-080mfch6.js} +15 -15
  5. package/dist/cli/{chunk-gfc5fqvf.js → chunk-1ch3ct3r.js} +18 -18
  6. package/dist/cli/{chunk-gfc5fqvf.js.map → chunk-1ch3ct3r.js.map} +1 -1
  7. package/dist/cli/{chunk-32khwcb6.js → chunk-2ssh60qh.js} +42 -69
  8. package/dist/cli/{chunk-32khwcb6.js.map → chunk-2ssh60qh.js.map} +9 -10
  9. package/dist/cli/{chunk-9qzgg6mj.js → chunk-34wap3ed.js} +3 -3
  10. package/dist/cli/{chunk-k3gr1nm9.js → chunk-40vk8kgf.js} +13 -16
  11. package/dist/cli/{chunk-k3gr1nm9.js.map → chunk-40vk8kgf.js.map} +3 -3
  12. package/dist/cli/chunk-487b95qy.js +329 -0
  13. package/dist/cli/chunk-487b95qy.js.map +13 -0
  14. package/dist/cli/chunk-4zxh25hc.js +157 -0
  15. package/dist/cli/chunk-4zxh25hc.js.map +10 -0
  16. package/dist/cli/chunk-69rjf786.js +146 -0
  17. package/dist/cli/chunk-69rjf786.js.map +11 -0
  18. package/dist/cli/{chunk-dqhg54mr.js → chunk-9h8mh2tk.js} +3 -3
  19. package/dist/cli/{chunk-dqhg54mr.js.map → chunk-9h8mh2tk.js.map} +2 -2
  20. package/dist/cli/{chunk-bqxaqn5p.js → chunk-9jqmq5hv.js} +8 -8
  21. package/dist/cli/{chunk-bqxaqn5p.js.map → chunk-9jqmq5hv.js.map} +1 -1
  22. package/dist/cli/{chunk-dv2z0rzf.js → chunk-aafhwx74.js} +38 -13
  23. package/dist/cli/{chunk-dv2z0rzf.js.map → chunk-aafhwx74.js.map} +5 -4
  24. package/dist/cli/{chunk-desvm72z.js → chunk-b680sv30.js} +22 -26
  25. package/dist/cli/chunk-b680sv30.js.map +13 -0
  26. package/dist/cli/{chunk-0w6hs479.js → chunk-beb0z983.js} +30 -18
  27. package/dist/cli/chunk-beb0z983.js.map +10 -0
  28. package/dist/cli/{chunk-1f882900.js → chunk-c5bjxq8v.js} +2 -2
  29. package/dist/cli/chunk-e1n41w9m.js +155 -0
  30. package/dist/cli/chunk-e1n41w9m.js.map +11 -0
  31. package/dist/cli/{chunk-sc93apfs.js → chunk-etckvbrh.js} +17 -17
  32. package/dist/cli/{chunk-sc93apfs.js.map → chunk-etckvbrh.js.map} +2 -2
  33. package/dist/cli/{chunk-bxwhtvw8.js → chunk-f260jdrn.js} +5 -5
  34. package/dist/cli/{chunk-74wfhv79.js → chunk-fnxgtjeq.js} +1 -1
  35. package/dist/cli/{chunk-ms9z66nz.js → chunk-gwy55cx3.js} +2 -2
  36. package/dist/cli/{chunk-ms9z66nz.js.map → chunk-gwy55cx3.js.map} +2 -2
  37. package/dist/cli/{chunk-dhwtqnnd.js → chunk-gxhcf2hs.js} +2 -2
  38. package/dist/cli/{chunk-tfycddbt.js → chunk-h0qwdbem.js} +2 -2
  39. package/dist/cli/{chunk-nbyvgnnm.js → chunk-hktq4j5r.js} +109 -49
  40. package/dist/cli/chunk-hktq4j5r.js.map +36 -0
  41. package/dist/cli/{chunk-twy7psdz.js → chunk-hn3g2b89.js} +14 -10
  42. package/dist/cli/chunk-hn3g2b89.js.map +10 -0
  43. package/dist/cli/{chunk-j1x92mx9.js → chunk-j4nfgn2j.js} +1 -1
  44. package/dist/cli/{chunk-dpy8qdjf.js → chunk-k45sq16q.js} +74 -183
  45. package/dist/cli/chunk-k45sq16q.js.map +18 -0
  46. package/dist/cli/{chunk-5cmts6dv.js → chunk-kmswjxj9.js} +7 -7
  47. package/dist/cli/{chunk-5cmts6dv.js.map → chunk-kmswjxj9.js.map} +1 -1
  48. package/dist/cli/{chunk-36bxs9c7.js → chunk-nqna8dzp.js} +27 -23
  49. package/dist/cli/chunk-nqna8dzp.js.map +10 -0
  50. package/dist/cli/{chunk-a57yh0h9.js → chunk-p834rkcn.js} +7 -7
  51. package/dist/cli/{chunk-a57yh0h9.js.map → chunk-p834rkcn.js.map} +1 -1
  52. package/dist/cli/{chunk-60hz265r.js → chunk-ppa6mg4d.js} +24 -11
  53. package/dist/cli/chunk-ppa6mg4d.js.map +10 -0
  54. package/dist/cli/{chunk-s0bdnc7v.js → chunk-q1wwrxzw.js} +5 -4
  55. package/dist/cli/chunk-q1wwrxzw.js.map +10 -0
  56. package/dist/cli/{chunk-ave1wcbr.js → chunk-qcyxkj44.js} +1 -1
  57. package/dist/cli/{chunk-124fvmzq.js → chunk-r5nm9w0c.js} +11183 -8806
  58. package/dist/cli/{chunk-124fvmzq.js.map → chunk-r5nm9w0c.js.map} +82 -66
  59. package/dist/cli/{chunk-ckrcm08h.js → chunk-rtvgmg2j.js} +48 -62
  60. package/dist/cli/{chunk-ckrcm08h.js.map → chunk-rtvgmg2j.js.map} +4 -5
  61. package/dist/cli/{chunk-297ajdcv.js → chunk-t2h8pwy1.js} +6 -6
  62. package/dist/cli/{chunk-297ajdcv.js.map → chunk-t2h8pwy1.js.map} +3 -3
  63. package/dist/cli/{chunk-gxtg6ed2.js → chunk-x60e15st.js} +21 -21
  64. package/dist/cli/{chunk-gxtg6ed2.js.map → chunk-x60e15st.js.map} +3 -3
  65. package/dist/cli/{chunk-58bt3hag.js → chunk-x7bh6d9j.js} +4 -4
  66. package/dist/cli/{chunk-hvcxng5f.js → chunk-ydqzkhjn.js} +6 -6
  67. package/dist/cli/{chunk-kdpzw38a.js → chunk-yg8ckc3g.js} +95 -7
  68. package/dist/cli/chunk-yg8ckc3g.js.map +11 -0
  69. package/dist/cli/{chunk-0pmb4tr4.js → chunk-zxd5jrgx.js} +1205 -222
  70. package/dist/cli/chunk-zxd5jrgx.js.map +44 -0
  71. package/dist/cli/index.js +78 -42
  72. package/dist/cli/index.js.map +7 -6
  73. package/dist/types/ai/changelog-components.d.ts +18 -0
  74. package/dist/types/ai/changelog-markdown.d.ts +12 -0
  75. package/dist/types/ai/openapi-components.d.ts +7 -7
  76. package/dist/types/ai/operation-details.d.ts +39 -0
  77. package/dist/types/ai/relative-links.d.ts +3 -1
  78. package/dist/types/ai/serializers.d.ts +6 -4
  79. package/dist/types/analytics/head.d.ts +4 -2
  80. package/dist/types/analytics/script.d.ts +8 -2
  81. package/dist/types/components/content/youtube.d.ts +20 -0
  82. package/dist/types/components/layout/nav-utils.d.ts +8 -0
  83. package/dist/types/components/openapi/example-panels.d.ts +33 -0
  84. package/dist/types/components/openapi/helpers.d.ts +53 -11
  85. package/dist/types/components/openapi/snippets.d.ts +7 -1
  86. package/dist/types/core/alert-diagnostics.d.ts +10 -0
  87. package/dist/types/core/builtin-tags.d.ts +16 -0
  88. package/dist/types/core/changelog-list.d.ts +48 -0
  89. package/dist/types/core/closest-match.d.ts +6 -0
  90. package/dist/types/core/code-fence-diagnostics.d.ts +18 -0
  91. package/dist/types/core/code-fences.d.ts +8 -1
  92. package/dist/types/core/config-input.d.ts +38 -6
  93. package/dist/types/core/content-assets.d.ts +16 -18
  94. package/dist/types/core/data.d.ts +6 -2
  95. package/dist/types/core/diagnostics.d.ts +14 -0
  96. package/dist/types/core/directive-diagnostics.d.ts +22 -8
  97. package/dist/types/core/former-routes.d.ts +14 -0
  98. package/dist/types/core/github.d.ts +5 -4
  99. package/dist/types/core/heading-markers.d.ts +65 -2
  100. package/dist/types/core/hidden-pages.d.ts +3 -0
  101. package/dist/types/core/includes.d.ts +23 -0
  102. package/dist/types/core/last-modified.d.ts +18 -6
  103. package/dist/types/core/links.d.ts +42 -2
  104. package/dist/types/core/load-module.d.ts +19 -4
  105. package/dist/types/core/manifest.d.ts +14 -1
  106. package/dist/types/core/markdown-mirrors.d.ts +22 -0
  107. package/dist/types/core/mcp-clients.d.ts +47 -0
  108. package/dist/types/core/mdx-elements.d.ts +32 -0
  109. package/dist/types/core/mdx-syntax.d.ts +100 -0
  110. package/dist/types/core/meta-diagnostics.d.ts +7 -0
  111. package/dist/types/core/meta.d.ts +32 -5
  112. package/dist/types/core/nav-diagnostics.d.ts +16 -2
  113. package/dist/types/core/navigation.d.ts +13 -0
  114. package/dist/types/core/ordering-prefix.d.ts +6 -0
  115. package/dist/types/core/pinned-ids.d.ts +23 -0
  116. package/dist/types/core/project-graph.d.ts +9 -1
  117. package/dist/types/core/redirect-patterns.d.ts +18 -0
  118. package/dist/types/core/relative-files.d.ts +31 -0
  119. package/dist/types/core/schema.d.ts +322 -11
  120. package/dist/types/core/sources/normalize.d.ts +101 -9
  121. package/dist/types/core/sources/types.d.ts +16 -0
  122. package/dist/types/core/static-files.d.ts +10 -0
  123. package/dist/types/core/static-host.d.ts +42 -0
  124. package/dist/types/core/svg-images.d.ts +26 -0
  125. package/dist/types/core/syntax-diagnostics.d.ts +14 -0
  126. package/dist/types/core/types.d.ts +42 -1
  127. package/dist/types/deploy/generated-files.d.ts +41 -0
  128. package/dist/types/deploy/redirects.d.ts +21 -7
  129. package/dist/types/markdown/code-title.d.ts +9 -5
  130. package/dist/types/markdown/directives.d.ts +8 -0
  131. package/dist/types/markdown/fence-language.d.ts +51 -0
  132. package/dist/types/markdown/fence-meta.d.ts +5 -0
  133. package/dist/types/markdown/github-alerts.d.ts +35 -0
  134. package/dist/types/openapi/asyncapi.d.ts +6 -5
  135. package/dist/types/openapi/checks.d.ts +10 -5
  136. package/dist/types/openapi/model.d.ts +55 -8
  137. package/dist/types/openapi/parse.d.ts +4 -1
  138. package/dist/types/ratelimit/index.d.ts +2 -0
  139. package/dist/types/ratelimit/memory.d.ts +1 -1
  140. package/dist/types/ratelimit/schema.d.ts +40 -0
  141. package/dist/types/ratelimit/unkey.d.ts +48 -0
  142. package/dist/types/sources/filesystem.d.ts +14 -4
  143. package/dist/types/sources/mdx-remote.d.ts +6 -3
  144. package/dist/types/sources/registry.d.ts +3 -3
  145. package/dist/types/theme/color.d.ts +33 -0
  146. package/dist/types/theme/palette.d.ts +122 -0
  147. package/docs/01-quickstart.mdx +1 -1
  148. package/docs/02-deployment.mdx +24 -8
  149. package/docs/04-migrating.mdx +16 -4
  150. package/docs/advanced/changelog.mdx +27 -1
  151. package/docs/advanced/skills.mdx +1 -1
  152. package/docs/cli/audit.mdx +1 -1
  153. package/docs/cli/doctor.mdx +2 -2
  154. package/docs/cli/evals.mdx +1 -1
  155. package/docs/cli/index.mdx +4 -2
  156. package/docs/cli/validate.mdx +10 -7
  157. package/docs/configuration/analytics.mdx +2 -0
  158. package/docs/configuration/customization.mdx +3 -1
  159. package/docs/configuration/index.mdx +7 -6
  160. package/docs/configuration/rate-limiting.mdx +20 -1
  161. package/docs/configuration/theming.mdx +5 -1
  162. package/docs/content/components.mdx +26 -6
  163. package/docs/content/frontmatter.mdx +1 -1
  164. package/docs/content/i18n.mdx +1 -1
  165. package/docs/content/includes.mdx +6 -2
  166. package/docs/content/index.mdx +11 -1
  167. package/docs/content/islands.mdx +2 -0
  168. package/docs/content/meta.mdx +9 -3
  169. package/docs/content/navigation.mdx +14 -6
  170. package/docs/content/sources/obsidian.mdx +1 -1
  171. package/docs/content/sources/remote-mdx.mdx +3 -1
  172. package/docs/content/syntax.mdx +76 -8
  173. package/docs/content/variables.mdx +3 -1
  174. package/docs/discoverability/agent-discovery.mdx +1 -1
  175. package/docs/discoverability/json-api.mdx +1 -1
  176. package/docs/discoverability/llms-txt.mdx +1 -1
  177. package/docs/discoverability/markdown.mdx +2 -2
  178. package/docs/discoverability/mcp.mdx +40 -8
  179. package/docs/discoverability/open-graph.mdx +1 -1
  180. package/docs/discoverability/sitemap-and-robots.mdx +1 -1
  181. package/docs/references/graphql.mdx +2 -0
  182. package/docs/references/openapi.mdx +45 -7
  183. package/package.json +4 -4
  184. package/skills/blume/SKILL.md +2 -2
  185. package/skills/blume-migrate/SKILL.md +51 -29
  186. package/skills/blume-migrate/references/docsify.md +262 -0
  187. package/skills/blume-migrate/references/docus.md +298 -0
  188. package/skills/blume-migrate/references/docusaurus.md +5 -5
  189. package/skills/blume-migrate/references/fern.md +327 -0
  190. package/skills/blume-migrate/references/fumadocs.md +1 -1
  191. package/skills/blume-migrate/references/gitbook.md +267 -0
  192. package/skills/blume-migrate/references/github-wiki.md +259 -0
  193. package/skills/blume-migrate/references/jekyll.md +253 -0
  194. package/skills/blume-migrate/references/mdbook.md +256 -0
  195. package/skills/blume-migrate/references/mintlify.md +5 -5
  196. package/skills/blume-migrate/references/mkdocs.md +236 -0
  197. package/skills/blume-migrate/references/monorepo.md +20 -8
  198. package/skills/blume-migrate/references/nextra.md +5 -5
  199. package/skills/blume-migrate/references/readme.md +257 -0
  200. package/skills/blume-migrate/references/redocly.md +198 -0
  201. package/skills/blume-migrate/references/starlight.md +3 -3
  202. package/skills/blume-migrate/references/vitepress.md +204 -0
  203. package/skills/blume-migrate/references/vuepress.md +271 -0
  204. package/skills/blume-migrate/scripts/docsify-codemod.mjs +2387 -0
  205. package/skills/blume-migrate/scripts/docus-codemod.mjs +2542 -0
  206. package/skills/blume-migrate/scripts/fern-codemod.mjs +2633 -0
  207. package/skills/blume-migrate/scripts/github-wiki-codemod.mjs +2808 -0
  208. package/skills/blume-migrate/scripts/include-excerpts.mjs +649 -0
  209. package/skills/blume-migrate/scripts/jekyll-codemod.mjs +3649 -0
  210. package/skills/blume-migrate/scripts/mintlify-codemod.mjs +17 -13
  211. package/skills/blume-migrate/scripts/mkdocs-codemod.mjs +2526 -0
  212. package/skills/blume-migrate/scripts/operation-routes.mjs +508 -0
  213. package/skills/blume-migrate/scripts/pin-heading-ids.mjs +1049 -0
  214. package/skills/blume-migrate/scripts/readme-codemod.mjs +4139 -0
  215. package/skills/blume-migrate/scripts/vitepress-codemod.mjs +1511 -0
  216. package/src/ai/changelog-components.ts +24 -0
  217. package/src/ai/changelog-markdown.ts +45 -41
  218. package/src/ai/component-markdown.ts +22 -9
  219. package/src/ai/llms.ts +36 -5
  220. package/src/ai/markdown.ts +2 -2
  221. package/src/ai/openapi-components.ts +25 -9
  222. package/src/ai/operation-details.ts +208 -0
  223. package/src/ai/relative-links.ts +48 -8
  224. package/src/ai/serializers.ts +7 -6
  225. package/src/ai/site-skill.ts +6 -2
  226. package/src/analytics/head.ts +11 -5
  227. package/src/analytics/script.ts +13 -3
  228. package/src/astro/generate.ts +47 -11
  229. package/src/astro/index.ts +1 -0
  230. package/src/astro/integration.ts +27 -1
  231. package/src/astro/mdx-source-errors.ts +46 -0
  232. package/src/astro/templates.ts +93 -189
  233. package/src/audit/checks/redirects.ts +5 -1
  234. package/src/audit/redirects.ts +36 -19
  235. package/src/audit/report.ts +22 -9
  236. package/src/audit/run.ts +12 -28
  237. package/src/audit/terms.ts +23 -0
  238. package/src/audit/url.ts +2 -2
  239. package/src/cli/build-failure.ts +256 -7
  240. package/src/cli/command-meta.ts +1 -1
  241. package/src/cli/commands/audit.ts +1 -1
  242. package/src/cli/commands/build.ts +12 -3
  243. package/src/cli/commands/check.ts +13 -2
  244. package/src/cli/commands/dev.ts +1 -1
  245. package/src/cli/commands/doctor.ts +4 -2
  246. package/src/cli/commands/init.ts +3 -3
  247. package/src/cli/commands/preview.ts +17 -1
  248. package/src/cli/commands/validate.ts +22 -10
  249. package/src/cli/prepare.ts +1 -1
  250. package/src/cli/preview-pages.ts +161 -0
  251. package/src/cli/required-secrets.ts +0 -1
  252. package/src/cli/unknown-flags.ts +1 -42
  253. package/src/components/code-copy.ts +51 -0
  254. package/src/components/code-language.ts +75 -0
  255. package/src/components/content/AccordionItem.astro +16 -1
  256. package/src/components/content/Callout.astro +11 -1
  257. package/src/components/content/Changelog.astro +189 -0
  258. package/src/components/content/CodeBlock.astro +8 -11
  259. package/src/components/content/Tabs.astro +16 -2
  260. package/src/components/content/YouTube.astro +6 -4
  261. package/src/components/content/code-block.ts +33 -0
  262. package/src/components/content/content-strings.ts +5 -1
  263. package/src/components/content/inline-markdown.ts +35 -8
  264. package/src/components/content/youtube.ts +49 -7
  265. package/src/components/layout/Analytics.astro +5 -1
  266. package/src/components/layout/NavTree.astro +8 -3
  267. package/src/components/layout/PageActions.astro +81 -23
  268. package/src/components/layout/RootLayout.astro +35 -32
  269. package/src/components/layout/SiteFooter.astro +31 -8
  270. package/src/components/layout/nav-utils.ts +14 -0
  271. package/src/components/layout/search/pagefind.ts +2 -1
  272. package/src/components/openapi/Operation.astro +4 -0
  273. package/src/components/openapi/PanelTabs.astro +9 -3
  274. package/src/components/openapi/RequestBody.astro +42 -8
  275. package/src/components/openapi/RequestPanel.astro +34 -53
  276. package/src/components/openapi/example-panels.ts +110 -0
  277. package/src/components/openapi/helpers.ts +195 -20
  278. package/src/components/openapi/operation-model.ts +63 -26
  279. package/src/components/openapi/request.ts +13 -0
  280. package/src/components/openapi/snippets.ts +14 -2
  281. package/src/core/alert-diagnostics.ts +89 -0
  282. package/src/core/builtin-tags.ts +76 -0
  283. package/src/core/changelog-list.ts +87 -0
  284. package/src/core/closest-match.ts +41 -0
  285. package/src/core/code-fence-diagnostics.ts +267 -0
  286. package/src/core/code-fences.ts +43 -7
  287. package/src/core/component-diagnostics.ts +65 -12
  288. package/src/core/config-input.ts +39 -5
  289. package/src/core/config.ts +46 -1
  290. package/src/core/content-assets.ts +64 -144
  291. package/src/core/data.ts +10 -3
  292. package/src/core/diagnostics.ts +36 -3
  293. package/src/core/directive-diagnostics.ts +265 -47
  294. package/src/core/former-routes.ts +46 -0
  295. package/src/core/git-ignored.ts +53 -0
  296. package/src/core/github.ts +5 -4
  297. package/src/core/heading-markers.ts +156 -2
  298. package/src/core/hidden-pages.ts +64 -0
  299. package/src/core/includes.ts +181 -41
  300. package/src/core/last-modified.ts +47 -10
  301. package/src/core/links.ts +438 -83
  302. package/src/core/load-module.ts +98 -9
  303. package/src/core/manifest.ts +12 -9
  304. package/src/core/markdown-mirrors.ts +52 -0
  305. package/src/core/mcp-clients.ts +87 -0
  306. package/src/core/mdx-elements.ts +153 -0
  307. package/src/core/mdx-syntax.ts +377 -0
  308. package/src/core/meta-diagnostics.ts +134 -0
  309. package/src/core/meta.ts +154 -55
  310. package/src/core/nav-diagnostics.ts +41 -4
  311. package/src/core/navigation.ts +70 -10
  312. package/src/core/ordering-prefix.ts +22 -2
  313. package/src/core/pinned-ids.ts +141 -0
  314. package/src/core/project-graph.ts +221 -29
  315. package/src/core/redirect-patterns.ts +41 -0
  316. package/src/core/relative-files.ts +145 -0
  317. package/src/core/schema.ts +110 -6
  318. package/src/core/sources/mdx-remote.ts +75 -14
  319. package/src/core/sources/normalize.ts +885 -179
  320. package/src/core/sources/notion.ts +5 -4
  321. package/src/core/sources/obsidian.ts +2 -5
  322. package/src/core/sources/types.ts +17 -0
  323. package/src/core/static-files.ts +40 -0
  324. package/src/core/static-host.ts +125 -0
  325. package/src/core/svg-images.ts +98 -0
  326. package/src/core/syntax-diagnostics.ts +491 -0
  327. package/src/core/types.ts +43 -1
  328. package/src/deploy/artifacts.ts +2 -28
  329. package/src/deploy/generated-files.ts +128 -0
  330. package/src/deploy/platforms/vercel.ts +25 -1
  331. package/src/deploy/redirects.ts +43 -16
  332. package/src/deploy/sitemap.ts +2 -1
  333. package/src/markdown/base-links.ts +87 -6
  334. package/src/markdown/code-title.ts +29 -13
  335. package/src/markdown/content-assets.ts +210 -0
  336. package/src/markdown/directives.ts +69 -5
  337. package/src/markdown/fence-language.ts +106 -0
  338. package/src/markdown/fence-meta.ts +42 -0
  339. package/src/markdown/github-alerts.ts +172 -0
  340. package/src/markdown/heading-anchors.ts +265 -56
  341. package/src/markdown/include.ts +30 -3
  342. package/src/markdown/index.ts +145 -40
  343. package/src/markdown/prompt-text.ts +95 -0
  344. package/src/markdown/relative-links.ts +37 -2
  345. package/src/markdown/route-snapshot.ts +22 -12
  346. package/src/markdown/svg-fallback.ts +51 -0
  347. package/src/markdown/title-heading.ts +134 -0
  348. package/src/migrate/migrate.ts +127 -2
  349. package/src/og/card.ts +14 -4
  350. package/src/openapi/asyncapi.ts +14 -6
  351. package/src/openapi/checks.ts +160 -15
  352. package/src/openapi/model.ts +172 -33
  353. package/src/openapi/parse.ts +13 -4
  354. package/src/openapi/render-mdx.ts +55 -9
  355. package/src/openapi/source.ts +87 -6
  356. package/src/ratelimit/index.ts +2 -0
  357. package/src/ratelimit/memory.ts +1 -1
  358. package/src/ratelimit/runtime.ts +81 -0
  359. package/src/ratelimit/schema.ts +2 -0
  360. package/src/ratelimit/unkey.ts +61 -0
  361. package/src/registry/eject.ts +0 -1
  362. package/src/registry/registry.ts +1 -1
  363. package/src/search/documents.ts +10 -2
  364. package/src/sources/filesystem.ts +28 -3
  365. package/src/sources/mdx-remote.ts +7 -15
  366. package/src/theme/color.ts +14 -0
  367. package/src/theme/entry.ts +22 -1
  368. package/src/theme/palette.ts +74 -1
  369. package/dist/cli/chunk-0pmb4tr4.js.map +0 -35
  370. package/dist/cli/chunk-0w6hs479.js.map +0 -10
  371. package/dist/cli/chunk-150jk7km.js +0 -52
  372. package/dist/cli/chunk-150jk7km.js.map +0 -10
  373. package/dist/cli/chunk-36bxs9c7.js.map +0 -10
  374. package/dist/cli/chunk-53jqw8z0.js +0 -89
  375. package/dist/cli/chunk-53jqw8z0.js.map +0 -10
  376. package/dist/cli/chunk-60hz265r.js.map +0 -10
  377. package/dist/cli/chunk-desvm72z.js.map +0 -14
  378. package/dist/cli/chunk-dpy8qdjf.js.map +0 -17
  379. package/dist/cli/chunk-es8h0v3j.js +0 -441
  380. package/dist/cli/chunk-es8h0v3j.js.map +0 -11
  381. package/dist/cli/chunk-kdpzw38a.js.map +0 -11
  382. package/dist/cli/chunk-nbyvgnnm.js.map +0 -36
  383. package/dist/cli/chunk-s0bdnc7v.js.map +0 -10
  384. package/dist/cli/chunk-twy7psdz.js.map +0 -10
  385. package/dist/cli/chunk-ydp3065p.js +0 -64
  386. package/dist/cli/chunk-ydp3065p.js.map +0 -10
  387. /package/dist/cli/{chunk-y4msrwm9.js.map → chunk-080mfch6.js.map} +0 -0
  388. /package/dist/cli/{chunk-9qzgg6mj.js.map → chunk-34wap3ed.js.map} +0 -0
  389. /package/dist/cli/{chunk-1f882900.js.map → chunk-c5bjxq8v.js.map} +0 -0
  390. /package/dist/cli/{chunk-bxwhtvw8.js.map → chunk-f260jdrn.js.map} +0 -0
  391. /package/dist/cli/{chunk-74wfhv79.js.map → chunk-fnxgtjeq.js.map} +0 -0
  392. /package/dist/cli/{chunk-dhwtqnnd.js.map → chunk-gxhcf2hs.js.map} +0 -0
  393. /package/dist/cli/{chunk-tfycddbt.js.map → chunk-h0qwdbem.js.map} +0 -0
  394. /package/dist/cli/{chunk-j1x92mx9.js.map → chunk-j4nfgn2j.js.map} +0 -0
  395. /package/dist/cli/{chunk-ave1wcbr.js.map → chunk-qcyxkj44.js.map} +0 -0
  396. /package/dist/cli/{chunk-58bt3hag.js.map → chunk-x7bh6d9j.js.map} +0 -0
  397. /package/dist/cli/{chunk-hvcxng5f.js.map → chunk-ydqzkhjn.js.map} +0 -0
@@ -12,11 +12,13 @@ import {
12
12
  buildFileRouteIndex,
13
13
  isIndexFileName,
14
14
  resolveRelativeHref,
15
+ resolveRootFileHref,
15
16
  routeOfLinkedFile,
16
17
  } from "../core/links.ts";
17
18
  import type { RelativeLinkBase } from "../core/links.ts";
18
19
  import { localizeHref, servesRoute } from "../core/locale-links.ts";
19
20
  import type { BlumeProject } from "../core/project-graph.ts";
21
+ import { resolveDocsCollection } from "../core/sources/collection.ts";
20
22
  import { extractLinks } from "../core/sources/normalize.ts";
21
23
 
22
24
  /**
@@ -33,7 +35,9 @@ import { extractLinks } from "../core/sources/normalize.ts";
33
35
  * `basePath` prefix, a public file (`/spec.pdf`) or image the
34
36
  * `deployment.base` alone, and, on a page in a prefixed locale, a page link
35
37
  * that locale's copy of the route when it is served
36
- * (`components/layout/LocaleLinks.astro`). Inline links, images, component
38
+ * (`components/layout/LocaleLinks.astro`). A root-relative link to a content
39
+ * file (`/guide/new.md`) lands on that file's page, as on the rendered page
40
+ * (`resolveRootFileHref`). Inline links, images, component
37
41
  * `href`s, and reference definitions are rewritten; fenced and inline code,
38
42
  * relative images and asset links, and external URLs are left as written.
39
43
  */
@@ -64,6 +68,13 @@ const MAYBE_RELATIVE =
64
68
  const MAYBE_PAGE_LINK =
65
69
  /\]\([\t ]*<?(?![a-z][\d+.a-z-]*:|\/\/|#)|^ {0,3}\[[^\]\n]+\]:[\t ]*<?(?![a-z][\d+.a-z-]*:|\/\/|#)|\shref=["'](?![a-z][\d+.a-z-]*:|\/\/|#)/imu;
66
70
 
71
+ /**
72
+ * Whether a text could hold a root-relative link to a Markdown file
73
+ * (`[x](/guide/new.md)`), which lands on that file's page whatever the base.
74
+ */
75
+ const MAYBE_ROOT_FILE =
76
+ /(?:\]\(|\]:|\shref=["'])[\t ]*<?\/(?!\/)[^\s"'()<>]*\.mdx?\b/imu;
77
+
67
78
  interface Splice {
68
79
  column: number;
69
80
  length: number;
@@ -85,11 +96,12 @@ const spliceLine = (line: string, splices: readonly Splice[]): string => {
85
96
  /**
86
97
  * Every routed splice in `text`, by 0-based line: links, images, and
87
98
  * reference definitions, as `extractLinks` finds them. An image target goes
88
- * through `imaged`, everything else through `routed`.
99
+ * through `imaged`, everything else through `routed`, told whether it's a
100
+ * raw HTML element's.
89
101
  */
90
102
  const collectSplices = (
91
103
  text: string,
92
- routed: (target: string) => string | undefined,
104
+ routed: (target: string, raw: boolean) => string | undefined,
93
105
  imaged: (target: string) => string | undefined
94
106
  ): Map<number, Splice[]> => {
95
107
  const splices = new Map<number, Splice[]>();
@@ -97,11 +109,13 @@ const collectSplices = (
97
109
  splices.set(index, [...(splices.get(index) ?? []), splice]);
98
110
  };
99
111
  for (const link of extractLinks(text)) {
100
- const route = (link.image ? imaged : routed)(link.target);
112
+ const route = link.image
113
+ ? imaged(link.target)
114
+ : routed(link.target, link.raw === true);
101
115
  if (route !== undefined) {
102
116
  add(link.line - 1, {
103
117
  column: link.column - 1,
104
- length: link.target.length,
118
+ length: link.sourceLength ?? link.target.length,
105
119
  text: route,
106
120
  });
107
121
  }
@@ -120,6 +134,10 @@ export const relativeLinkRewriter = (
120
134
  ): RelativeLinkRewriter => {
121
135
  const { basePath, deployment, i18n } = project.config;
122
136
  const deployBase = normalizeBasePath(deployment.options.base);
137
+ const contentRoot = resolveDocsCollection(
138
+ project.config,
139
+ project.context.root
140
+ ).base;
123
141
  const localeTokens = i18n
124
142
  ? ["$", ...i18n.locales.map((locale) => locale.code)]
125
143
  : [];
@@ -179,7 +197,10 @@ export const relativeLinkRewriter = (
179
197
  deployBase !== "" || basePath !== "" || localize !== undefined;
180
198
  if (
181
199
  !(page.sourcePath || rootLinks) ||
182
- !(rootLinks ? MAYBE_PAGE_LINK : MAYBE_RELATIVE).test(text)
200
+ !(
201
+ (rootLinks ? MAYBE_PAGE_LINK : MAYBE_RELATIVE).test(text) ||
202
+ MAYBE_ROOT_FILE.test(text)
203
+ )
183
204
  ) {
184
205
  return text;
185
206
  }
@@ -197,8 +218,27 @@ export const relativeLinkRewriter = (
197
218
  const href = localize ? localizeHref(based, localize) : based;
198
219
  return href === target ? undefined : href;
199
220
  };
200
- const routed = (target: string): string | undefined =>
201
- isInternalPath(target) ? rooted(target) : relative?.(target);
221
+ // A root-relative link naming a content file (`/guide/new.md`) means
222
+ // that file's page, as a relative one does; a raw `<a href>` keeps it,
223
+ // as the rendered page's does.
224
+ const rootFile = (target: string): string | undefined => {
225
+ const route = resolveRootFileHref(target, {
226
+ contentRoot,
227
+ deployBase,
228
+ routeOfFile: (file) => fileRoutes.bySource.get(file),
229
+ });
230
+ if (route === undefined) {
231
+ return undefined;
232
+ }
233
+ const based = mountBasePath(deployBase, route);
234
+ return localize ? localizeHref(based, localize) : based;
235
+ };
236
+ const routed = (target: string, raw: boolean): string | undefined => {
237
+ if (!isInternalPath(target)) {
238
+ return relative?.(target);
239
+ }
240
+ return (raw ? undefined : rootFile(target)) ?? rooted(target);
241
+ };
202
242
  // An image is always a file: `public/` (or a generated asset endpoint),
203
243
  // served under the deployment base but never `basePath`. A relative one
204
244
  // is left to `core/content-assets.ts`.
@@ -1,5 +1,6 @@
1
1
  import type { BlumeProject } from "../core/project-graph.ts";
2
2
  import { isOpenApiSource } from "../openapi/source.ts";
3
+ import { changelogComponentSerializers } from "./changelog-components.ts";
3
4
  import type { ComponentMarkdown } from "./component-markdown.ts";
4
5
  import { exampleComponentSerializers } from "./component-markdown.ts";
5
6
  import { openapiComponentSerializers } from "./openapi-components.ts";
@@ -8,18 +9,18 @@ import { openapiComponentSerializers } from "./openapi-components.ts";
8
9
  * Every serializer a project brings to a downlevel pass, layered once for all
9
10
  * the agent surfaces (`<route>.md`, llms-full.txt, the search and assistant
10
11
  * corpora): the built-in families that read project data — examples, then the
11
- * API reference — under the user's `agents.markdownComponents`, which is spread
12
- * last so an entry of the same name still wins. One layering, so a new family
13
- * or a new surface cannot leave one consumer printing raw JSX.
12
+ * API reference, then the changelog index — under the user's
13
+ * `agents.markdownComponents`, which is spread last so an entry of the same
14
+ * name still wins. One layering, so a new family or a new surface cannot leave
15
+ * one consumer printing raw JSX.
14
16
  */
15
- export const projectComponentSerializers = (
16
- project: Pick<BlumeProject, "config" | "examples" | "sources">
17
- ) =>
17
+ export const projectComponentSerializers = (project: BlumeProject) =>
18
18
  ({
19
19
  ...exampleComponentSerializers(project.examples ?? {}),
20
20
  ...openapiComponentSerializers(
21
21
  project.sources.find(isOpenApiSource)?.openApiData() ?? {},
22
22
  project.config.deployment.options.base
23
23
  ),
24
+ ...changelogComponentSerializers(project),
24
25
  ...project.config.agents.markdownComponents,
25
26
  }) satisfies Record<string, ComponentMarkdown>;
@@ -6,6 +6,8 @@ import {
6
6
  } from "../core/base-path.ts";
7
7
  import { CHANGELOG_INDEX_ROUTE } from "../core/changelog-index.ts";
8
8
  import { discoverPagesSync } from "../core/custom-pages.ts";
9
+ import { isHiddenPage } from "../core/hidden-pages.ts";
10
+ import { metaDescription } from "../core/manifest.ts";
9
11
  import type { BlumeProject } from "../core/project-graph.ts";
10
12
  import { absoluteUrl } from "../core/site-url.ts";
11
13
  import type { NavNode, PageRecord } from "../core/types.ts";
@@ -67,7 +69,7 @@ const mappedPages = (project: BlumeProject): PageRecord[] => {
67
69
  !(
68
70
  page.meta.ai.exclude ||
69
71
  page.meta.draft ||
70
- page.meta.sidebar.hidden ||
72
+ isHiddenPage(page, project.graph) ||
71
73
  page.meta.seo.noindex ||
72
74
  page.version !== "" ||
73
75
  page.source.name === "openapi" ||
@@ -104,7 +106,9 @@ class PageMap {
104
106
  return [];
105
107
  }
106
108
  this.#listed.add(route);
107
- const summary = page.description ? `: ${page.description}` : "";
109
+ // As `llms.txt` lists it: the page's meta description.
110
+ const description = metaDescription(page);
111
+ const summary = description ? `: ${description}` : "";
108
112
  return [`- [${linkText(page.title)}](${this.#url(route)})${summary}`];
109
113
  }
110
114
 
@@ -50,7 +50,8 @@ export type HeadNode =
50
50
  */
51
51
  // oxlint-disable-next-line complexity -- each adapter has one exhaustive, type-narrowed dispatch case
52
52
  const adapterScripts = (
53
- adapter: Exclude<AnalyticsAdapter, { kind: "vercel" }>
53
+ adapter: Exclude<AnalyticsAdapter, { kind: "vercel" }>,
54
+ base: string
54
55
  ): HeadScript[] => {
55
56
  switch (adapter.kind) {
56
57
  case "adobe": {
@@ -114,7 +115,7 @@ const adapterScripts = (
114
115
  return segmentHead(adapter.options);
115
116
  }
116
117
  default: {
117
- return scriptHead(adapter.options);
118
+ return scriptHead(adapter.options, base);
118
119
  }
119
120
  }
120
121
  };
@@ -122,9 +123,14 @@ const adapterScripts = (
122
123
  /**
123
124
  * Map the configured adapters to what `Analytics.astro` renders, in declared
124
125
  * order. A second `vercel()` is dropped — two copies of the component would
125
- * count every pageview twice — so the first one keeps its position.
126
+ * count every pageview twice — so the first one keeps its position. `base` is
127
+ * the deployment base a `script()` adapter's root-relative `src` is served
128
+ * under.
126
129
  */
127
- export const analyticsHead = (adapters: AnalyticsAdapter[]): HeadNode[] => {
130
+ export const analyticsHead = (
131
+ adapters: AnalyticsAdapter[],
132
+ base = "/"
133
+ ): HeadNode[] => {
128
134
  const nodes: HeadNode[] = [];
129
135
  let hasVercel = false;
130
136
  for (const adapter of adapters) {
@@ -134,7 +140,7 @@ export const analyticsHead = (adapters: AnalyticsAdapter[]): HeadNode[] => {
134
140
  nodes.push({ props: adapter.options, type: "vercel" });
135
141
  }
136
142
  } else {
137
- for (const tag of adapterScripts(adapter)) {
143
+ for (const tag of adapterScripts(adapter, base)) {
138
144
  nodes.push({ ...tag, type: "script" });
139
145
  }
140
146
  }
@@ -2,6 +2,7 @@ import { z } from "zod";
2
2
 
3
3
  import type { AdapterDescriptor } from "../core/adapter.ts";
4
4
  import { adapterDescriptorSchema } from "../core/adapter.ts";
5
+ import { normalizeBasePath, withBasePath } from "../core/base-path.ts";
5
6
  import type { HeadScript } from "./head.ts";
6
7
 
7
8
  /** Options for {@link script}; set exactly one of `src` or `content`. */
@@ -46,8 +47,17 @@ export const script = (options: ScriptOptions): ScriptAdapter => ({
46
47
  runtimeDeps: [],
47
48
  });
48
49
 
49
- /** The tag. Explicit `src`/`strategy` win over a same-named spread attribute. */
50
- export const scriptHead = (options: ScriptOptions): HeadScript[] => {
50
+ /**
51
+ * The tag. Explicit `src`/`strategy` win over a same-named spread attribute.
52
+ * A root-relative `src` (`/js/x.js`, a file in `public/`) is served under the
53
+ * deployment `base`, so it gets the base the way a link does; one already
54
+ * under it is left alone, and absolute and protocol-relative URLs pass
55
+ * through.
56
+ */
57
+ export const scriptHead = (
58
+ options: ScriptOptions,
59
+ base: string
60
+ ): HeadScript[] => {
51
61
  const attributes: HeadScript["attributes"] = { ...options.attributes };
52
62
  if (!options.src) {
53
63
  return [{ attributes, content: options.content ?? "" }];
@@ -55,6 +65,6 @@ export const scriptHead = (options: ScriptOptions): HeadScript[] => {
55
65
  if (options.strategy) {
56
66
  attributes[options.strategy] = true;
57
67
  }
58
- attributes.src = options.src;
68
+ attributes.src = withBasePath(normalizeBasePath(base), options.src);
59
69
  return [{ attributes, content: null }];
60
70
  };
@@ -37,6 +37,7 @@ import { normalizeBasePath, withBasePath } from "../core/base-path.ts";
37
37
  import { withChangelogIndexText } from "../core/changelog-index.ts";
38
38
  import {
39
39
  missingExampleDiagnostics,
40
+ unknownPropDiagnostics,
40
41
  validateUsedComponents,
41
42
  } from "../core/component-diagnostics.ts";
42
43
  import {
@@ -46,7 +47,7 @@ import {
46
47
  import type { ComponentOverrideAnalysis } from "../core/component-overrides.ts";
47
48
  import {
48
49
  collectContentAssets,
49
- rewriteRelativeImages,
50
+ rewriteRelativeAssets,
50
51
  } from "../core/content-assets.ts";
51
52
  import type {
52
53
  BlumeBanner,
@@ -55,8 +56,13 @@ import type {
55
56
  BlumeFavicon,
56
57
  BlumeLogo,
57
58
  } from "../core/data.ts";
58
- import { BlumeError } from "../core/diagnostics.ts";
59
+ import {
60
+ BlumeError,
61
+ enrichDiagnostic,
62
+ formatDiagnostic,
63
+ } from "../core/diagnostics.ts";
59
64
  import { writeTextAtomic } from "../core/fs-atomic.ts";
65
+ import { gitIgnoredPaths } from "../core/git-ignored.ts";
60
66
  import {
61
67
  apiUrl as githubApiUrl,
62
68
  editBaseUrl as githubEditBaseUrl,
@@ -67,6 +73,7 @@ import { resolveFallbackLocale } from "../core/i18n.ts";
67
73
  import { buildIncludeGraph } from "../core/includes.ts";
68
74
  import { resolveLocalizable } from "../core/localizable.ts";
69
75
  import {
76
+ validateMcpClientIcons,
70
77
  validateNavTargets,
71
78
  validateSearchPopularIcons,
72
79
  } from "../core/nav-diagnostics.ts";
@@ -79,6 +86,7 @@ import { trimChar } from "../core/trim.ts";
79
86
  import { resolveTsconfigAliases } from "../core/tsconfig-aliases.ts";
80
87
  import type { Diagnostic, Navigation } from "../core/types.ts";
81
88
  import { getBlumeVersion } from "../core/version.ts";
89
+ import { servedFiles } from "../deploy/generated-files.ts";
82
90
  import { buildRssFeeds, renderRssFeed } from "../deploy/rss.ts";
83
91
  import {
84
92
  languageIconCss,
@@ -697,7 +705,7 @@ export const collectStaged = (project: BlumeProject): Map<string, string> => {
697
705
  // Point it at the served original instead — the same rewrite the
698
706
  // agent-facing Markdown gets.
699
707
  const text = page.sourcePath
700
- ? rewriteRelativeImages({
708
+ ? rewriteRelativeAssets({
701
709
  deployBase: project.config.deployment.options.base,
702
710
  projectRoot: project.context.root,
703
711
  source: page.body.text,
@@ -1021,7 +1029,9 @@ const resolveFooter = (
1021
1029
  ): ResolvedConfig["footer"] | null => {
1022
1030
  const { footer } = config;
1023
1031
  return footer &&
1024
- (footer.links.length > 0 || Object.keys(footer.socials).length > 0)
1032
+ (footer.copyright !== undefined ||
1033
+ footer.links.length > 0 ||
1034
+ Object.keys(footer.socials).length > 0)
1025
1035
  ? footer
1026
1036
  : null;
1027
1037
  };
@@ -1120,9 +1130,22 @@ export const buildRuntimeData = (project: BlumeProject): string => {
1120
1130
  const editBase = github ? githubEditBaseUrl(github) : null;
1121
1131
  const logo = resolveLogo(project);
1122
1132
  const ogLogo = resolveOgMark(project, logo?.svg);
1133
+ // Files git ignores (a generated reference in a gitignored folder) are
1134
+ // never in the repository, so an edit link would 404. One git call covers
1135
+ // every page an edit link is built for.
1136
+ const ignored = editBase
1137
+ ? gitIgnoredPaths(
1138
+ context.root,
1139
+ manifest.routes.flatMap((route) =>
1140
+ route.editUrl === undefined && route.sourcePath
1141
+ ? [route.sourcePath]
1142
+ : []
1143
+ )
1144
+ )
1145
+ : new Set<string>();
1123
1146
 
1124
1147
  const editUrlFor = (sourcePath?: string): string | null => {
1125
- if (!(editBase && sourcePath)) {
1148
+ if (!(editBase && sourcePath) || ignored.has(sourcePath)) {
1126
1149
  return null;
1127
1150
  }
1128
1151
  const rel = relative(context.root, sourcePath).split("\\").join("/");
@@ -1253,6 +1276,7 @@ export const buildRuntimeData = (project: BlumeProject): string => {
1253
1276
  logo,
1254
1277
  mcp: config.agents.mcp.enabled
1255
1278
  ? {
1279
+ clients: config.agents.mcp.clients,
1256
1280
  name: config.agents.mcp.name ?? config.title,
1257
1281
  route: config.agents.mcp.route,
1258
1282
  }
@@ -2194,6 +2218,7 @@ export const generateRuntime = async (
2194
2218
  context,
2195
2219
  filesystem: hasFilesystemSource,
2196
2220
  staged: hasStaged,
2221
+ unparsable: project.unparsable,
2197
2222
  })
2198
2223
  ),
2199
2224
  write(
@@ -2308,7 +2333,6 @@ export const generateRuntime = async (
2308
2333
  exportEpub,
2309
2334
  exportPdf,
2310
2335
  needsReact,
2311
- staged: hasStaged,
2312
2336
  })
2313
2337
  );
2314
2338
  }
@@ -2417,17 +2441,24 @@ export const generateRuntime = async (
2417
2441
 
2418
2442
  // Missing-navigation-target check, now that every servable route is known
2419
2443
  // (see `navTargetRoutes`).
2420
- // Curated `search.popular` icons live outside the navigation model, so they
2421
- // miss `validateNavIcons` in the graph build — they're checked here too,
2422
- // where the search config is known. A typo otherwise just renders the
2423
- // default glyph.
2444
+ // Curated `search.popular` icons and custom MCP clients' icons live outside
2445
+ // the navigation model, so they miss `validateNavIcons` in the graph build —
2446
+ // they're checked here too, where their config is known. A typo otherwise
2447
+ // just renders the default glyph, or no icon at all.
2424
2448
  warnings.push(
2425
2449
  ...[
2426
2450
  ...validateNavTargets(
2427
2451
  project.graph.navigation,
2428
- navTargetRoutes(project, pages)
2452
+ navTargetRoutes(project, pages),
2453
+ servedFiles(project)
2429
2454
  ),
2430
2455
  ...validateSearchPopularIcons(config.search.popular),
2456
+ ...(config.agents.mcp.enabled
2457
+ ? validateMcpClientIcons(
2458
+ config.agents.mcp.clients,
2459
+ config.i18n?.defaultLocale
2460
+ )
2461
+ : []),
2431
2462
  ].map(diagnosticWarning)
2432
2463
  );
2433
2464
 
@@ -2449,6 +2480,11 @@ export const generateRuntime = async (
2449
2480
  context.root,
2450
2481
  knownComponentTags
2451
2482
  ).map(diagnosticWarning),
2483
+ // Printed whole, like a scan diagnostic, so the line it names shows.
2484
+ ...unknownPropDiagnostics(project.graph.pages, knownComponentTags).map(
2485
+ (diagnostic) =>
2486
+ formatDiagnostic(enrichDiagnostic(diagnostic), context.root)
2487
+ ),
2452
2488
  ...dependencyWarnings
2453
2489
  );
2454
2490
  if (hasScalarReferences(config)) {
@@ -2,6 +2,7 @@ export { withAdapterRoot } from "./adapter-root.ts";
2
2
  export { generateRuntime, prerenderDepsPlugin } from "./generate.ts";
3
3
  export { includeHmrPlugin } from "./include-hmr.ts";
4
4
  export { withIncludeRefresh } from "./include-refresh.ts";
5
+ export { mdxSourceErrorsPlugin } from "./mdx-source-errors.ts";
5
6
  export { variablesVitePlugin } from "./variables.ts";
6
7
  export type { GenerateResult } from "./generate.ts";
7
8
  export {
@@ -1,10 +1,12 @@
1
+ import { Server } from "node:http";
1
2
  import type { IncomingMessage, ServerResponse } from "node:http";
2
3
  import { fileURLToPath } from "node:url";
3
4
 
4
- import type { AstroIntegration } from "astro";
5
+ import type { AstroConfig, AstroIntegration } from "astro";
5
6
  import { join, relative, resolve } from "pathe";
6
7
 
7
8
  import { loadEnvFiles } from "../cli/env.ts";
9
+ import { normalizeBasePath } from "../core/base-path.ts";
8
10
  import type { CustomPageRoute } from "../core/custom-pages.ts";
9
11
  import { enrichDiagnostic } from "../core/diagnostics.ts";
10
12
  import { POWERED_BY_HEADERS } from "../core/powered-by.ts";
@@ -15,6 +17,11 @@ import {
15
17
  requestPath,
16
18
  } from "../core/redirect-patterns.ts";
17
19
  import type { CompiledRedirect } from "../core/redirect-patterns.ts";
20
+ import {
21
+ answerRequestsFirst,
22
+ publicFolder,
23
+ trailingSlashAnswer,
24
+ } from "../core/static-host.ts";
18
25
  import type { Diagnostic } from "../core/types.ts";
19
26
  import { publishBuildArtifacts } from "../deploy/artifacts.ts";
20
27
  import type { ArtifactLogger } from "../deploy/artifacts.ts";
@@ -492,6 +499,9 @@ export const blumeIntegration = (
492
499
  // The Astro root, kept for the build-artifacts scan: `astro:build:done`
493
500
  // receives no config.
494
501
  let astroRoot: URL | null = null;
502
+ // What the dev server's trailing-slash handling needs from the config
503
+ // (see `core/static-host.ts`): the base the URLs carry, and `public/`.
504
+ let staticHost: Pick<AstroConfig, "base" | "publicDir"> | null = null;
495
505
  return {
496
506
  hooks: {
497
507
  "astro:build:done": async ({ dir, logger }) => {
@@ -511,6 +521,7 @@ export const blumeIntegration = (
511
521
  },
512
522
  "astro:config:done": ({ config, injectTypes }) => {
513
523
  astroRoot = config.root;
524
+ staticHost = config;
514
525
  const from = fileURLToPath(
515
526
  codegenDir ?? defaultCodegenDir(config.root)
516
527
  );
@@ -587,6 +598,21 @@ export const blumeIntegration = (
587
598
  route: "",
588
599
  });
589
600
  }
601
+ // A trailing slash is answered the way a static host answers it,
602
+ // ahead of Astro's own middleware, which would 404 it (see
603
+ // `core/static-host.ts`). They're unshifted after this hook runs,
604
+ // so this goes on the HTTP server instead.
605
+ if (staticHost && server.httpServer instanceof Server) {
606
+ const base = normalizeBasePath(staticHost.base);
607
+ const isPublicFolder = publicFolder(
608
+ fileURLToPath(staticHost.publicDir)
609
+ );
610
+ answerRequestsFirst(
611
+ server.httpServer,
612
+ (request) => trailingSlashAnswer(request, base, isPublicFolder),
613
+ options.poweredBy === false ? {} : POWERED_BY_HEADERS
614
+ );
615
+ }
590
616
  },
591
617
  },
592
618
  name: "blume",
@@ -0,0 +1,46 @@
1
+ /**
2
+ * A Vite plugin that names an `.mdx` page's file on an error the page throws
3
+ * while it renders. A `{…}` expression that reads a name nothing defines
4
+ * (`{user.name}`, or `{{version}}` on a site without that variable) compiles,
5
+ * then throws a `ReferenceError` from inside the page's module, and Astro pins
6
+ * it on the route that rendered it: the catch-all every page shares. Setting
7
+ * the error's `id`, as Vite does for a module that fails, points it at the
8
+ * page's file instead, so the build report can find the expression (see
9
+ * `cli/build-failure.ts`) and the dev overlay shows the page.
10
+ *
11
+ * It runs after `@astrojs/mdx` and wraps the module's `MDXContent`, the
12
+ * function the compiler emits for the body and Astro's `Content` export calls
13
+ * by name. A module without one is left alone.
14
+ */
15
+
16
+ /** The Vite plugin slice this needs (structurally typed, like Blume's other plugins). */
17
+ export interface MdxSourceErrorsPlugin {
18
+ enforce: "post";
19
+ name: string;
20
+ transform: (code: string, id: string) => { code: string; map: null } | null;
21
+ }
22
+
23
+ const MDX_CONTENT = /^\s*function MDXContent\(/mu;
24
+
25
+ /** The statement that wraps `MDXContent` so its errors carry `id`. */
26
+ const wrapContent = (id: string): string => `
27
+ MDXContent = ((render, id) => function MDXContent(props) {
28
+ try {
29
+ return render(props);
30
+ } catch (error) {
31
+ if (error instanceof Error && !("id" in error)) error.id = id;
32
+ throw error;
33
+ }
34
+ })(MDXContent, ${JSON.stringify(id)});`;
35
+
36
+ export const mdxSourceErrorsPlugin = (): MdxSourceErrorsPlugin => ({
37
+ enforce: "post",
38
+ name: "blume:mdx-source-errors",
39
+ // A query (`?astroPropagatedAssets`) marks another module made from the
40
+ // same file, which holds no body.
41
+ transform(code, id) {
42
+ return id.endsWith(".mdx") && MDX_CONTENT.test(code)
43
+ ? { code: code + wrapContent(id), map: null }
44
+ : null;
45
+ },
46
+ });