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
@@ -0,0 +1,128 @@
1
+ import { join } from "pathe";
2
+
3
+ import { buildAgentReadability } from "../ai/agent-readability.ts";
4
+ import {
5
+ AI_CATALOG_PATH,
6
+ ARD_MANIFEST_PATH,
7
+ buildAiCatalog,
8
+ } from "../ai/ai-catalog.ts";
9
+ import { API_CATALOG_PATH, buildApiCatalog } from "../ai/api-catalog.ts";
10
+ import {
11
+ API_NAVIGATION_PATH,
12
+ API_PAGES_PATH,
13
+ OPENAPI_PATH,
14
+ } from "../ai/api/paths.ts";
15
+ import { SKILL_MD_PATH } from "../ai/site-skill.ts";
16
+ import { AGENT_SKILLS_INDEX_PATH, servesSkillsIndex } from "../ai/skills.ts";
17
+ import type { SkillArtifact } from "../ai/skills.ts";
18
+ import {
19
+ buildSignaturesDirectory,
20
+ SIGNATURES_DIRECTORY_PATH,
21
+ } from "../ai/web-bot-auth.ts";
22
+ import type { BlumeProject } from "../core/project-graph.ts";
23
+ import type { ResolvedConfig } from "../core/schema.ts";
24
+ import { staticFileResolver } from "../core/static-files.ts";
25
+ import { buildRobots } from "./robots.ts";
26
+ import { buildRssFeeds } from "./rss.ts";
27
+ import { buildSitemapFiles } from "./sitemap.ts";
28
+
29
+ /** A generated `.well-known` discovery document: `null` content when it's off. */
30
+ export interface WellKnownFile {
31
+ content: string | null;
32
+ label: string;
33
+ path: string;
34
+ }
35
+
36
+ /**
37
+ * The generated `.well-known` discovery documents — the Web Bot Auth signature
38
+ * directory, the RFC 9727 API catalog, and the AI catalog under both the
39
+ * ai-catalog spec's well-known URI and the ARD v0.91 one (see
40
+ * `ai/ai-catalog.ts`) — each with `null` content when its feature is off.
41
+ */
42
+ export const wellKnownFiles = (
43
+ config: ResolvedConfig,
44
+ skills: readonly SkillArtifact[]
45
+ ): WellKnownFile[] => {
46
+ const aiCatalog = buildAiCatalog(config, skills);
47
+ return [
48
+ {
49
+ content: buildSignaturesDirectory(config),
50
+ label: "Web Bot Auth",
51
+ path: SIGNATURES_DIRECTORY_PATH,
52
+ },
53
+ {
54
+ content: buildApiCatalog(config),
55
+ label: "RFC 9727",
56
+ path: API_CATALOG_PATH,
57
+ },
58
+ { content: aiCatalog, label: "AI Catalog", path: AI_CATALOG_PATH },
59
+ { content: aiCatalog, label: "ARD manifest", path: ARD_MANIFEST_PATH },
60
+ ];
61
+ };
62
+
63
+ /**
64
+ * The files a build serves beside its pages that don't come from `public/`,
65
+ * as base-less root-relative paths: what `publishBuildArtifacts` writes once
66
+ * Astro is done (llms.txt, the sitemap, robots.txt, `agent-readability.json`,
67
+ * the `.well-known` discovery documents, the Agent Skills index, `skill.md`)
68
+ * and the generated endpoints at fixed paths (the RSS feeds, the JSON docs
69
+ * API and its OpenAPI description, the MCP discovery documents). Each is
70
+ * listed under the gate its writer reads, so a feature the config switches
71
+ * off drops its file here too: `blume validate` accepts a link to
72
+ * `/llms.txt` exactly when the build writes one.
73
+ *
74
+ * Read from the config alone, before a build: the skill archives a build
75
+ * publishes beside the index, and the platform files (`_redirects`,
76
+ * `_headers`, `vercel.json`), aren't listed. A page's Markdown copy
77
+ * (`/guide.md`) is its route's, and resolves through the page.
78
+ */
79
+ export const generatedFilePaths = (project: BlumeProject): string[] => {
80
+ const { config } = project;
81
+ const paths: string[] = [];
82
+ if (config.agents.llmsTxt.enabled) {
83
+ paths.push("/llms.txt", "/llms-full.txt");
84
+ }
85
+ paths.push(
86
+ ...(buildSitemapFiles(project) ?? []).map((file) => `/${file.name}`)
87
+ );
88
+ if (buildRobots(project)) {
89
+ paths.push("/robots.txt");
90
+ }
91
+ if (buildAgentReadability(project)) {
92
+ paths.push("/agent-readability.json");
93
+ }
94
+ paths.push(
95
+ ...wellKnownFiles(config, []).flatMap((file) =>
96
+ file.content ? [file.path] : []
97
+ )
98
+ );
99
+ if (servesSkillsIndex(config)) {
100
+ paths.push(AGENT_SKILLS_INDEX_PATH);
101
+ if (config.agents.skillMd) {
102
+ paths.push(SKILL_MD_PATH);
103
+ }
104
+ }
105
+ paths.push(...buildRssFeeds(project).map((feed) => feed.path));
106
+ if (config.agents.api) {
107
+ paths.push(OPENAPI_PATH, API_PAGES_PATH, API_NAVIGATION_PATH);
108
+ }
109
+ if (config.agents.mcp.enabled) {
110
+ paths.push("/.well-known/mcp.json", "/.well-known/mcp/server-card.json");
111
+ }
112
+ return paths;
113
+ };
114
+
115
+ /**
116
+ * Whether a base-less path is a file the project's site serves beside its
117
+ * pages: one in `public/` (`/spec.pdf`, a folder's `index.html`) or one the
118
+ * build generates (`/llms.txt`). What `generateRuntime` and `blume doctor`
119
+ * check navigation entries against, beside the routes, as `blume validate`
120
+ * checks links.
121
+ */
122
+ export const servedFiles = (
123
+ project: BlumeProject
124
+ ): ((path: string) => boolean) =>
125
+ staticFileResolver(
126
+ join(project.context.root, "public"),
127
+ generatedFilePaths(project)
128
+ );
@@ -1,5 +1,5 @@
1
1
  import { existsSync } from "node:fs";
2
- import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
2
+ import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
3
3
 
4
4
  import { dirname, join } from "pathe";
5
5
 
@@ -99,6 +99,28 @@ export const checkVercelFunctionBundles = async (
99
99
  return !fatal;
100
100
  };
101
101
 
102
+ /**
103
+ * Remove the routing config of a build that failed after the adapter wrote
104
+ * it. The adapter's config is complete as far as the adapter goes, but the
105
+ * redirects, negotiation, and base routes Blume adds were never spliced in,
106
+ * so `vercel deploy --prebuilt` would ship a site missing them. Without a
107
+ * `config.json`, the tree is no Build Output a deploy accepts; the function
108
+ * bundles stay for inspection.
109
+ */
110
+ const withdrawBuildOutput = async (
111
+ context: ProjectContext,
112
+ log: BuildLog
113
+ ): Promise<void> => {
114
+ const configPath = join(buildOutputDir(context), "config.json");
115
+ if (!existsSync(configPath)) {
116
+ return;
117
+ }
118
+ await rm(configPath, { force: true });
119
+ log.info(
120
+ "Removed .vercel/output/config.json, so this failed build can't be deployed."
121
+ );
122
+ };
123
+
102
124
  /**
103
125
  * Splice `Accept: text/markdown` negotiation routes into the adapter's Build
104
126
  * Output config, so a content-page request that prefers Markdown gets the
@@ -299,6 +321,7 @@ export const vercelPlatform: DeployPlatform = {
299
321
  log
300
322
  );
301
323
  if (!ok) {
324
+ await withdrawBuildOutput(context, log);
302
325
  return false;
303
326
  }
304
327
  // A verify build moves its static files too, where the budget gate reads
@@ -310,6 +333,7 @@ export const vercelPlatform: DeployPlatform = {
310
333
  return true;
311
334
  }
312
335
  if (!(await rebaseVercelRoutes(project, log))) {
336
+ await withdrawBuildOutput(context, log);
313
337
  return false;
314
338
  }
315
339
  await emitVercelNegotiation(project, log);
@@ -8,6 +8,11 @@ import {
8
8
  } from "../core/base-path.ts";
9
9
  import { routeSetFor, servesRoute } from "../core/locale-links.ts";
10
10
  import type { RouteSet } from "../core/locale-links.ts";
11
+ import {
12
+ MIRROR_EXTENSIONS,
13
+ mirrorName,
14
+ mirrorOwner,
15
+ } from "../core/markdown-mirrors.ts";
11
16
  import type { BlumeProject } from "../core/project-graph.ts";
12
17
  import {
13
18
  compileRedirects,
@@ -113,29 +118,49 @@ export const applyBaseToPlatformRedirects = (
113
118
  };
114
119
 
115
120
  /**
116
- * The raw-Markdown copies Blume serves beside every page (`/guide.md`,
117
- * `/guide.mdx`; see `rawMarkdownEndpointTemplate`).
121
+ * A path naming an `.html` file (`/guide.html`), the URL another generator
122
+ * served a page at. No Blume page was ever served there, so it had no
123
+ * Markdown copy (`/guide.html.md`) for a redirect to move.
118
124
  */
119
- const MIRROR_EXTENSIONS = [".md", ".mdx"] as const;
125
+ const HTML_FILE_PATH = /\.html$/u;
126
+
127
+ /** A path naming a folder's `index.html` (`/guide/index.html`, `/index.html`). */
128
+ const INDEX_FILE_PATH = /^(?<folder>.*)\/index\.html$/u;
120
129
 
121
130
  /**
122
- * Where a page's Markdown copy is served, from the page's served path and the
123
- * deployment base that path carries: the endpoints name the root `index`.
131
+ * Whether `from` names a page's own `index.html` (`/guide/index.html` for the
132
+ * page `/guide`, `/index.html` for the root), a URL a migrated site may have
133
+ * linked. A static build writes the page there, and Astro writes a redirect
134
+ * page to `<from>/index.html`, which would need that file to be a folder:
135
+ * the build fails with `EISDIR`. Hosts serve the page at that URL anyway,
136
+ * unless a redirect rule answers it first. `pages` are the served page
137
+ * routes, which carry `basePath`, as `from` does.
124
138
  */
125
- const mirrorPath = (path: string, base: string, extension: string): string => {
126
- const route = stripBasePath(base, path);
127
- return `${base}${route === "/" ? "/index" : route}${extension}`;
139
+ export const isPageIndexFile = (from: string, pages: RouteSet): boolean => {
140
+ const folder = INDEX_FILE_PATH.exec(normalizePath(from))?.groups?.folder;
141
+ return folder !== undefined && servesRoute(pages, folder || "/");
128
142
  };
129
143
 
144
+ /**
145
+ * Where a page's Markdown copy is served, from the page's served path and the
146
+ * deployment base that path carries.
147
+ */
148
+ const mirrorPath = (path: string, base: string, extension: string): string =>
149
+ `${base}${mirrorName(stripBasePath(base, path))}${extension}`;
150
+
130
151
  /**
131
152
  * Based `redirects`, each exact one that moves a page followed by the same
132
- * redirect for the page's Markdown copies: from a path no page is served at
133
- * to one a page is, `/old.md` goes to `/new.md` (and `.mdx` alike), where it
134
- * would otherwise 404 for an agent still reading the old URL. A pattern gets
135
- * none; one that carries its capture over (`/beta/:slug*` → `/v2/:slug*`)
136
- * already moves `/beta/guide.md` along. `pages` are the served page routes
137
- * (carrying `basePath`, not `deployment.base`), and `bases` the deployment
138
- * base each end carries: Astro's config leaves it off `from` (see
153
+ * redirect for the page's Markdown copies: from a path whose copies no page
154
+ * serves to one a page is served at, `/old.md` goes to `/new.md` (and `.mdx`
155
+ * alike), where it would otherwise 404 for an agent still reading the old
156
+ * URL. A served copy never gives way: `/index` names the root's copies, so
157
+ * `/index` → `/` moving them would turn the home page's `/index.md` into a
158
+ * redirect to itself. A pattern gets none; one that carries its capture over
159
+ * (`/beta/:slug*` → `/v2/:slug*`) already moves `/beta/guide.md` along. Nor
160
+ * does a redirect from an `.html` file (`/guide.html`, from a migrated site),
161
+ * which had no copy to move. `pages` are the served page routes (carrying
162
+ * `basePath`, not `deployment.base`), and `bases` the deployment base each
163
+ * end carries: Astro's config leaves it off `from` (see
139
164
  * {@link applyBaseToAstroRedirects}).
140
165
  */
141
166
  export const withMirrorRedirects = (
@@ -148,8 +173,10 @@ export const withMirrorRedirects = (
148
173
  const to = normalizePath(redirect.to);
149
174
  const movesPage =
150
175
  !isPatternPath(redirect.from) &&
176
+ !HTML_FILE_PATH.test(from) &&
151
177
  servesRoute(pages, stripBasePath(bases.to, to)) &&
152
- !servesRoute(pages, stripBasePath(bases.from, from));
178
+ mirrorOwner(pages, mirrorName(stripBasePath(bases.from, from))) ===
179
+ undefined;
153
180
  return movesPage
154
181
  ? [
155
182
  redirect,
@@ -11,6 +11,7 @@ import {
11
11
  normalizeBasePath,
12
12
  normalizePath,
13
13
  } from "../core/base-path.ts";
14
+ import { isHiddenPage } from "../core/hidden-pages.ts";
14
15
  import type { BlumeProject } from "../core/project-graph.ts";
15
16
  import { siteRoot } from "../core/site-url.ts";
16
17
 
@@ -163,7 +164,7 @@ export const buildSitemapFiles = (
163
164
  const served = mountBasePath(deployBase, page.route);
164
165
  if (
165
166
  page.meta.draft ||
166
- page.meta.sidebar.hidden ||
167
+ isHiddenPage(page, project.graph) ||
167
168
  page.meta.seo.noindex ||
168
169
  ERROR_ROUTES.has(page.route) ||
169
170
  archivedExcluded(page) ||
@@ -4,22 +4,51 @@ import {
4
4
  withBasePath,
5
5
  } from "../core/base-path.ts";
6
6
  import { servesRoute } from "../core/locale-links.ts";
7
- import type { MdastNode } from "./mdast.ts";
7
+ import { rewriteElementUrls } from "../core/sources/normalize.ts";
8
+ import type { MdastNode, MdastValue } from "./mdast.ts";
8
9
  import { routeSnapshotReader } from "./route-snapshot.ts";
9
10
 
10
11
  interface UrlNode extends MdastNode {
11
12
  url?: string | null;
12
13
  }
13
14
 
15
+ /** Raw HTML in a `.md` page. */
16
+ interface HtmlNode extends MdastNode {
17
+ value: string;
18
+ }
19
+
20
+ /** An MDX JSX attribute, as the plugin reads it. */
21
+ interface JsxAttribute {
22
+ [key: string]: MdastValue;
23
+ name?: string;
24
+ type?: string;
25
+ value?: MdastValue;
26
+ }
27
+
28
+ /** An MDX JSX element (`<a href="/guide">`), as the plugin reads it. */
29
+ interface JsxNode extends MdastNode {
30
+ attributes?: JsxAttribute[];
31
+ children?: MdastValue[];
32
+ name?: string | null;
33
+ }
34
+
14
35
  /**
15
36
  * The slice of Satteri's MDAST visitor context this plugin needs. Nodes are
16
37
  * read-only (the tree compiles to an op-stream), so a URL edit is recorded via
17
38
  * `setProperty`, not by mutating the node object.
18
39
  */
19
40
  interface MdastUrlContext {
20
- setProperty: (node: MdastNode, key: "url", value: string) => void;
41
+ replaceNode: (node: MdastNode, replacement: MdastNode) => void;
42
+ setProperty: (node: MdastNode, key: "url" | "value", value: string) => void;
21
43
  }
22
44
 
45
+ /** An HTML element's name, as opposed to a component's (`Card`). */
46
+ const HTML_ELEMENT = /^[a-z]/u;
47
+
48
+ /** A plain string attribute value; an expression value isn't a URL. */
49
+ const isStringValue = (value: MdastValue): value is string =>
50
+ typeof value === "string";
51
+
23
52
  /** Only a string URL can be rebased; MDAST allows null or absent urls. */
24
53
  const isUrl = (url: string | null | undefined): url is string =>
25
54
  typeof url === "string";
@@ -53,10 +82,14 @@ export interface BaseLinksPluginOptions {
53
82
  * gains `deployment.base` layered over the site-wide `basePath` (`[x](/guide)`
54
83
  * -> `/base/docs/guide`); a public asset — an image, or a link to a file like
55
84
  * `/spec.pdf` (see `withAuthoredBasePath`) — gains `deployment.base` alone,
56
- * since Astro serves `public/` under it but never under `basePath`.
57
- * Idempotent per layer (a hand-written `/docs/x` isn't double-prefixed) and
58
- * inert for external URLs, fragments, and relative paths. Only constructed
59
- * when a base is set (see `markdown/index.ts`).
85
+ * since Astro serves `public/` under it but never under `basePath`. A raw
86
+ * HTML element's `href` or `src` (`<a href="/guide">`, `<img src="/x.png">`,
87
+ * in a `.md` page or as an `.mdx` element) gains `deployment.base` alone: the
88
+ * whole site moves under it, but a raw `href` keeps no `basePath`, as
89
+ * `blume validate` reads it. Idempotent per layer (a hand-written `/docs/x`
90
+ * isn't double-prefixed) and inert for external URLs, fragments, and
91
+ * relative paths. Only constructed when a base is set (see
92
+ * `markdown/index.ts`).
60
93
  */
61
94
  export const baseLinksPlugin = (
62
95
  deployBase: string,
@@ -95,12 +128,60 @@ export const baseLinksPlugin = (
95
128
  // endpoint), served under the deployment base but not `basePath`.
96
129
  const rebaseImage = (node: UrlNode, ctx: MdastUrlContext): void =>
97
130
  rebase(node, ctx, (url) => withBasePath(deployBase, url));
131
+
132
+ /** A raw element's root-relative URL under the deployment base, if it moves. */
133
+ const based = (url: string): string | null => {
134
+ const next = withBasePath(deployBase, url);
135
+ return next === url ? null : next;
136
+ };
137
+
138
+ const rebaseHtml = (node: HtmlNode, ctx: MdastUrlContext): void => {
139
+ const next = rewriteElementUrls(node.value, ({ value }) => based(value));
140
+ if (next !== node.value) {
141
+ ctx.setProperty(node, "value", next);
142
+ }
143
+ };
144
+
145
+ const rebaseElement = (node: JsxNode, ctx: MdastUrlContext): void => {
146
+ if (!(node.name && HTML_ELEMENT.test(node.name))) {
147
+ return;
148
+ }
149
+ let changed = false;
150
+ const attributes = (node.attributes ?? []).map((attribute) => {
151
+ const { name, value } = attribute;
152
+ const next =
153
+ attribute.type === "mdxJsxAttribute" &&
154
+ (name === "href" || name === "src") &&
155
+ isStringValue(value)
156
+ ? based(value)
157
+ : null;
158
+ if (next === null) {
159
+ return attribute;
160
+ }
161
+ changed = true;
162
+ return { name, type: attribute.type, value: next };
163
+ });
164
+ if (changed) {
165
+ // Sätteri can't set a JSX element's attributes in place, so the element
166
+ // is swapped for a copy; its children carry over and are still visited.
167
+ ctx.replaceNode(node, {
168
+ attributes,
169
+ children: node.children ?? [],
170
+ name: node.name,
171
+ type: node.type,
172
+ });
173
+ }
174
+ };
175
+
98
176
  // `link` covers inline links; `definition` covers reference-style
99
177
  // definitions (`[x]: /guide`), which a link or an image may cite.
100
178
  return {
101
179
  definition: rebaseLink,
180
+ html: rebaseHtml,
102
181
  image: rebaseImage,
103
182
  link: rebaseLink,
183
+ mdxJsxFlowElement: rebaseElement,
184
+ mdxJsxTextElement: rebaseElement,
104
185
  name: "blume-base-links",
105
186
  };
106
187
  };
@@ -2,9 +2,9 @@
2
2
  * Code-fence meta. A Shiki transformer reads the tokens after the language and
3
3
  * promotes them to attributes on the rendered `<pre>`:
4
4
  *
5
- * - a title — the first bare token (```ts blume.config.ts) or `title="..."` —
6
- * becomes `data-title`; the theme's code header shows it, falling back to the
7
- * language label.
5
+ * - a title — the bare words (```ts blume.config.ts, ```js Install the
6
+ * client) or `title="..."` — becomes `data-title`; the theme's code header
7
+ * shows it, falling back to the language label.
8
8
  * - the `lineNumbers` keyword (```ts file.ts lineNumbers) becomes
9
9
  * `data-line-numbers`; the theme renders a counter-driven line-number gutter.
10
10
  * - the `wrap` keyword becomes `data-wrap`: this block's long lines wrap
@@ -16,6 +16,7 @@
16
16
  */
17
17
 
18
18
  import {
19
+ isForeignKeyword,
19
20
  isLineRange,
20
21
  metaTokens,
21
22
  QUOTED_ATTR,
@@ -33,7 +34,7 @@ export const EXPANDABLE_MIN_LINES = 16;
33
34
 
34
35
  /** The slice of Shiki's transformer `this` context Blume reads. */
35
36
  interface CodeMetaContext {
36
- options: { meta?: { __raw?: string } };
37
+ options: { lang?: string; meta?: { __raw?: string } };
37
38
  /** The block's code, as highlighted. */
38
39
  source?: string;
39
40
  }
@@ -51,17 +52,29 @@ export interface CodeTitleTransformer {
51
52
 
52
53
  const TITLE_ATTR = /(?:^|\s)title=(?:"(?<dq>[^"]*)"|'(?<sq>[^']*)')/u;
53
54
 
54
- // The first bare token is the title (```ts blume.config.ts): a non-empty token
55
- // that isn't a Shiki line range (`{1,3-5}`), a `key=value` attr, or a reserved
56
- // keyword.
57
- const isTitleToken = (token: string): boolean =>
55
+ // The bare words are the title (```ts blume.config.ts, ```js Install the
56
+ // client): every token that isn't a Shiki line range (`{1,3-5}`), a
57
+ // `key=value` attr, a reserved keyword, or another tool's keyword for a
58
+ // fence in `lang` (```rust ignore).
59
+ const isTitleToken = (token: string, lang: string | undefined): boolean =>
58
60
  token.length > 0 &&
59
61
  !isLineRange(token) &&
60
62
  !token.includes("=") &&
61
- !RESERVED_META_KEYWORDS.has(token);
63
+ !RESERVED_META_KEYWORDS.has(token) &&
64
+ !isForeignKeyword(token, lang);
62
65
 
63
- /** The title a fence's meta string promotes to `data-title`, if any. */
64
- export const parseCodeTitle = (raw: string | undefined): string | undefined => {
66
+ // A title wrapped in one pair of brackets (```ts [file.ts], the Docus and
67
+ // VitePress code-group spelling) shows without them.
68
+ const BRACKETED_TITLE = /^\[(?<inner>[^\]]+)\]$/u;
69
+
70
+ /**
71
+ * The title a fence's meta string promotes to `data-title`, if any. `lang` is
72
+ * the fence's language, which decides the other tools' keywords it drops.
73
+ */
74
+ export const parseCodeTitle = (
75
+ raw: string | undefined,
76
+ lang?: string
77
+ ): string | undefined => {
65
78
  if (!raw) {
66
79
  return undefined;
67
80
  }
@@ -79,7 +92,10 @@ export const parseCodeTitle = (raw: string | undefined): string | undefined => {
79
92
  // The shared tokenizer keeps a quoted attr (rejected below by its `=`) and
80
93
  // a spaced line range (`{1, 3-5}`) whole, so neither can shed a fragment
81
94
  // that reads as a bare title.
82
- return metaTokens(raw).find(isTitleToken);
95
+ const title = metaTokens(raw)
96
+ .filter((token) => isTitleToken(token, lang))
97
+ .join(" ");
98
+ return BRACKETED_TITLE.exec(title)?.groups?.inner ?? (title || undefined);
83
99
  };
84
100
 
85
101
  // The shared tokenizer keeps a quoted attr whole, so a keyword inside a
@@ -96,7 +112,7 @@ export const codeTitleTransformer = (): CodeTitleTransformer => ({
96
112
  name: "blume:code-meta",
97
113
  pre(node) {
98
114
  const raw = this.options.meta?.__raw;
99
- const title = parseCodeTitle(raw);
115
+ const title = parseCodeTitle(raw, this.options.lang);
100
116
  if (title) {
101
117
  node.properties.dataTitle = title;
102
118
  }