blume 2.1.3 → 2.2.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 (377) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +126 -0
  3. package/README.md +4 -4
  4. package/dist/cli/{chunk-74wfhv79.js → chunk-05f21r8h.js} +1 -1
  5. package/dist/cli/{chunk-9qzgg6mj.js → chunk-1cm0h819.js} +3 -3
  6. package/dist/cli/{chunk-nbyvgnnm.js → chunk-1rkxzg3n.js} +104 -47
  7. package/dist/cli/chunk-1rkxzg3n.js.map +36 -0
  8. package/dist/cli/{chunk-desvm72z.js → chunk-2nr29y95.js} +22 -26
  9. package/dist/cli/chunk-2nr29y95.js.map +13 -0
  10. package/dist/cli/{chunk-32khwcb6.js → chunk-49r370mb.js} +42 -69
  11. package/dist/cli/{chunk-32khwcb6.js.map → chunk-49r370mb.js.map} +9 -10
  12. package/dist/cli/{chunk-ms9z66nz.js → chunk-4rftmttv.js} +2 -2
  13. package/dist/cli/{chunk-s0bdnc7v.js → chunk-79bzzkx4.js} +5 -4
  14. package/dist/cli/chunk-79bzzkx4.js.map +10 -0
  15. package/dist/cli/{chunk-hvcxng5f.js → chunk-7tekknev.js} +6 -6
  16. package/dist/cli/{chunk-dv2z0rzf.js → chunk-8b489v1b.js} +38 -13
  17. package/dist/cli/{chunk-dv2z0rzf.js.map → chunk-8b489v1b.js.map} +5 -4
  18. package/dist/cli/{chunk-0pmb4tr4.js → chunk-8f8xw4x2.js} +1195 -222
  19. package/dist/cli/chunk-8f8xw4x2.js.map +44 -0
  20. package/dist/cli/chunk-96snx8yy.js +329 -0
  21. package/dist/cli/chunk-96snx8yy.js.map +13 -0
  22. package/dist/cli/{chunk-ckrcm08h.js → chunk-a3ydrqny.js} +48 -62
  23. package/dist/cli/{chunk-ckrcm08h.js.map → chunk-a3ydrqny.js.map} +4 -5
  24. package/dist/cli/{chunk-bxwhtvw8.js → chunk-ahc5gc9h.js} +5 -5
  25. package/dist/cli/{chunk-dpy8qdjf.js → chunk-as09pafp.js} +73 -182
  26. package/dist/cli/chunk-as09pafp.js.map +18 -0
  27. package/dist/cli/{chunk-tfycddbt.js → chunk-bjk964fp.js} +2 -2
  28. package/dist/cli/{chunk-gfc5fqvf.js → chunk-br0n8n3e.js} +18 -18
  29. package/dist/cli/{chunk-gfc5fqvf.js.map → chunk-br0n8n3e.js.map} +1 -1
  30. package/dist/cli/{chunk-297ajdcv.js → chunk-c69tw29j.js} +6 -6
  31. package/dist/cli/{chunk-297ajdcv.js.map → chunk-c69tw29j.js.map} +3 -3
  32. package/dist/cli/chunk-cn3dy3hz.js +146 -0
  33. package/dist/cli/chunk-cn3dy3hz.js.map +11 -0
  34. package/dist/cli/{chunk-124fvmzq.js → chunk-dm4mg9bj.js} +11064 -8742
  35. package/dist/cli/{chunk-124fvmzq.js.map → chunk-dm4mg9bj.js.map} +78 -64
  36. package/dist/cli/{chunk-dqhg54mr.js → chunk-dmx6cezx.js} +1 -1
  37. package/dist/cli/{chunk-60hz265r.js → chunk-dpcahcrb.js} +24 -11
  38. package/dist/cli/chunk-dpcahcrb.js.map +10 -0
  39. package/dist/cli/{chunk-a57yh0h9.js → chunk-dyk3r7jw.js} +7 -7
  40. package/dist/cli/{chunk-a57yh0h9.js.map → chunk-dyk3r7jw.js.map} +1 -1
  41. package/dist/cli/{chunk-twy7psdz.js → chunk-enk76qd5.js} +14 -10
  42. package/dist/cli/chunk-enk76qd5.js.map +10 -0
  43. package/dist/cli/{chunk-k3gr1nm9.js → chunk-fqy4n3q2.js} +13 -16
  44. package/dist/cli/{chunk-k3gr1nm9.js.map → chunk-fqy4n3q2.js.map} +3 -3
  45. package/dist/cli/{chunk-sc93apfs.js → chunk-frk2wdq1.js} +17 -17
  46. package/dist/cli/{chunk-sc93apfs.js.map → chunk-frk2wdq1.js.map} +2 -2
  47. package/dist/cli/{chunk-kdpzw38a.js → chunk-gprwej7h.js} +95 -7
  48. package/dist/cli/chunk-gprwej7h.js.map +11 -0
  49. package/dist/cli/chunk-kkrteb15.js +157 -0
  50. package/dist/cli/chunk-kkrteb15.js.map +10 -0
  51. package/dist/cli/{chunk-ave1wcbr.js → chunk-m9bp2hc1.js} +1 -1
  52. package/dist/cli/{chunk-58bt3hag.js → chunk-mrvh4c88.js} +4 -4
  53. package/dist/cli/{chunk-5cmts6dv.js → chunk-p8zg3a2h.js} +7 -7
  54. package/dist/cli/{chunk-5cmts6dv.js.map → chunk-p8zg3a2h.js.map} +1 -1
  55. package/dist/cli/{chunk-0w6hs479.js → chunk-rkqae7kh.js} +30 -18
  56. package/dist/cli/chunk-rkqae7kh.js.map +10 -0
  57. package/dist/cli/{chunk-bqxaqn5p.js → chunk-t5xx74rd.js} +8 -8
  58. package/dist/cli/{chunk-bqxaqn5p.js.map → chunk-t5xx74rd.js.map} +1 -1
  59. package/dist/cli/{chunk-1f882900.js → chunk-th0pgaj5.js} +2 -2
  60. package/dist/cli/{chunk-dhwtqnnd.js → chunk-vnbvhgmd.js} +2 -2
  61. package/dist/cli/{chunk-gxtg6ed2.js → chunk-vzy9rh6y.js} +21 -21
  62. package/dist/cli/{chunk-gxtg6ed2.js.map → chunk-vzy9rh6y.js.map} +3 -3
  63. package/dist/cli/{chunk-j1x92mx9.js → chunk-w6e455pz.js} +1 -1
  64. package/dist/cli/chunk-wehv26ry.js +155 -0
  65. package/dist/cli/chunk-wehv26ry.js.map +11 -0
  66. package/dist/cli/{chunk-y4msrwm9.js → chunk-xgq4eytj.js} +15 -15
  67. package/dist/cli/{chunk-36bxs9c7.js → chunk-xt0qvz1t.js} +27 -23
  68. package/dist/cli/chunk-xt0qvz1t.js.map +10 -0
  69. package/dist/cli/index.js +78 -42
  70. package/dist/cli/index.js.map +7 -6
  71. package/dist/types/ai/changelog-components.d.ts +18 -0
  72. package/dist/types/ai/changelog-markdown.d.ts +12 -0
  73. package/dist/types/ai/openapi-components.d.ts +7 -7
  74. package/dist/types/ai/operation-details.d.ts +39 -0
  75. package/dist/types/ai/relative-links.d.ts +3 -1
  76. package/dist/types/ai/serializers.d.ts +6 -4
  77. package/dist/types/analytics/head.d.ts +4 -2
  78. package/dist/types/analytics/script.d.ts +8 -2
  79. package/dist/types/components/content/youtube.d.ts +20 -0
  80. package/dist/types/components/layout/nav-utils.d.ts +8 -0
  81. package/dist/types/components/openapi/example-panels.d.ts +33 -0
  82. package/dist/types/components/openapi/helpers.d.ts +53 -11
  83. package/dist/types/components/openapi/snippets.d.ts +7 -1
  84. package/dist/types/core/alert-diagnostics.d.ts +10 -0
  85. package/dist/types/core/builtin-tags.d.ts +16 -0
  86. package/dist/types/core/changelog-list.d.ts +48 -0
  87. package/dist/types/core/closest-match.d.ts +6 -0
  88. package/dist/types/core/code-fence-diagnostics.d.ts +18 -0
  89. package/dist/types/core/code-fences.d.ts +8 -1
  90. package/dist/types/core/config-input.d.ts +1 -1
  91. package/dist/types/core/content-assets.d.ts +16 -18
  92. package/dist/types/core/diagnostics.d.ts +14 -0
  93. package/dist/types/core/directive-diagnostics.d.ts +22 -8
  94. package/dist/types/core/former-routes.d.ts +14 -0
  95. package/dist/types/core/github.d.ts +5 -4
  96. package/dist/types/core/heading-markers.d.ts +65 -2
  97. package/dist/types/core/hidden-pages.d.ts +3 -0
  98. package/dist/types/core/includes.d.ts +23 -0
  99. package/dist/types/core/last-modified.d.ts +18 -6
  100. package/dist/types/core/links.d.ts +42 -2
  101. package/dist/types/core/load-module.d.ts +19 -4
  102. package/dist/types/core/manifest.d.ts +14 -1
  103. package/dist/types/core/markdown-mirrors.d.ts +22 -0
  104. package/dist/types/core/mdx-elements.d.ts +32 -0
  105. package/dist/types/core/mdx-syntax.d.ts +100 -0
  106. package/dist/types/core/meta-diagnostics.d.ts +7 -0
  107. package/dist/types/core/meta.d.ts +32 -5
  108. package/dist/types/core/nav-diagnostics.d.ts +9 -2
  109. package/dist/types/core/navigation.d.ts +13 -0
  110. package/dist/types/core/ordering-prefix.d.ts +6 -0
  111. package/dist/types/core/pinned-ids.d.ts +23 -0
  112. package/dist/types/core/project-graph.d.ts +9 -1
  113. package/dist/types/core/redirect-patterns.d.ts +18 -0
  114. package/dist/types/core/relative-files.d.ts +31 -0
  115. package/dist/types/core/schema.d.ts +267 -7
  116. package/dist/types/core/sources/normalize.d.ts +101 -9
  117. package/dist/types/core/sources/types.d.ts +16 -0
  118. package/dist/types/core/static-files.d.ts +10 -0
  119. package/dist/types/core/static-host.d.ts +42 -0
  120. package/dist/types/core/svg-images.d.ts +26 -0
  121. package/dist/types/core/syntax-diagnostics.d.ts +14 -0
  122. package/dist/types/core/types.d.ts +42 -1
  123. package/dist/types/deploy/generated-files.d.ts +41 -0
  124. package/dist/types/deploy/redirects.d.ts +21 -7
  125. package/dist/types/markdown/code-title.d.ts +9 -5
  126. package/dist/types/markdown/directives.d.ts +8 -0
  127. package/dist/types/markdown/fence-language.d.ts +51 -0
  128. package/dist/types/markdown/fence-meta.d.ts +5 -0
  129. package/dist/types/markdown/github-alerts.d.ts +35 -0
  130. package/dist/types/openapi/asyncapi.d.ts +6 -5
  131. package/dist/types/openapi/checks.d.ts +10 -5
  132. package/dist/types/openapi/model.d.ts +55 -8
  133. package/dist/types/openapi/parse.d.ts +4 -1
  134. package/dist/types/sources/filesystem.d.ts +14 -4
  135. package/dist/types/sources/mdx-remote.d.ts +6 -3
  136. package/dist/types/sources/registry.d.ts +3 -3
  137. package/dist/types/theme/color.d.ts +33 -0
  138. package/dist/types/theme/palette.d.ts +122 -0
  139. package/docs/01-quickstart.mdx +1 -1
  140. package/docs/02-deployment.mdx +23 -8
  141. package/docs/04-migrating.mdx +16 -4
  142. package/docs/advanced/changelog.mdx +27 -1
  143. package/docs/advanced/skills.mdx +1 -1
  144. package/docs/cli/audit.mdx +1 -1
  145. package/docs/cli/doctor.mdx +2 -2
  146. package/docs/cli/evals.mdx +1 -1
  147. package/docs/cli/index.mdx +4 -2
  148. package/docs/cli/validate.mdx +10 -7
  149. package/docs/configuration/analytics.mdx +2 -0
  150. package/docs/configuration/customization.mdx +3 -1
  151. package/docs/configuration/index.mdx +2 -2
  152. package/docs/configuration/theming.mdx +5 -1
  153. package/docs/content/components.mdx +26 -6
  154. package/docs/content/frontmatter.mdx +1 -1
  155. package/docs/content/includes.mdx +6 -2
  156. package/docs/content/index.mdx +11 -1
  157. package/docs/content/islands.mdx +2 -0
  158. package/docs/content/meta.mdx +9 -3
  159. package/docs/content/navigation.mdx +14 -6
  160. package/docs/content/sources/obsidian.mdx +1 -1
  161. package/docs/content/sources/remote-mdx.mdx +3 -1
  162. package/docs/content/syntax.mdx +76 -8
  163. package/docs/content/variables.mdx +3 -1
  164. package/docs/discoverability/agent-discovery.mdx +1 -1
  165. package/docs/discoverability/json-api.mdx +1 -1
  166. package/docs/discoverability/llms-txt.mdx +1 -1
  167. package/docs/discoverability/markdown.mdx +2 -2
  168. package/docs/discoverability/open-graph.mdx +1 -1
  169. package/docs/discoverability/sitemap-and-robots.mdx +1 -1
  170. package/docs/references/graphql.mdx +2 -0
  171. package/docs/references/openapi.mdx +45 -7
  172. package/package.json +4 -4
  173. package/skills/blume/SKILL.md +2 -2
  174. package/skills/blume-migrate/SKILL.md +49 -27
  175. package/skills/blume-migrate/references/docsify.md +262 -0
  176. package/skills/blume-migrate/references/docus.md +298 -0
  177. package/skills/blume-migrate/references/docusaurus.md +3 -3
  178. package/skills/blume-migrate/references/fern.md +327 -0
  179. package/skills/blume-migrate/references/fumadocs.md +1 -1
  180. package/skills/blume-migrate/references/gitbook.md +267 -0
  181. package/skills/blume-migrate/references/github-wiki.md +259 -0
  182. package/skills/blume-migrate/references/jekyll.md +253 -0
  183. package/skills/blume-migrate/references/mdbook.md +256 -0
  184. package/skills/blume-migrate/references/mintlify.md +5 -5
  185. package/skills/blume-migrate/references/mkdocs.md +236 -0
  186. package/skills/blume-migrate/references/monorepo.md +20 -8
  187. package/skills/blume-migrate/references/nextra.md +2 -2
  188. package/skills/blume-migrate/references/readme.md +257 -0
  189. package/skills/blume-migrate/references/redocly.md +198 -0
  190. package/skills/blume-migrate/references/starlight.md +3 -3
  191. package/skills/blume-migrate/references/vitepress.md +204 -0
  192. package/skills/blume-migrate/references/vuepress.md +271 -0
  193. package/skills/blume-migrate/scripts/docsify-codemod.mjs +2387 -0
  194. package/skills/blume-migrate/scripts/docus-codemod.mjs +2542 -0
  195. package/skills/blume-migrate/scripts/fern-codemod.mjs +2633 -0
  196. package/skills/blume-migrate/scripts/github-wiki-codemod.mjs +2808 -0
  197. package/skills/blume-migrate/scripts/include-excerpts.mjs +649 -0
  198. package/skills/blume-migrate/scripts/jekyll-codemod.mjs +3649 -0
  199. package/skills/blume-migrate/scripts/mintlify-codemod.mjs +17 -13
  200. package/skills/blume-migrate/scripts/mkdocs-codemod.mjs +2526 -0
  201. package/skills/blume-migrate/scripts/operation-routes.mjs +508 -0
  202. package/skills/blume-migrate/scripts/pin-heading-ids.mjs +1049 -0
  203. package/skills/blume-migrate/scripts/readme-codemod.mjs +4139 -0
  204. package/skills/blume-migrate/scripts/vitepress-codemod.mjs +1511 -0
  205. package/src/ai/changelog-components.ts +24 -0
  206. package/src/ai/changelog-markdown.ts +45 -41
  207. package/src/ai/component-markdown.ts +22 -9
  208. package/src/ai/llms.ts +36 -5
  209. package/src/ai/markdown.ts +2 -2
  210. package/src/ai/openapi-components.ts +25 -9
  211. package/src/ai/operation-details.ts +208 -0
  212. package/src/ai/relative-links.ts +48 -8
  213. package/src/ai/serializers.ts +7 -6
  214. package/src/ai/site-skill.ts +6 -2
  215. package/src/analytics/head.ts +11 -5
  216. package/src/analytics/script.ts +13 -3
  217. package/src/astro/generate.ts +32 -6
  218. package/src/astro/index.ts +1 -0
  219. package/src/astro/integration.ts +27 -1
  220. package/src/astro/mdx-source-errors.ts +46 -0
  221. package/src/astro/templates.ts +89 -185
  222. package/src/audit/checks/redirects.ts +5 -1
  223. package/src/audit/redirects.ts +36 -19
  224. package/src/audit/report.ts +22 -9
  225. package/src/audit/run.ts +12 -28
  226. package/src/audit/terms.ts +23 -0
  227. package/src/audit/url.ts +2 -2
  228. package/src/cli/build-failure.ts +256 -7
  229. package/src/cli/command-meta.ts +1 -1
  230. package/src/cli/commands/audit.ts +1 -1
  231. package/src/cli/commands/build.ts +12 -3
  232. package/src/cli/commands/check.ts +13 -2
  233. package/src/cli/commands/dev.ts +1 -1
  234. package/src/cli/commands/doctor.ts +4 -2
  235. package/src/cli/commands/init.ts +3 -3
  236. package/src/cli/commands/preview.ts +17 -1
  237. package/src/cli/commands/validate.ts +22 -10
  238. package/src/cli/prepare.ts +1 -1
  239. package/src/cli/preview-pages.ts +161 -0
  240. package/src/cli/unknown-flags.ts +1 -42
  241. package/src/components/code-copy.ts +51 -0
  242. package/src/components/code-language.ts +75 -0
  243. package/src/components/content/AccordionItem.astro +16 -1
  244. package/src/components/content/Callout.astro +11 -1
  245. package/src/components/content/Changelog.astro +189 -0
  246. package/src/components/content/CodeBlock.astro +8 -11
  247. package/src/components/content/Tabs.astro +16 -2
  248. package/src/components/content/YouTube.astro +6 -4
  249. package/src/components/content/code-block.ts +33 -0
  250. package/src/components/content/content-strings.ts +5 -1
  251. package/src/components/content/inline-markdown.ts +35 -8
  252. package/src/components/content/youtube.ts +49 -7
  253. package/src/components/layout/Analytics.astro +5 -1
  254. package/src/components/layout/NavTree.astro +8 -3
  255. package/src/components/layout/RootLayout.astro +18 -31
  256. package/src/components/layout/nav-utils.ts +14 -0
  257. package/src/components/layout/search/pagefind.ts +2 -1
  258. package/src/components/openapi/Operation.astro +4 -0
  259. package/src/components/openapi/PanelTabs.astro +9 -3
  260. package/src/components/openapi/RequestBody.astro +42 -8
  261. package/src/components/openapi/RequestPanel.astro +34 -53
  262. package/src/components/openapi/example-panels.ts +110 -0
  263. package/src/components/openapi/helpers.ts +195 -20
  264. package/src/components/openapi/operation-model.ts +63 -26
  265. package/src/components/openapi/request.ts +13 -0
  266. package/src/components/openapi/snippets.ts +14 -2
  267. package/src/core/alert-diagnostics.ts +89 -0
  268. package/src/core/builtin-tags.ts +76 -0
  269. package/src/core/changelog-list.ts +87 -0
  270. package/src/core/closest-match.ts +41 -0
  271. package/src/core/code-fence-diagnostics.ts +267 -0
  272. package/src/core/code-fences.ts +43 -7
  273. package/src/core/component-diagnostics.ts +65 -12
  274. package/src/core/config-input.ts +1 -1
  275. package/src/core/config.ts +46 -1
  276. package/src/core/content-assets.ts +64 -144
  277. package/src/core/diagnostics.ts +36 -3
  278. package/src/core/directive-diagnostics.ts +265 -47
  279. package/src/core/former-routes.ts +46 -0
  280. package/src/core/git-ignored.ts +53 -0
  281. package/src/core/github.ts +5 -4
  282. package/src/core/heading-markers.ts +156 -2
  283. package/src/core/hidden-pages.ts +64 -0
  284. package/src/core/includes.ts +181 -41
  285. package/src/core/last-modified.ts +47 -10
  286. package/src/core/links.ts +438 -83
  287. package/src/core/load-module.ts +98 -9
  288. package/src/core/manifest.ts +12 -9
  289. package/src/core/markdown-mirrors.ts +52 -0
  290. package/src/core/mdx-elements.ts +153 -0
  291. package/src/core/mdx-syntax.ts +377 -0
  292. package/src/core/meta-diagnostics.ts +134 -0
  293. package/src/core/meta.ts +154 -55
  294. package/src/core/nav-diagnostics.ts +16 -4
  295. package/src/core/navigation.ts +70 -10
  296. package/src/core/ordering-prefix.ts +22 -2
  297. package/src/core/pinned-ids.ts +141 -0
  298. package/src/core/project-graph.ts +221 -29
  299. package/src/core/redirect-patterns.ts +41 -0
  300. package/src/core/relative-files.ts +145 -0
  301. package/src/core/schema.ts +17 -3
  302. package/src/core/sources/mdx-remote.ts +75 -14
  303. package/src/core/sources/normalize.ts +885 -179
  304. package/src/core/sources/notion.ts +5 -4
  305. package/src/core/sources/obsidian.ts +2 -5
  306. package/src/core/sources/types.ts +17 -0
  307. package/src/core/static-files.ts +40 -0
  308. package/src/core/static-host.ts +125 -0
  309. package/src/core/svg-images.ts +98 -0
  310. package/src/core/syntax-diagnostics.ts +491 -0
  311. package/src/core/types.ts +43 -1
  312. package/src/deploy/artifacts.ts +2 -28
  313. package/src/deploy/generated-files.ts +128 -0
  314. package/src/deploy/platforms/vercel.ts +25 -1
  315. package/src/deploy/redirects.ts +43 -16
  316. package/src/deploy/sitemap.ts +2 -1
  317. package/src/markdown/base-links.ts +87 -6
  318. package/src/markdown/code-title.ts +29 -13
  319. package/src/markdown/content-assets.ts +210 -0
  320. package/src/markdown/directives.ts +69 -5
  321. package/src/markdown/fence-language.ts +106 -0
  322. package/src/markdown/fence-meta.ts +42 -0
  323. package/src/markdown/github-alerts.ts +172 -0
  324. package/src/markdown/heading-anchors.ts +265 -56
  325. package/src/markdown/include.ts +30 -3
  326. package/src/markdown/index.ts +145 -40
  327. package/src/markdown/prompt-text.ts +95 -0
  328. package/src/markdown/relative-links.ts +37 -2
  329. package/src/markdown/route-snapshot.ts +22 -12
  330. package/src/markdown/svg-fallback.ts +51 -0
  331. package/src/markdown/title-heading.ts +134 -0
  332. package/src/migrate/migrate.ts +127 -2
  333. package/src/og/card.ts +14 -4
  334. package/src/openapi/asyncapi.ts +14 -6
  335. package/src/openapi/checks.ts +160 -15
  336. package/src/openapi/model.ts +172 -33
  337. package/src/openapi/parse.ts +13 -4
  338. package/src/openapi/render-mdx.ts +55 -9
  339. package/src/openapi/source.ts +87 -6
  340. package/src/registry/eject.ts +0 -1
  341. package/src/search/documents.ts +10 -2
  342. package/src/sources/filesystem.ts +28 -3
  343. package/src/sources/mdx-remote.ts +7 -15
  344. package/src/theme/color.ts +14 -0
  345. package/src/theme/entry.ts +22 -1
  346. package/src/theme/palette.ts +74 -1
  347. package/dist/cli/chunk-0pmb4tr4.js.map +0 -35
  348. package/dist/cli/chunk-0w6hs479.js.map +0 -10
  349. package/dist/cli/chunk-150jk7km.js +0 -52
  350. package/dist/cli/chunk-150jk7km.js.map +0 -10
  351. package/dist/cli/chunk-36bxs9c7.js.map +0 -10
  352. package/dist/cli/chunk-53jqw8z0.js +0 -89
  353. package/dist/cli/chunk-53jqw8z0.js.map +0 -10
  354. package/dist/cli/chunk-60hz265r.js.map +0 -10
  355. package/dist/cli/chunk-desvm72z.js.map +0 -14
  356. package/dist/cli/chunk-dpy8qdjf.js.map +0 -17
  357. package/dist/cli/chunk-es8h0v3j.js +0 -441
  358. package/dist/cli/chunk-es8h0v3j.js.map +0 -11
  359. package/dist/cli/chunk-kdpzw38a.js.map +0 -11
  360. package/dist/cli/chunk-nbyvgnnm.js.map +0 -36
  361. package/dist/cli/chunk-s0bdnc7v.js.map +0 -10
  362. package/dist/cli/chunk-twy7psdz.js.map +0 -10
  363. package/dist/cli/chunk-ydp3065p.js +0 -64
  364. package/dist/cli/chunk-ydp3065p.js.map +0 -10
  365. /package/dist/cli/{chunk-74wfhv79.js.map → chunk-05f21r8h.js.map} +0 -0
  366. /package/dist/cli/{chunk-9qzgg6mj.js.map → chunk-1cm0h819.js.map} +0 -0
  367. /package/dist/cli/{chunk-ms9z66nz.js.map → chunk-4rftmttv.js.map} +0 -0
  368. /package/dist/cli/{chunk-hvcxng5f.js.map → chunk-7tekknev.js.map} +0 -0
  369. /package/dist/cli/{chunk-bxwhtvw8.js.map → chunk-ahc5gc9h.js.map} +0 -0
  370. /package/dist/cli/{chunk-tfycddbt.js.map → chunk-bjk964fp.js.map} +0 -0
  371. /package/dist/cli/{chunk-dqhg54mr.js.map → chunk-dmx6cezx.js.map} +0 -0
  372. /package/dist/cli/{chunk-ave1wcbr.js.map → chunk-m9bp2hc1.js.map} +0 -0
  373. /package/dist/cli/{chunk-58bt3hag.js.map → chunk-mrvh4c88.js.map} +0 -0
  374. /package/dist/cli/{chunk-1f882900.js.map → chunk-th0pgaj5.js.map} +0 -0
  375. /package/dist/cli/{chunk-dhwtqnnd.js.map → chunk-vnbvhgmd.js.map} +0 -0
  376. /package/dist/cli/{chunk-j1x92mx9.js.map → chunk-w6e455pz.js.map} +0 -0
  377. /package/dist/cli/{chunk-y4msrwm9.js.map → chunk-xgq4eytj.js.map} +0 -0
@@ -1,22 +1,31 @@
1
+ import { fileURLToPath } from "node:url";
2
+
1
3
  import { satteri } from "@astrojs/markdown-satteri";
2
4
  import {
3
5
  transformerMetaHighlight,
4
6
  transformerNotationDiff,
7
+ transformerNotationErrorLevel,
5
8
  transformerNotationFocus,
6
9
  transformerNotationHighlight,
7
10
  transformerNotationWordHighlight,
8
11
  } from "@shikijs/transformers";
9
12
  import { escape as escapeHtml } from "html-escaper";
13
+ import { resolve } from "pathe";
10
14
  import { codeToHtml } from "shiki";
11
15
 
12
- import { hasVariables } from "../core/variables.ts";
16
+ import { withIncludedDefinitions } from "../core/includes.ts";
17
+ import { escapePinnedIds } from "../core/pinned-ids.ts";
18
+ import { hasVariables, substituteVariables } from "../core/variables.ts";
13
19
  import type { ContentVariables } from "../core/variables.ts";
14
20
  import { apiRailPlugin } from "./api-rail.ts";
15
21
  import { baseLinksPlugin } from "./base-links.ts";
16
22
  import { codeTitleTransformer } from "./code-title.ts";
23
+ import { contentAssetsPlugin } from "./content-assets.ts";
17
24
  import { directiveToCalloutPlugin } from "./directives.ts";
18
25
  import { externalLinksPlugin } from "./external-links.ts";
19
26
  import { MARKDOWN_BODY_FEATURES, MDX_BODY_FEATURES } from "./features.ts";
27
+ import { fenceLanguagePlugin } from "./fence-language.ts";
28
+ import { githubAlertsPlugin } from "./github-alerts.ts";
20
29
  import { headingAnchorPlugin } from "./heading-anchors.ts";
21
30
  import { includePlugin } from "./include.ts";
22
31
  import { inlineCodeHighlightPlugin } from "./inline-code.ts";
@@ -24,10 +33,13 @@ import { languageIconTransformer } from "./language-icon.ts";
24
33
  import { mathPlugin } from "./math.ts";
25
34
  import { mermaidPlugin } from "./mermaid.ts";
26
35
  import { packageInstallPlugin } from "./package-install.ts";
36
+ import { promptTextPlugin } from "./prompt-text.ts";
27
37
  import { relativeLinksPlugin } from "./relative-links.ts";
38
+ import { svgFallbackPlugin } from "./svg-fallback.ts";
28
39
  import { tableWrapPlugin } from "./table-wrap.ts";
29
40
  import { DEFAULT_CODE_THEMES } from "./themes.ts";
30
41
  import type { CodeThemes } from "./themes.ts";
42
+ import { titleHeadingPlugin } from "./title-heading.ts";
31
43
  import { ts2jsPlugin } from "./ts2js.ts";
32
44
  import { variablesPlugin } from "./variables.ts";
33
45
  import { viewsPlugin } from "./views.ts";
@@ -94,29 +106,33 @@ const asShikiTransformer = (transformer: { name: string }): ShikiTransformer =>
94
106
  * (`[#custom-id]`, `[!toc]`, `[toc]`), which must parse regardless of config —
95
107
  * while `markdown.headingAnchors: false` only turns off the self-linking
96
108
  * anchor wrap on `<h2>`–`<h6>`. Inline code runs first so the anchor wrap
97
- * re-refs already-highlighted code. The external-link plugin is added only
98
- * when `markdown.externalLinks` is on, so off stays today's exact output.
109
+ * re-refs already-highlighted code. The title-heading plugin runs after the
110
+ * heading plugin has slugged every heading, so dropping an untitled page's
111
+ * title `<h1>` moves no other heading's id. The external-link plugin is added
112
+ * only when `markdown.externalLinks` is on, so off stays today's exact output.
99
113
  */
100
114
  const blumeHastPlugins = (options: BlumeMarkdownOptions): HastPlugin[] => [
101
115
  asHastPlugin(inlineCodeHighlightPlugin(options.codeThemes)),
102
116
  asHastPlugin(tableWrapPlugin()),
103
117
  asHastPlugin(headingAnchorPlugin({ wrap: options.headingAnchors !== false })),
118
+ asHastPlugin(titleHeadingPlugin()),
104
119
  ...(options.externalLinks ? [asHastPlugin(externalLinksPlugin())] : []),
105
120
  ];
106
121
 
107
122
  /**
108
- * Shiki transformers enabled by default for every code block. The four upstream
123
+ * Shiki transformers enabled by default for every code block. The upstream
109
124
  * notation transformers read GitHub-style comments and strip them from the
110
125
  * output: `// [!code highlight]`, `// [!code ++]` / `// [!code --]`,
111
- * `// [!code word:x]`, and `// [!code focus]`. The v3 match algorithm scopes a
126
+ * `// [!code word:x]`, `// [!code focus]`, and `// [!code error]` /
127
+ * `// [!code warning]`. The v3 match algorithm scopes a
112
128
  * notation to the line it sits on (or the next, for a trailing comment).
113
129
  * `transformerMetaHighlight` adds numeric range highlighting from the fence meta
114
130
  * (` ```ts {1,3-5} `), reusing the same `highlighted` class. Blume's own
115
131
  * {@link languageIconTransformer} prepends a brand icon, and
116
132
  * {@link codeTitleTransformer} runs last to promote fence-meta (title / line
117
133
  * numbers) to `<pre>` attributes. The theme styles the classes these emit
118
- * (`highlighted`, `diff add/remove`, `highlighted-word`, `focused`,
119
- * `blume-lang-icon`).
134
+ * (`highlighted`, `highlighted error/warning`, `diff add/remove`,
135
+ * `highlighted-word`, `focused`, `blume-lang-icon`).
120
136
  */
121
137
  export interface BlumeShikiOptions {
122
138
  /** Prepend a brand language icon to the header (`markdown.code.icons`). */
@@ -131,6 +147,15 @@ export const blumeShikiTransformers = (
131
147
  transformerNotationDiff({ matchAlgorithm: "v3" }),
132
148
  transformerNotationWordHighlight({ matchAlgorithm: "v3" }),
133
149
  transformerNotationFocus({ matchAlgorithm: "v3" }),
150
+ // Only the two levels the theme styles: upstream also maps `[!code info]`,
151
+ // which would otherwise strip the comment and leave a plain highlight.
152
+ transformerNotationErrorLevel({
153
+ classMap: {
154
+ error: ["highlighted", "error"],
155
+ warning: ["highlighted", "warning"],
156
+ },
157
+ matchAlgorithm: "v3",
158
+ }),
134
159
  transformerMetaHighlight(),
135
160
  ];
136
161
  if (options.icons !== false) {
@@ -305,14 +330,17 @@ export interface BlumeMarkdownOptions {
305
330
  }
306
331
 
307
332
  /**
308
- * MDAST plugins that apply to both `.md` and `.mdx`: relative page links
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).
333
+ * MDAST plugins that apply to both `.md` and `.mdx`: an SVG Astro can't
334
+ * measure pointed at its served original, relative page links rewritten to
335
+ * the served URL of the route they mean, references to other files beside the
336
+ * page to their served copies, then the base-path link rewrite for
337
+ * root-relative links (added only when a `basePath` or `deployBase` is
338
+ * configured).
312
339
  */
313
340
  const blumeSharedMdastPlugins = (
314
341
  options: BlumeMarkdownOptions
315
342
  ): MdastPlugin[] => [
343
+ asMdastPlugin(svgFallbackPlugin()),
316
344
  asMdastPlugin(
317
345
  relativeLinksPlugin({
318
346
  contentRoot: options.contentRoot,
@@ -320,6 +348,12 @@ const blumeSharedMdastPlugins = (
320
348
  deployBase: options.deployBase,
321
349
  })
322
350
  ),
351
+ asMdastPlugin(
352
+ contentAssetsPlugin({
353
+ dataFile: options.dataFile,
354
+ deployBase: options.deployBase,
355
+ })
356
+ ),
323
357
  ...(options.basePath || options.deployBase
324
358
  ? [
325
359
  asMdastPlugin(
@@ -344,29 +378,94 @@ const blumeIncludePlugin = (options: BlumeMarkdownOptions): MdastPlugin =>
344
378
  })
345
379
  );
346
380
 
381
+ /** A Sätteri processor, as `satteri()` builds one. */
382
+ type SatteriProcessor = ReturnType<typeof satteri>;
383
+
384
+ /**
385
+ * `processor`, rendering each page from the source its parse needs: with the
386
+ * link-reference definitions its includes write appended (see
387
+ * `withIncludedDefinitions`) — the include splice runs after the page is
388
+ * parsed, too late for the parse to resolve a reference in the page against
389
+ * a partial's definition — and with the punctuation in its pinned heading
390
+ * ids escaped (see `escapePinnedIds`), so smart punctuation leaves a
391
+ * `[#a--b]` pin as written. Pages with neither render their source as given.
392
+ */
393
+ const withRenderSource = (
394
+ processor: SatteriProcessor,
395
+ options: BlumeMarkdownOptions
396
+ ): SatteriProcessor => {
397
+ const contentRoot = options.contentRoot
398
+ ? resolve(options.contentRoot)
399
+ : undefined;
400
+ const source = async (content: string, sourcePath: string) =>
401
+ substituteVariables(
402
+ await withIncludedDefinitions(content, { contentRoot, sourcePath }),
403
+ options.variables
404
+ );
405
+ const { createMdxRenderer, createRenderer } = processor;
406
+ const wrapped: SatteriProcessor = {
407
+ ...processor,
408
+ createRenderer: async (shared) => {
409
+ const renderer = await createRenderer(shared);
410
+ return {
411
+ render: async (content, renderOptions) =>
412
+ renderer.render(
413
+ escapePinnedIds(
414
+ renderOptions?.fileURL
415
+ ? await source(content, fileURLToPath(renderOptions.fileURL))
416
+ : content,
417
+ "markdown"
418
+ ),
419
+ renderOptions
420
+ ),
421
+ };
422
+ },
423
+ };
424
+ if (createMdxRenderer) {
425
+ wrapped.createMdxRenderer = async (shared, mdx) => {
426
+ const renderer = await createMdxRenderer(shared, mdx);
427
+ return {
428
+ process: async (content, filePath, frontmatter) =>
429
+ renderer.process(
430
+ escapePinnedIds(await source(content, filePath), "mdx"),
431
+ filePath,
432
+ frontmatter
433
+ ),
434
+ };
435
+ };
436
+ }
437
+ return wrapped;
438
+ };
439
+
347
440
  /** Sätteri processor for plain `.md`, with Blume's curated feature set. */
348
441
  export const blumeMarkdownProcessor = (options: BlumeMarkdownOptions = {}) =>
349
- satteri({
350
- features: { ...MARKDOWN_BODY_FEATURES },
351
- hastPlugins: blumeHastPlugins(options),
352
- mdastPlugins: [
353
- blumeIncludePlugin(options),
354
- ...(hasVariables(options.variables)
355
- ? [asMdastPlugin(variablesPlugin(options.variables))]
356
- : []),
357
- ...blumeSharedMdastPlugins(options),
358
- ],
359
- });
442
+ withRenderSource(
443
+ satteri({
444
+ features: { ...MARKDOWN_BODY_FEATURES },
445
+ hastPlugins: blumeHastPlugins(options),
446
+ mdastPlugins: [
447
+ blumeIncludePlugin(options),
448
+ asMdastPlugin(fenceLanguagePlugin()),
449
+ ...(hasVariables(options.variables)
450
+ ? [asMdastPlugin(variablesPlugin(options.variables))]
451
+ : []),
452
+ ...blumeSharedMdastPlugins(options),
453
+ ],
454
+ }),
455
+ options
456
+ );
360
457
 
361
458
  export type BlumeMdxOptions = BlumeMarkdownOptions;
362
459
 
363
460
  /**
364
461
  * Sätteri MDX processor: Blume's feature set plus the MDAST plugins that target
365
462
  * components — `package-install` → package-manager tabs, ` ```ts ts2js ` →
366
- * TypeScript/JavaScript tabs, `:::note` → `<Callout>`, ` ```mermaid ` → a
463
+ * TypeScript/JavaScript tabs, `:::note` and GitHub's `> [!NOTE]` alerts →
464
+ * `<Callout>`, ` ```mermaid ` → a
367
465
  * `<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
466
+ * (`$$…$$`) → the `<Math>` component, top-level `<RequestExample>` and
467
+ * `<ResponseExample>` → the page's `<ApiRail>`, and a `<Prompt>` body's text
468
+ * back to the punctuation its author typed. Used as the `processor` for
370
469
  * `@astrojs/mdx` so these apply to `.mdx` only (plain `.md` uses
371
470
  * {@link blumeMarkdownProcessor}).
372
471
  *
@@ -380,18 +479,24 @@ export type BlumeMdxOptions = BlumeMarkdownOptions;
380
479
  * Satteri's full `MdastPlugin` type at this single boundary.
381
480
  */
382
481
  export const blumeMdxProcessor = (options: BlumeMdxOptions = {}) =>
383
- satteri({
384
- features: { ...MDX_BODY_FEATURES },
385
- hastPlugins: blumeHastPlugins(options),
386
- mdastPlugins: [
387
- blumeIncludePlugin(options),
388
- asMdastPlugin(packageInstallPlugin()),
389
- asMdastPlugin(ts2jsPlugin()),
390
- asMdastPlugin(directiveToCalloutPlugin()),
391
- asMdastPlugin(mermaidPlugin()),
392
- asMdastPlugin(mathPlugin()),
393
- ...blumeSharedMdastPlugins(options),
394
- asMdastPlugin(apiRailPlugin()),
395
- asMdastPlugin(viewsPlugin()),
396
- ],
397
- });
482
+ withRenderSource(
483
+ satteri({
484
+ features: { ...MDX_BODY_FEATURES },
485
+ hastPlugins: blumeHastPlugins(options),
486
+ mdastPlugins: [
487
+ blumeIncludePlugin(options),
488
+ asMdastPlugin(fenceLanguagePlugin()),
489
+ asMdastPlugin(packageInstallPlugin()),
490
+ asMdastPlugin(ts2jsPlugin()),
491
+ asMdastPlugin(directiveToCalloutPlugin()),
492
+ asMdastPlugin(githubAlertsPlugin()),
493
+ asMdastPlugin(mermaidPlugin()),
494
+ asMdastPlugin(mathPlugin()),
495
+ ...blumeSharedMdastPlugins(options),
496
+ asMdastPlugin(apiRailPlugin()),
497
+ asMdastPlugin(viewsPlugin()),
498
+ asMdastPlugin(promptTextPlugin()),
499
+ ],
500
+ }),
501
+ options
502
+ );
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Smart punctuation (Astro's `smartypants`, on by default) curls quotes and
3
+ * turns `--`, `---`, and `...` into dashes and an ellipsis as a page parses.
4
+ * That suits prose, but a `<Prompt>`'s body is never shown: it's copied into
5
+ * an AI tool as Markdown, where `lang=“ts”` and `–force` are broken code. This
6
+ * plugin puts back the punctuation the author typed in the body's text.
7
+ *
8
+ * Each text node is compared with its own source: a typographic mark the
9
+ * source doesn't contain came from smart punctuation, so it goes back to the
10
+ * ASCII it was made from. A mark the author typed (or wrote as an entity)
11
+ * stays.
12
+ */
13
+
14
+ import type { MdastNode } from "./mdast.ts";
15
+
16
+ /** Each group of marks smart punctuation makes, and the ASCII it makes them from. */
17
+ const MARKS: readonly { ascii: string; marks: readonly string[] }[] = [
18
+ { ascii: '"', marks: ["“", "”"] },
19
+ { ascii: "'", marks: ["‘", "’"] },
20
+ { ascii: "---", marks: ["—"] },
21
+ { ascii: "--", marks: ["–"] },
22
+ { ascii: "...", marks: ["…"] },
23
+ ];
24
+
25
+ const ANY_MARK = /[“”‘’—–…]/u;
26
+
27
+ /** A character reference (`&rsquo;`, `&#8217;`), which can spell any mark. */
28
+ const ENTITY = /&[#\da-z]+;/iu;
29
+
30
+ /** The text as the author typed it, given the source it was parsed from. */
31
+ export const restorePunctuation = (value: string, source: string): string => {
32
+ if (!ANY_MARK.test(value) || ENTITY.test(source)) {
33
+ return value;
34
+ }
35
+ let restored = value;
36
+ for (const { ascii, marks } of MARKS) {
37
+ if (!marks.some((mark) => source.includes(mark))) {
38
+ for (const mark of marks) {
39
+ restored = restored.replaceAll(mark, ascii);
40
+ }
41
+ }
42
+ }
43
+ return restored;
44
+ };
45
+
46
+ /** The node slice the plugin reads. */
47
+ interface PromptNode extends MdastNode {
48
+ children?: PromptNode[];
49
+ name?: string | null;
50
+ position?: { end: { offset?: number }; start: { offset?: number } };
51
+ value?: string;
52
+ }
53
+
54
+ /** The visitor context slice the plugin reads and writes through. */
55
+ interface PromptContext {
56
+ readonly source: string;
57
+ setProperty: (node: PromptNode, key: "value", value: string) => void;
58
+ }
59
+
60
+ /** Restore every text node under `node`. */
61
+ const restoreTexts = (node: PromptNode, ctx: PromptContext): void => {
62
+ for (const child of node.children ?? []) {
63
+ const start = child.position?.start.offset;
64
+ const end = child.position?.end.offset;
65
+ if (
66
+ child.type === "text" &&
67
+ child.value !== undefined &&
68
+ start !== undefined &&
69
+ end !== undefined
70
+ ) {
71
+ const value = restorePunctuation(
72
+ child.value,
73
+ ctx.source.slice(start, end)
74
+ );
75
+ if (value !== child.value) {
76
+ ctx.setProperty(child, "value", value);
77
+ }
78
+ }
79
+ restoreTexts(child, ctx);
80
+ }
81
+ };
82
+
83
+ const visit = (node: PromptNode, ctx: PromptContext): void => {
84
+ if (node.name === "Prompt") {
85
+ restoreTexts(node, ctx);
86
+ }
87
+ };
88
+
89
+ /** Satteri MDAST plugin that undoes smart punctuation in `<Prompt>` bodies. */
90
+ export const promptTextPlugin = () => ({
91
+ mdxJsxFlowElement: visit,
92
+ mdxJsxTextElement: visit,
93
+ name: "blume-prompt-text",
94
+ options: { position: true },
95
+ });
@@ -3,7 +3,11 @@ import { fileURLToPath } from "node:url";
3
3
  import { dirname, normalize, relative, resolve } from "pathe";
4
4
 
5
5
  import { mountBasePath } from "../core/base-path.ts";
6
- import { isIndexFileName, resolveRelativeHref } from "../core/links.ts";
6
+ import {
7
+ isIndexFileName,
8
+ resolveRelativeHref,
9
+ resolveRootFileHref,
10
+ } from "../core/links.ts";
7
11
  import type { RelativeLinkBase } from "../core/links.ts";
8
12
  import type { MdastNode, MdastValue } from "./mdast.ts";
9
13
  import { routeSnapshotReader } from "./route-snapshot.ts";
@@ -71,6 +75,13 @@ interface LocatedPage extends RelativeLinkBase {
71
75
  /** A JSX component name (`Card`, `Tree.File`), as opposed to an HTML tag. */
72
76
  const COMPONENT_NAME = /^[A-Z]/u;
73
77
 
78
+ /**
79
+ * A root-relative link to a Markdown file (`/guide/new.md#x`), never
80
+ * `//host`: the one root-relative shape this plugin rewrites, when it names a
81
+ * content file.
82
+ */
83
+ const ROOT_FILE_LINK = /^\/(?!\/)[^?#]*\.mdx?(?:[?#]|$)/iu;
84
+
74
85
  /** A plain string attribute value; an expression value isn't a link target. */
75
86
  const isStringValue = (value: MdastValue): value is string =>
76
87
  typeof value === "string";
@@ -146,7 +157,10 @@ export interface RelativeLinksPluginOptions {
146
157
  * snapshot the CLI publishes (or, ejected, the file eject writes), keyed by
147
158
  * collection entry: `docs` entries by their path under `contentRoot`, staged
148
159
  * entries (remote sources) by the longest trailing path that names one. A file
149
- * the snapshot doesn't know keeps its links as written.
160
+ * the snapshot doesn't know keeps its links as written. A root-relative link
161
+ * to a content file (`/guide/new.md`, see `resolveRootFileHref`) lands on that
162
+ * file's page too, from any page; one naming no content file is left as
163
+ * written, since `/guide/new.md` is also a page's Markdown copy.
150
164
  *
151
165
  * A rewritten route already carries `basePath`, and the `deployment.base`
152
166
  * prefix goes on here, unconditionally: the route is one Blume serves, so
@@ -225,11 +239,32 @@ export const relativeLinksPlugin = (
225
239
  return undefined;
226
240
  };
227
241
 
242
+ /**
243
+ * The route a root-relative link to a content file (`/guide/new.md`)
244
+ * means, when it names one (see `resolveRootFileHref`).
245
+ */
246
+ const resolveRootFile = (url: string): string | undefined => {
247
+ const index = contentRoot ? routeIndex() : undefined;
248
+ if (!(contentRoot && index)) {
249
+ return undefined;
250
+ }
251
+ const route = resolveRootFileHref(url, {
252
+ contentRoot,
253
+ deployBase,
254
+ routeOfFile: (file) =>
255
+ index.routes.get(entryKey("docs", relative(contentRoot, file))),
256
+ });
257
+ return route === undefined ? undefined : mountBasePath(deployBase, route);
258
+ };
259
+
228
260
  /** The route `url` means on the page at `fileURL`, when it's relative. */
229
261
  const resolveUrl = (
230
262
  url: string,
231
263
  fileURL: URL | undefined
232
264
  ): string | undefined => {
265
+ if (ROOT_FILE_LINK.test(url)) {
266
+ return resolveRootFile(url);
267
+ }
233
268
  // Cheap first: a target that can't be relative never needs the index. A
234
269
  // dotted name (`./node.js`) passes, since only the index knows its route.
235
270
  if (
@@ -3,35 +3,45 @@ import { readFileSync, statSync } from "node:fs";
3
3
  import { resolve } from "pathe";
4
4
 
5
5
  import { readRuntimeModule } from "../astro/runtime-modules.ts";
6
+ import type { RuntimeModuleId } from "../astro/runtime-modules.ts";
6
7
 
7
8
  /**
8
- * A reader for the `blume:data` snapshot the Markdown link plugins consult for
9
- * the site's routes: the text the CLI publishes, or — in an ejected app, with
10
- * no CLI in the process — the snapshot file eject writes (`dataFile`), re-read
11
- * only when it changes. Resolves to `undefined` when neither is available.
9
+ * A reader for a runtime module snapshot a Markdown plugin consults: the text
10
+ * the CLI publishes under `id`, or — in an ejected app, with no CLI in the
11
+ * process — the file eject writes for it (`file`), re-read only when it
12
+ * changes. Resolves to `undefined` when neither is available.
12
13
  */
13
- export const routeSnapshotReader = (
14
- dataFile?: string
14
+ export const snapshotReader = (
15
+ id: RuntimeModuleId,
16
+ file?: string
15
17
  ): (() => string | undefined) => {
16
- const file = dataFile ? resolve(dataFile) : undefined;
18
+ const path = file ? resolve(file) : undefined;
17
19
  let fileStamp: number | undefined;
18
20
  let fileText: string | undefined;
19
21
 
20
22
  /** The ejected snapshot file's text, re-read only when it changes. */
21
- const readDataFile = (path: string): string | undefined => {
23
+ const readFile = (target: string): string | undefined => {
22
24
  let stamp: number;
23
25
  try {
24
- stamp = statSync(path).mtimeMs;
26
+ stamp = statSync(target).mtimeMs;
25
27
  } catch {
26
28
  return undefined;
27
29
  }
28
30
  if (stamp !== fileStamp) {
29
31
  fileStamp = stamp;
30
- fileText = readFileSync(path, "utf-8");
32
+ fileText = readFileSync(target, "utf-8");
31
33
  }
32
34
  return fileText;
33
35
  };
34
36
 
35
- return () =>
36
- readRuntimeModule("blume:data") ?? (file ? readDataFile(file) : undefined);
37
+ return () => readRuntimeModule(id) ?? (path ? readFile(path) : undefined);
37
38
  };
39
+
40
+ /**
41
+ * A reader for the `blume:data` snapshot the Markdown link plugins consult for
42
+ * the site's routes: the text the CLI publishes, or the snapshot file an
43
+ * ejected app aliases (`dataFile`).
44
+ */
45
+ export const routeSnapshotReader = (
46
+ dataFile?: string
47
+ ): (() => string | undefined) => snapshotReader("blume:data", dataFile);
@@ -0,0 +1,51 @@
1
+ import { fileURLToPath } from "node:url";
2
+
3
+ import { dirname, resolve } from "pathe";
4
+
5
+ import { unmeasurableSvgUrl } from "../core/svg-images.ts";
6
+ import type { MdastNode } from "./mdast.ts";
7
+
8
+ /**
9
+ * Sätteri MDAST plugin that points an image embed of a colocated SVG Astro
10
+ * can't measure (a draw.io export, say: see `core/svg-images.ts`) at its
11
+ * served original, `/blume-assets/content/…`. A relative image is one Astro
12
+ * imports and optimizes, and an import it can't read the size of fails the
13
+ * whole build; a root-relative URL is one it leaves alone, so the page shows
14
+ * the file as it is. Runs before the base links plugin, which mounts
15
+ * `deployment.base` on the URL like any other root-relative image.
16
+ */
17
+
18
+ interface ImageNode extends MdastNode {
19
+ url?: string | null;
20
+ }
21
+
22
+ /** The slice of Sätteri's MDAST visitor context this plugin needs. */
23
+ interface ImageContext {
24
+ fileURL: URL | undefined;
25
+ setProperty: (node: MdastNode, key: "url", value: string) => void;
26
+ }
27
+
28
+ const SVG = /\.svg$/iu;
29
+
30
+ export const svgFallbackPlugin = () => {
31
+ // Content image URLs are keyed by their path from the project root, where
32
+ // Blume runs Astro (see `contentAssetParam`).
33
+ const projectRoot = resolve(".");
34
+ return {
35
+ async image(node: ImageNode, ctx: ImageContext) {
36
+ const { url } = node;
37
+ if (!(ctx.fileURL && url && SVG.test(url))) {
38
+ return;
39
+ }
40
+ const served = await unmeasurableSvgUrl(
41
+ dirname(fileURLToPath(ctx.fileURL)),
42
+ url,
43
+ projectRoot
44
+ );
45
+ if (served) {
46
+ ctx.setProperty(node, "url", served);
47
+ }
48
+ },
49
+ name: "blume-svg-fallback",
50
+ };
51
+ };