blume 2.0.1 → 2.0.3

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 (492) hide show
  1. package/CHANGELOG.md +147 -0
  2. package/dist/cli/{chunk-qwsrynx5.js → chunk-1d7ve1dm.js} +38 -19
  3. package/dist/cli/{chunk-qwsrynx5.js.map → chunk-1d7ve1dm.js.map} +4 -4
  4. package/dist/cli/{chunk-s6jhgk0q.js → chunk-2eytanqx.js} +2 -2
  5. package/dist/cli/{chunk-kdp5q7ke.js → chunk-2hsdwb9n.js} +18 -19
  6. package/dist/cli/{chunk-kdp5q7ke.js.map → chunk-2hsdwb9n.js.map} +2 -2
  7. package/dist/cli/{chunk-hdpx1tax.js → chunk-2z928egk.js} +5 -5
  8. package/dist/cli/{chunk-jwyddg7y.js → chunk-35d4wj9f.js} +30 -24
  9. package/dist/cli/chunk-35d4wj9f.js.map +15 -0
  10. package/dist/cli/{chunk-ah61y8py.js → chunk-364znk6q.js} +2 -2
  11. package/dist/cli/{chunk-jts8mvcz.js → chunk-3em5wd2y.js} +25 -6
  12. package/dist/cli/{chunk-jts8mvcz.js.map → chunk-3em5wd2y.js.map} +3 -3
  13. package/dist/cli/{chunk-s1p84fyh.js → chunk-5f86nr5m.js} +63 -22
  14. package/dist/cli/chunk-5f86nr5m.js.map +11 -0
  15. package/dist/cli/{chunk-2hn4b8z7.js → chunk-5m5nmvyq.js} +170 -76
  16. package/dist/cli/chunk-5m5nmvyq.js.map +13 -0
  17. package/dist/cli/{chunk-epjnccmv.js → chunk-5xvm6tfj.js} +18 -15
  18. package/dist/cli/chunk-5xvm6tfj.js.map +10 -0
  19. package/dist/cli/{chunk-fz5wtpmh.js → chunk-6dsbexzp.js} +18 -15
  20. package/dist/cli/chunk-6dsbexzp.js.map +10 -0
  21. package/dist/cli/{chunk-vtk4a6dg.js → chunk-8ktnccpt.js} +1 -1
  22. package/dist/cli/{chunk-6hsn950k.js → chunk-9t7a85s3.js} +178 -48
  23. package/dist/cli/chunk-9t7a85s3.js.map +10 -0
  24. package/dist/cli/{chunk-fxypxtvm.js → chunk-a58773jm.js} +2 -2
  25. package/dist/cli/{chunk-mb2919y2.js → chunk-acanzt5p.js} +17 -6
  26. package/dist/cli/chunk-acanzt5p.js.map +10 -0
  27. package/dist/cli/{chunk-27g6wdth.js → chunk-akbpwfxc.js} +90 -26
  28. package/dist/cli/chunk-akbpwfxc.js.map +10 -0
  29. package/dist/cli/{chunk-wm7js3j9.js → chunk-b07cmahc.js} +2 -2
  30. package/dist/cli/{chunk-wgm7m9qk.js → chunk-c8chx29p.js} +179 -51
  31. package/dist/cli/chunk-c8chx29p.js.map +36 -0
  32. package/dist/cli/{chunk-qs4q5p4e.js → chunk-crgn1q09.js} +32 -14
  33. package/dist/cli/chunk-crgn1q09.js.map +10 -0
  34. package/dist/cli/chunk-e04dxsz1.js +39 -0
  35. package/dist/cli/chunk-e04dxsz1.js.map +10 -0
  36. package/dist/cli/{chunk-yt5n7ppj.js → chunk-ey84smr6.js} +17 -7
  37. package/dist/cli/chunk-ey84smr6.js.map +10 -0
  38. package/dist/cli/{chunk-f2z5v128.js → chunk-g4hq16wv.js} +14 -15
  39. package/dist/cli/{chunk-f2z5v128.js.map → chunk-g4hq16wv.js.map} +2 -2
  40. package/dist/cli/{chunk-fa25z98p.js → chunk-ga0pf4aj.js} +9 -8
  41. package/dist/cli/chunk-ga0pf4aj.js.map +11 -0
  42. package/dist/cli/{chunk-kpf8rrjc.js → chunk-hm3vjy5s.js} +109 -58
  43. package/dist/cli/chunk-hm3vjy5s.js.map +19 -0
  44. package/dist/cli/{chunk-fs23ddbb.js → chunk-j85vccga.js} +625 -750
  45. package/dist/cli/chunk-j85vccga.js.map +36 -0
  46. package/dist/cli/{chunk-ch6g3ar0.js → chunk-p3v96n38.js} +6 -6
  47. package/dist/cli/{chunk-ch6g3ar0.js.map → chunk-p3v96n38.js.map} +3 -3
  48. package/dist/cli/{chunk-q5163e60.js → chunk-p73c0m7w.js} +21 -19
  49. package/dist/cli/chunk-p73c0m7w.js.map +11 -0
  50. package/dist/cli/{chunk-dh8cwk36.js → chunk-pehfxfta.js} +24 -9
  51. package/dist/cli/chunk-pehfxfta.js.map +10 -0
  52. package/dist/cli/{chunk-qkqwkpte.js → chunk-pv29h0wf.js} +3688 -1313
  53. package/dist/cli/chunk-pv29h0wf.js.map +190 -0
  54. package/dist/cli/{chunk-6vm74dry.js → chunk-q58y5e6a.js} +10 -10
  55. package/dist/cli/{chunk-6vm74dry.js.map → chunk-q58y5e6a.js.map} +3 -3
  56. package/dist/cli/{chunk-zxcczpyx.js → chunk-r20tn01b.js} +1 -1
  57. package/dist/cli/{chunk-m3vmjgmq.js → chunk-r9rcc4w7.js} +16 -8
  58. package/dist/cli/chunk-r9rcc4w7.js.map +10 -0
  59. package/dist/cli/{chunk-5shv93fd.js → chunk-tkacnehg.js} +2 -2
  60. package/dist/cli/{chunk-zxh4d9vy.js → chunk-tzmab476.js} +4 -4
  61. package/dist/cli/{chunk-yw7dm696.js → chunk-vg9r4eb9.js} +24 -11
  62. package/dist/cli/chunk-vg9r4eb9.js.map +14 -0
  63. package/dist/cli/{chunk-79jhk4py.js → chunk-wrr3j9w9.js} +260 -132
  64. package/dist/cli/chunk-wrr3j9w9.js.map +35 -0
  65. package/dist/cli/{chunk-6crbhc3x.js → chunk-yfyb25rh.js} +54 -27
  66. package/dist/cli/chunk-yfyb25rh.js.map +14 -0
  67. package/dist/cli/index.js +162 -35
  68. package/dist/cli/index.js.map +4 -4
  69. package/dist/types/ai/agent-surface.d.ts +32 -0
  70. package/dist/types/ai/api-catalog.d.ts +7 -1
  71. package/dist/types/ai/ask-context.d.ts +7 -0
  72. package/dist/types/ai/component-markdown.d.ts +4 -4
  73. package/dist/types/ai/link-headers.d.ts +8 -1
  74. package/dist/types/ai/openapi-components.d.ts +5 -2
  75. package/dist/types/ai/relative-links.d.ts +11 -4
  76. package/dist/types/ai/skills.d.ts +4 -1
  77. package/dist/types/ai/static-expression.d.ts +28 -0
  78. package/dist/types/ai/tar.d.ts +1 -3
  79. package/dist/types/analytics/databuddy.d.ts +43 -0
  80. package/dist/types/analytics/index.d.ts +4 -0
  81. package/dist/types/analytics/one-dollar-stats.d.ts +48 -0
  82. package/dist/types/analytics/schema.d.ts +28 -0
  83. package/dist/types/astro/integration.d.ts +3 -2
  84. package/dist/types/cli/env.d.ts +5 -0
  85. package/dist/types/cli/init/scaffold.d.ts +19 -3
  86. package/dist/types/cli/init/starter-spec.d.ts +11 -0
  87. package/dist/types/core/base-path.d.ts +24 -1
  88. package/dist/types/core/config-input.d.ts +13 -11
  89. package/dist/types/core/config.d.ts +2 -2
  90. package/dist/types/core/directive-diagnostics.d.ts +12 -0
  91. package/dist/types/core/graph.d.ts +2 -0
  92. package/dist/types/core/heading-markers.d.ts +5 -7
  93. package/dist/types/core/i18n-ui.d.ts +31 -0
  94. package/dist/types/core/i18n.d.ts +9 -1
  95. package/dist/types/core/last-modified.d.ts +10 -0
  96. package/dist/types/core/links.d.ts +3 -1
  97. package/dist/types/core/load-module.d.ts +10 -0
  98. package/dist/types/core/locale-links.d.ts +12 -2
  99. package/dist/types/core/meta.d.ts +13 -1
  100. package/dist/types/core/nav-diagnostics.d.ts +10 -0
  101. package/dist/types/core/navigation.d.ts +38 -0
  102. package/dist/types/core/ordering-prefix.d.ts +4 -0
  103. package/dist/types/core/safe-links.d.ts +3 -1
  104. package/dist/types/core/schema.d.ts +59 -13
  105. package/dist/types/core/sources/github-releases.d.ts +5 -0
  106. package/dist/types/core/sources/lower.d.ts +38 -13
  107. package/dist/types/core/sources/normalize.d.ts +23 -2
  108. package/dist/types/core/sources/remote.d.ts +11 -1
  109. package/dist/types/core/sources/resolve.d.ts +12 -0
  110. package/dist/types/core/sources/types.d.ts +31 -0
  111. package/dist/types/core/sources/watch.d.ts +12 -5
  112. package/dist/types/core/standard-schema.d.ts +5 -0
  113. package/dist/types/core/types.d.ts +9 -0
  114. package/dist/types/deploy/adapters/node.d.ts +5 -2
  115. package/dist/types/deploy/adapters/types.d.ts +7 -0
  116. package/dist/types/deploy/artifacts.d.ts +6 -4
  117. package/dist/types/deploy/cloudflare-negotiation.d.ts +3 -2
  118. package/dist/types/deploy/headers.d.ts +36 -7
  119. package/dist/types/deploy/node-headers.d.ts +43 -8
  120. package/dist/types/deploy/platforms/netlify.d.ts +27 -2
  121. package/dist/types/deploy/platforms/node.d.ts +6 -5
  122. package/dist/types/deploy/platforms/types.d.ts +15 -0
  123. package/dist/types/deploy/platforms/vercel.d.ts +3 -2
  124. package/dist/types/deploy/redirects.d.ts +31 -15
  125. package/dist/types/deploy/vercel-negotiation.d.ts +3 -2
  126. package/dist/types/markdown/directives.d.ts +62 -0
  127. package/dist/types/markdown/features.d.ts +21 -0
  128. package/dist/types/markdown/mdast.d.ts +63 -0
  129. package/dist/types/openapi/asyncapi.d.ts +4 -2
  130. package/dist/types/openapi/model.d.ts +20 -6
  131. package/dist/types/search/sync/algolia.d.ts +3 -1
  132. package/docs/01-quickstart.mdx +3 -2
  133. package/docs/02-deployment.mdx +20 -9
  134. package/docs/08-faq.mdx +10 -3
  135. package/docs/advanced/changelog.mdx +1 -1
  136. package/docs/advanced/custom-pages.mdx +7 -5
  137. package/docs/cli/audit.mdx +19 -3
  138. package/docs/cli/doctor.mdx +2 -2
  139. package/docs/cli/evals.mdx +4 -4
  140. package/docs/cli/index.mdx +4 -1
  141. package/docs/cli/translate.mdx +4 -4
  142. package/docs/cli/version.mdx +1 -1
  143. package/docs/configuration/analytics.mdx +42 -2
  144. package/docs/configuration/assistant.mdx +1 -1
  145. package/docs/configuration/customization.mdx +6 -3
  146. package/docs/configuration/index.mdx +5 -3
  147. package/docs/configuration/search.mdx +2 -2
  148. package/docs/content/components.mdx +2 -2
  149. package/docs/content/frontmatter.mdx +5 -1
  150. package/docs/content/i18n.mdx +3 -1
  151. package/docs/content/index.mdx +8 -4
  152. package/docs/content/islands.mdx +1 -1
  153. package/docs/content/meta.mdx +7 -3
  154. package/docs/content/navigation.mdx +29 -4
  155. package/docs/content/sources.mdx +18 -16
  156. package/docs/content/syntax.mdx +14 -0
  157. package/docs/content/versioning.mdx +2 -1
  158. package/docs/discoverability/agent-discovery.mdx +22 -8
  159. package/docs/discoverability/index.mdx +4 -3
  160. package/docs/discoverability/llms-txt.mdx +2 -5
  161. package/docs/discoverability/markdown.mdx +6 -4
  162. package/docs/discoverability/mcp.mdx +4 -0
  163. package/docs/discoverability/metadata.mdx +3 -2
  164. package/docs/discoverability/open-graph.mdx +8 -4
  165. package/docs/discoverability/rss.mdx +4 -2
  166. package/docs/references/asyncapi.mdx +2 -2
  167. package/docs/references/graphql.mdx +2 -2
  168. package/docs/references/openapi.mdx +6 -4
  169. package/package.json +1 -1
  170. package/skills/blume-migrate/SKILL.md +6 -6
  171. package/skills/blume-migrate/references/docusaurus.md +6 -6
  172. package/skills/blume-migrate/references/fumadocs.md +1 -1
  173. package/skills/blume-migrate/references/mintlify.md +3 -3
  174. package/skills/blume-migrate/references/monorepo.md +1 -1
  175. package/skills/blume-migrate/references/nextra.md +2 -2
  176. package/skills/blume-migrate/references/starlight.md +5 -5
  177. package/skills/blume-migrate/scripts/mintlify-codemod.mjs +9 -4
  178. package/skills/blume-update-docs/references/audit-checklist.md +1 -1
  179. package/src/ai/agent-readability.ts +2 -2
  180. package/src/ai/agent-surface.ts +56 -0
  181. package/src/ai/ai-catalog.ts +6 -2
  182. package/src/ai/api/handlers.ts +2 -2
  183. package/src/ai/api/spec.ts +17 -4
  184. package/src/ai/api-catalog.ts +13 -3
  185. package/src/ai/ask-context.ts +14 -2
  186. package/src/ai/ask-data.ts +26 -12
  187. package/src/ai/changelog-markdown.ts +2 -2
  188. package/src/ai/component-markdown.ts +43 -37
  189. package/src/ai/link-headers.ts +22 -8
  190. package/src/ai/llms.ts +22 -10
  191. package/src/ai/markdown.ts +23 -11
  192. package/src/ai/mcp/discovery.ts +2 -2
  193. package/src/ai/mcp/query.ts +2 -2
  194. package/src/ai/mcp/server.ts +80 -9
  195. package/src/ai/openapi-components.ts +22 -6
  196. package/src/ai/relative-links.ts +99 -15
  197. package/src/ai/serializers.ts +2 -1
  198. package/src/ai/skills.ts +14 -1
  199. package/src/ai/static-expression.ts +416 -0
  200. package/src/ai/tar.ts +139 -12
  201. package/src/ai/visibility.ts +45 -14
  202. package/src/analytics/databuddy.ts +67 -0
  203. package/src/analytics/head.ts +8 -0
  204. package/src/analytics/index.ts +7 -0
  205. package/src/analytics/one-dollar-stats.ts +86 -0
  206. package/src/analytics/posthog.ts +21 -3
  207. package/src/analytics/schema.ts +4 -0
  208. package/src/astro/generate.ts +72 -28
  209. package/src/astro/include-hmr.ts +31 -12
  210. package/src/astro/include-refresh.ts +19 -8
  211. package/src/astro/integration.ts +108 -14
  212. package/src/astro/runtime-modules.ts +28 -13
  213. package/src/astro/templates.ts +139 -57
  214. package/src/audit/catalog.ts +2 -2
  215. package/src/audit/checks/assets.ts +25 -5
  216. package/src/audit/checks/content.ts +20 -2
  217. package/src/audit/checks/i18n.ts +46 -8
  218. package/src/audit/checks/indexability.ts +39 -19
  219. package/src/audit/checks/links.ts +22 -1
  220. package/src/audit/checks/llms.ts +6 -3
  221. package/src/audit/checks/network.ts +3 -1
  222. package/src/audit/checks/og-image.ts +10 -0
  223. package/src/audit/checks/robots.ts +6 -1
  224. package/src/audit/checks/sitemap.ts +56 -31
  225. package/src/audit/checks/social.ts +21 -2
  226. package/src/audit/crawl.ts +88 -15
  227. package/src/audit/graph.ts +3 -1
  228. package/src/audit/report.ts +54 -17
  229. package/src/audit/run.ts +13 -9
  230. package/src/audit/snapshot.ts +5 -0
  231. package/src/audit/types.ts +18 -0
  232. package/src/audit/url.ts +37 -6
  233. package/src/cli/args.ts +32 -0
  234. package/src/cli/build-failure.ts +50 -0
  235. package/src/cli/commands/audit.ts +11 -1
  236. package/src/cli/commands/build.ts +19 -5
  237. package/src/cli/commands/check.ts +2 -0
  238. package/src/cli/commands/doctor.ts +3 -1
  239. package/src/cli/commands/eval.ts +20 -18
  240. package/src/cli/commands/init.ts +15 -5
  241. package/src/cli/commands/preview.ts +15 -0
  242. package/src/cli/commands/sync.ts +2 -0
  243. package/src/cli/commands/translate.ts +18 -18
  244. package/src/cli/commands/upgrade.ts +11 -0
  245. package/src/cli/commands/validate.ts +3 -1
  246. package/src/cli/dev-lock.ts +157 -37
  247. package/src/cli/eject-scripts.ts +32 -7
  248. package/src/cli/env.ts +12 -1
  249. package/src/cli/init/scaffold.ts +117 -11
  250. package/src/cli/init/starter-spec.ts +235 -0
  251. package/src/cli/report-format.ts +11 -6
  252. package/src/components/colors.ts +19 -0
  253. package/src/components/content/AccordionItem.astro +26 -22
  254. package/src/components/content/Badge.astro +7 -12
  255. package/src/components/content/Card.astro +2 -2
  256. package/src/components/content/Component.astro +11 -5
  257. package/src/components/content/Expandable.astro +5 -1
  258. package/src/components/content/Frame.astro +2 -7
  259. package/src/components/content/GithubInfo.astro +12 -2
  260. package/src/components/content/Prompt.astro +2 -7
  261. package/src/components/content/Tab.astro +0 -1
  262. package/src/components/content/Tabs.astro +58 -5
  263. package/src/components/content/Tile.astro +1 -1
  264. package/src/components/content/Tooltip.astro +69 -7
  265. package/src/components/content/Tree.astro +7 -2
  266. package/src/components/content/TypeTable.astro +10 -5
  267. package/src/components/content/Update.astro +8 -2
  268. package/src/components/content/auto-type-table.ts +4 -1
  269. package/src/components/content/badge-color.ts +20 -0
  270. package/src/components/content/base-href.ts +14 -22
  271. package/src/components/content/inline-markdown.ts +27 -7
  272. package/src/components/copy-feedback.ts +35 -8
  273. package/src/components/islands/assistant.tsx +37 -6
  274. package/src/components/islands/base-path.ts +47 -10
  275. package/src/components/islands/hooks.ts +42 -21
  276. package/src/components/islands/webmcp.ts +16 -10
  277. package/src/components/layout/Banner.astro +23 -4
  278. package/src/components/layout/Breadcrumbs.astro +5 -2
  279. package/src/components/layout/DiscoveryLinks.astro +9 -5
  280. package/src/components/layout/Header.astro +23 -10
  281. package/src/components/layout/LanguageSwitcher.astro +2 -2
  282. package/src/components/layout/Logo.astro +5 -0
  283. package/src/components/layout/NavSelector.astro +8 -3
  284. package/src/components/layout/NavTabMenu.astro +133 -0
  285. package/src/components/layout/NavTree.astro +31 -12
  286. package/src/components/layout/NavTreeCache.astro +5 -2
  287. package/src/components/layout/NavTreeScript.astro +45 -5
  288. package/src/components/layout/PageActions.astro +4 -3
  289. package/src/components/layout/PageLayout.astro +28 -13
  290. package/src/components/layout/Pagination.astro +3 -3
  291. package/src/components/layout/ReferenceLayout.astro +4 -1
  292. package/src/components/layout/RootLayout.astro +25 -12
  293. package/src/components/layout/Search.astro +38 -14
  294. package/src/components/layout/VersionBanner.astro +2 -2
  295. package/src/components/layout/analytics-client.ts +14 -0
  296. package/src/components/layout/toc-active.ts +41 -0
  297. package/src/components/layout/toc-element.ts +8 -14
  298. package/src/components/openapi/ApiTagOperations.astro +2 -2
  299. package/src/components/openapi/AsyncApiOperation.astro +7 -4
  300. package/src/components/openapi/GraphqlChip.astro +2 -2
  301. package/src/components/openapi/GraphqlType.astro +11 -3
  302. package/src/components/openapi/MessageComposer.astro +1 -1
  303. package/src/components/openapi/Operation.astro +23 -5
  304. package/src/components/openapi/PanelTabs.astro +4 -1
  305. package/src/components/openapi/Playground.astro +8 -2
  306. package/src/components/openapi/RequestPanel.astro +13 -6
  307. package/src/components/openapi/SchemaProperty.astro +11 -33
  308. package/src/components/openapi/SchemaTable.astro +34 -52
  309. package/src/components/openapi/async.ts +38 -6
  310. package/src/components/openapi/helpers.ts +100 -12
  311. package/src/components/openapi/message-composer.ts +14 -1
  312. package/src/components/openapi/message-model.ts +12 -2
  313. package/src/components/openapi/message.ts +21 -3
  314. package/src/components/openapi/operation-model.ts +119 -17
  315. package/src/components/openapi/panel.ts +31 -5
  316. package/src/components/openapi/param-style.ts +181 -0
  317. package/src/components/openapi/playground-client.ts +110 -23
  318. package/src/components/openapi/playground-schema.ts +25 -6
  319. package/src/components/openapi/request.ts +192 -26
  320. package/src/components/openapi/schema-tree.ts +209 -0
  321. package/src/components/openapi/snippets.ts +113 -19
  322. package/src/components/openapi/validate-json.ts +1 -1
  323. package/src/components/openapi/ws-client.ts +18 -2
  324. package/src/core/base-path.ts +69 -8
  325. package/src/core/config-input.ts +13 -11
  326. package/src/core/config.ts +18 -4
  327. package/src/core/diagnostics.ts +219 -30
  328. package/src/core/directive-diagnostics.ts +99 -0
  329. package/src/core/frontmatter.ts +21 -18
  330. package/src/core/graph.ts +120 -11
  331. package/src/core/heading-markers.ts +5 -18
  332. package/src/core/i18n-ui.ts +39 -2
  333. package/src/core/i18n.ts +44 -5
  334. package/src/core/last-modified.ts +25 -3
  335. package/src/core/links.ts +7 -4
  336. package/src/core/load-module.ts +20 -0
  337. package/src/core/locale-links.ts +22 -18
  338. package/src/core/manifest.ts +3 -2
  339. package/src/core/meta.ts +89 -12
  340. package/src/core/nav-diagnostics.ts +56 -1
  341. package/src/core/navigation.ts +272 -83
  342. package/src/core/ordering-prefix.ts +27 -0
  343. package/src/core/project-graph.ts +50 -6
  344. package/src/core/safe-href.ts +53 -1
  345. package/src/core/safe-links.ts +11 -2
  346. package/src/core/schema.ts +94 -18
  347. package/src/core/sources/assets.ts +83 -41
  348. package/src/core/sources/contentful-rich-text.ts +28 -20
  349. package/src/core/sources/contentful.ts +25 -12
  350. package/src/core/sources/filesystem.ts +25 -3
  351. package/src/core/sources/github-releases.ts +131 -11
  352. package/src/core/sources/lexical.ts +30 -20
  353. package/src/core/sources/lower.ts +276 -56
  354. package/src/core/sources/mdx-remote.ts +52 -16
  355. package/src/core/sources/normalize.ts +456 -119
  356. package/src/core/sources/notion.ts +77 -34
  357. package/src/core/sources/obsidian.ts +40 -9
  358. package/src/core/sources/payload.ts +1 -0
  359. package/src/core/sources/portable-text.ts +85 -49
  360. package/src/core/sources/remote.ts +18 -2
  361. package/src/core/sources/resolve.ts +52 -33
  362. package/src/core/sources/sanity.ts +1 -0
  363. package/src/core/sources/strapi-blocks.ts +28 -19
  364. package/src/core/sources/strapi.ts +1 -0
  365. package/src/core/sources/types.ts +31 -0
  366. package/src/core/sources/watch.ts +20 -7
  367. package/src/core/standard-schema.ts +10 -6
  368. package/src/core/types.ts +9 -0
  369. package/src/core/ui-packs/ar.ts +13 -0
  370. package/src/core/ui-packs/bg.ts +13 -0
  371. package/src/core/ui-packs/bn.ts +13 -0
  372. package/src/core/ui-packs/ca.ts +13 -0
  373. package/src/core/ui-packs/cs.ts +13 -0
  374. package/src/core/ui-packs/da.ts +13 -0
  375. package/src/core/ui-packs/de.ts +13 -0
  376. package/src/core/ui-packs/el.ts +13 -0
  377. package/src/core/ui-packs/es.ts +13 -0
  378. package/src/core/ui-packs/fa.ts +13 -0
  379. package/src/core/ui-packs/fi.ts +13 -0
  380. package/src/core/ui-packs/fr.ts +13 -0
  381. package/src/core/ui-packs/he.ts +13 -0
  382. package/src/core/ui-packs/hi.ts +13 -0
  383. package/src/core/ui-packs/hr.ts +13 -0
  384. package/src/core/ui-packs/hu.ts +13 -0
  385. package/src/core/ui-packs/id.ts +13 -0
  386. package/src/core/ui-packs/it.ts +13 -0
  387. package/src/core/ui-packs/ja.ts +13 -0
  388. package/src/core/ui-packs/ko.ts +13 -0
  389. package/src/core/ui-packs/nl.ts +13 -0
  390. package/src/core/ui-packs/no.ts +13 -0
  391. package/src/core/ui-packs/pl.ts +13 -0
  392. package/src/core/ui-packs/pt-br.ts +13 -0
  393. package/src/core/ui-packs/pt.ts +13 -0
  394. package/src/core/ui-packs/ro.ts +13 -0
  395. package/src/core/ui-packs/ru.ts +13 -0
  396. package/src/core/ui-packs/sk.ts +13 -0
  397. package/src/core/ui-packs/sr.ts +13 -0
  398. package/src/core/ui-packs/sv.ts +13 -0
  399. package/src/core/ui-packs/th.ts +13 -0
  400. package/src/core/ui-packs/tr.ts +13 -0
  401. package/src/core/ui-packs/uk.ts +13 -0
  402. package/src/core/ui-packs/vi.ts +13 -0
  403. package/src/core/ui-packs/zh-tw.ts +13 -0
  404. package/src/core/ui-packs/zh.ts +13 -0
  405. package/src/core/version-cut.ts +69 -17
  406. package/src/core/versions.ts +4 -1
  407. package/src/deploy/adapters/node.ts +5 -2
  408. package/src/deploy/adapters/registry.ts +2 -1
  409. package/src/deploy/adapters/types.ts +13 -1
  410. package/src/deploy/artifacts.ts +52 -11
  411. package/src/deploy/cloudflare-negotiation.ts +27 -16
  412. package/src/deploy/headers.ts +75 -55
  413. package/src/deploy/node-headers.ts +148 -27
  414. package/src/deploy/platforms/cloudflare.ts +13 -3
  415. package/src/deploy/platforms/netlify.ts +83 -5
  416. package/src/deploy/platforms/node.ts +8 -5
  417. package/src/deploy/platforms/static.ts +2 -0
  418. package/src/deploy/platforms/types.ts +15 -0
  419. package/src/deploy/platforms/vercel.ts +10 -3
  420. package/src/deploy/redirects.ts +68 -21
  421. package/src/deploy/robots.ts +2 -2
  422. package/src/deploy/rss.ts +4 -3
  423. package/src/deploy/sitemap.ts +7 -5
  424. package/src/deploy/vercel-negotiation.ts +35 -3
  425. package/src/eval/agents.ts +10 -2
  426. package/src/eval/run.ts +25 -0
  427. package/src/markdown/base-links.ts +74 -29
  428. package/src/markdown/directives.ts +242 -36
  429. package/src/markdown/features.ts +17 -0
  430. package/src/markdown/index.ts +13 -9
  431. package/src/markdown/mdast.ts +5 -2
  432. package/src/markdown/relative-links.ts +15 -27
  433. package/src/markdown/route-snapshot.ts +37 -0
  434. package/src/og/card.ts +149 -9
  435. package/src/og/derive.ts +156 -5
  436. package/src/og/index.ts +1 -0
  437. package/src/openapi/asyncapi.ts +4 -2
  438. package/src/openapi/graphql-build.ts +28 -2
  439. package/src/openapi/model.ts +99 -27
  440. package/src/openapi/proxy.ts +63 -10
  441. package/src/openapi/render-mdx.ts +51 -3
  442. package/src/registry/eject.ts +297 -43
  443. package/src/search/adapters/version-scope.ts +30 -0
  444. package/src/search/documents.ts +89 -27
  445. package/src/search/popular.ts +2 -1
  446. package/src/search/sync/algolia.ts +36 -2
  447. package/src/seo/jsonld.ts +7 -3
  448. package/src/sources/registry.ts +5 -0
  449. package/src/theme/entry.ts +11 -4
  450. package/src/translate/agents.ts +6 -1
  451. package/src/translate/ledger.ts +26 -3
  452. package/src/translate/meta.ts +76 -24
  453. package/src/translate/run.ts +14 -8
  454. package/src/translate/validate.ts +14 -2
  455. package/src/translate/work-list.ts +91 -19
  456. package/src/upgrade/upgrade.ts +36 -4
  457. package/dist/cli/chunk-27g6wdth.js.map +0 -10
  458. package/dist/cli/chunk-2hn4b8z7.js.map +0 -12
  459. package/dist/cli/chunk-6crbhc3x.js.map +0 -14
  460. package/dist/cli/chunk-6hsn950k.js.map +0 -10
  461. package/dist/cli/chunk-79jhk4py.js.map +0 -35
  462. package/dist/cli/chunk-82bbrxdn.js +0 -51
  463. package/dist/cli/chunk-82bbrxdn.js.map +0 -10
  464. package/dist/cli/chunk-abh8yjkn.js +0 -31
  465. package/dist/cli/chunk-abh8yjkn.js.map +0 -10
  466. package/dist/cli/chunk-ce574jw2.js +0 -23
  467. package/dist/cli/chunk-ce574jw2.js.map +0 -10
  468. package/dist/cli/chunk-dh8cwk36.js.map +0 -10
  469. package/dist/cli/chunk-epjnccmv.js.map +0 -10
  470. package/dist/cli/chunk-fa25z98p.js.map +0 -11
  471. package/dist/cli/chunk-fs23ddbb.js.map +0 -35
  472. package/dist/cli/chunk-fz5wtpmh.js.map +0 -10
  473. package/dist/cli/chunk-jwyddg7y.js.map +0 -15
  474. package/dist/cli/chunk-kpf8rrjc.js.map +0 -19
  475. package/dist/cli/chunk-m3vmjgmq.js.map +0 -10
  476. package/dist/cli/chunk-mb2919y2.js.map +0 -10
  477. package/dist/cli/chunk-q5163e60.js.map +0 -11
  478. package/dist/cli/chunk-qkqwkpte.js.map +0 -182
  479. package/dist/cli/chunk-qs4q5p4e.js.map +0 -10
  480. package/dist/cli/chunk-s1p84fyh.js.map +0 -10
  481. package/dist/cli/chunk-wgm7m9qk.js.map +0 -36
  482. package/dist/cli/chunk-yt5n7ppj.js.map +0 -10
  483. package/dist/cli/chunk-yw7dm696.js.map +0 -14
  484. /package/dist/cli/{chunk-s6jhgk0q.js.map → chunk-2eytanqx.js.map} +0 -0
  485. /package/dist/cli/{chunk-hdpx1tax.js.map → chunk-2z928egk.js.map} +0 -0
  486. /package/dist/cli/{chunk-ah61y8py.js.map → chunk-364znk6q.js.map} +0 -0
  487. /package/dist/cli/{chunk-vtk4a6dg.js.map → chunk-8ktnccpt.js.map} +0 -0
  488. /package/dist/cli/{chunk-fxypxtvm.js.map → chunk-a58773jm.js.map} +0 -0
  489. /package/dist/cli/{chunk-wm7js3j9.js.map → chunk-b07cmahc.js.map} +0 -0
  490. /package/dist/cli/{chunk-zxcczpyx.js.map → chunk-r20tn01b.js.map} +0 -0
  491. /package/dist/cli/{chunk-5shv93fd.js.map → chunk-tkacnehg.js.map} +0 -0
  492. /package/dist/cli/{chunk-zxh4d9vy.js.map → chunk-tzmab476.js.map} +0 -0
@@ -1,5 +1,11 @@
1
- import { isInternalPath, withComposedBasePath } from "../core/base-path.ts";
1
+ import {
2
+ isInternalPath,
3
+ withAuthoredBasePath,
4
+ withBasePath,
5
+ } from "../core/base-path.ts";
6
+ import { servesRoute } from "../core/locale-links.ts";
2
7
  import type { MdastNode } from "./mdast.ts";
8
+ import { routeSnapshotReader } from "./route-snapshot.ts";
3
9
 
4
10
  interface UrlNode extends MdastNode {
5
11
  url?: string | null;
@@ -14,47 +20,86 @@ interface MdastUrlContext {
14
20
  setProperty: (node: MdastNode, key: "url", value: string) => void;
15
21
  }
16
22
 
17
- /**
18
- * A path whose final segment carries a file extension (`/spec.pdf`, `/logo.svg`)
19
- * — treated as a public asset, which Blume serves from `public/` at the site
20
- * root and does *not* move under `basePath`. Bare page links (`/guide`) have no
21
- * extension. The rare dotted route (`/releases/v1.0`) is left un-based here; the
22
- * build-time link checker still resolves it against the route set.
23
- */
24
- const ASSET_PATH = /\.[a-z0-9]+$/iu;
25
-
26
- /** Strip any `#fragment`/`?query` so only the path is extension-tested. */
27
- const pathOf = (url: string): string => url.replace(/[#?].*$/u, "");
28
-
29
23
  /** Only a string URL can be rebased; MDAST allows null or absent urls. */
30
24
  const isUrl = (url: string | null | undefined): url is string =>
31
25
  typeof url === "string";
32
26
 
27
+ export interface BaseLinksPluginOptions {
28
+ /**
29
+ * The `blume:data` JSON file, for an ejected app: with no CLI in the process
30
+ * to publish the snapshot, the plugin reads the file eject writes instead.
31
+ */
32
+ dataFile?: string;
33
+ }
34
+
33
35
  /**
34
- * Satteri MDAST plugin that prepends the served-URL base — `deployment.base`
35
- * layered over the site-wide `basePath` — to root-relative internal page links
36
- * (`[x](/guide)` -> `/base/docs/guide`), so authors write links as if mounted
37
- * at root. Idempotent per layer (via `withComposedBasePath`, so a hand-written
38
- * `/docs/x` isn't double-prefixed) and inert for external URLs, fragments,
39
- * relative paths, images, and asset links. Only constructed when a base is set
40
- * (see `markdown/index.ts`).
36
+ * Satteri MDAST plugin that prepends the served-URL base to root-relative
37
+ * internal links, so authors write links as if mounted at root. A page link
38
+ * gains `deployment.base` layered over the site-wide `basePath` (`[x](/guide)`
39
+ * -> `/base/docs/guide`); a public asset — an image, or a link to a file like
40
+ * `/spec.pdf` (see `withAuthoredBasePath`) — gains `deployment.base` alone,
41
+ * since Astro serves `public/` under it but never under `basePath`.
42
+ * Idempotent per layer (a hand-written `/docs/x` isn't double-prefixed) and
43
+ * inert for external URLs, fragments, and relative paths. Only constructed
44
+ * when a base is set (see `markdown/index.ts`).
41
45
  */
42
- export const baseLinksPlugin = (deployBase: string, basePath: string) => {
43
- const rebase = (node: UrlNode, ctx: MdastUrlContext): void => {
46
+ export const baseLinksPlugin = (
47
+ deployBase: string,
48
+ basePath: string,
49
+ options: BaseLinksPluginOptions = {}
50
+ ) => {
51
+ const readSnapshot = routeSnapshotReader(options.dataFile);
52
+ // Parsed once per published snapshot, like the relative-links index.
53
+ let cached: { routes: Set<string>; text: string } | undefined;
54
+
55
+ /** Every route the site serves (base-prefixed), from the route snapshot. */
56
+ const servedRoutes = (): ReadonlySet<string> => {
57
+ const text = readSnapshot();
58
+ if (text === undefined) {
59
+ return new Set();
60
+ }
61
+ if (cached?.text !== text) {
62
+ const data: { routes: { path: string }[] } = JSON.parse(text);
63
+ cached = {
64
+ routes: new Set(data.routes.map((route) => route.path)),
65
+ text,
66
+ };
67
+ }
68
+ return cached.routes;
69
+ };
70
+
71
+ /** Whether a page is served at a based, fragment-less path. */
72
+ const servesPage = (route: string): boolean =>
73
+ servesRoute(servedRoutes(), route);
74
+
75
+ /** Record `based(url)` on the node when its root-relative URL changes. */
76
+ const rebase = (
77
+ node: UrlNode,
78
+ ctx: MdastUrlContext,
79
+ based: (url: string) => string
80
+ ): void => {
44
81
  const { url } = node;
45
- if (isUrl(url) && isInternalPath(url) && !ASSET_PATH.test(pathOf(url))) {
46
- const next = withComposedBasePath(deployBase, basePath, url);
82
+ if (isUrl(url) && isInternalPath(url)) {
83
+ const next = based(url);
47
84
  if (next !== url) {
48
85
  ctx.setProperty(node, "url", next);
49
86
  }
50
87
  }
51
88
  };
52
- // `link` covers inline links; `definition` covers reference-style link
53
- // definitions (`[x]: /guide`). `image` is intentionally excluded — images are
54
- // public assets served at the site root, unaffected by `basePath`.
89
+ const rebaseLink = (node: UrlNode, ctx: MdastUrlContext): void =>
90
+ rebase(node, ctx, (url) =>
91
+ withAuthoredBasePath(deployBase, basePath, url, servesPage)
92
+ );
93
+ // An image is always a file, never a page: `public/` (or a generated asset
94
+ // endpoint), served under the deployment base but not `basePath`.
95
+ const rebaseImage = (node: UrlNode, ctx: MdastUrlContext): void =>
96
+ rebase(node, ctx, (url) => withBasePath(deployBase, url));
97
+ // `link` covers inline links; `definition` covers reference-style
98
+ // definitions (`[x]: /guide`), which a link or an image may cite.
55
99
  return {
56
- definition: rebase,
57
- link: rebase,
100
+ definition: rebaseLink,
101
+ image: rebaseImage,
102
+ link: rebaseLink,
58
103
  name: "blume-base-links",
59
104
  };
60
105
  };
@@ -3,15 +3,30 @@ import { toString as mdastToString } from "mdast-util-to-string";
3
3
  import { jsxAttribute, jsxFlowElement } from "./mdast.ts";
4
4
  import type { MdastNode, MdastVisitorContext } from "./mdast.ts";
5
5
 
6
- interface DirectiveNode extends MdastNode {
6
+ export interface DirectiveNode extends MdastNode {
7
7
  attributes?: Record<string, string | null | undefined> | null;
8
8
  // Satteri gives an empty container directive (`:::note\n:::`) `children: null`.
9
9
  children?: MdastNode[] | null;
10
10
  name: string;
11
+ position?: {
12
+ start?: { offset?: number };
13
+ end?: { offset?: number };
14
+ };
15
+ }
16
+
17
+ /**
18
+ * The visitor-context slice the directive visitors use. `source` is the page
19
+ * the directive offsets index into; without it the literal fallback rebuilds
20
+ * a directive from its node instead. `parent` walks up to an enclosing
21
+ * container directive, which renders the directives inside it itself.
22
+ */
23
+ interface DirectiveVisitorContext extends MdastVisitorContext {
24
+ parent?: (node: MdastNode) => MdastNode | undefined;
25
+ source?: string;
11
26
  }
12
27
 
13
28
  /** Directive names that map directly onto a Callout type. */
14
- const CALLOUT_TYPES = new Set([
29
+ export const CALLOUT_TYPES: ReadonlySet<string> = new Set([
15
30
  "danger",
16
31
  "info",
17
32
  "note",
@@ -25,7 +40,7 @@ interface CalloutAliases {
25
40
  [alias: string]: string;
26
41
  }
27
42
 
28
- const ALIASES: CalloutAliases = {
43
+ export const CALLOUT_ALIASES: Readonly<CalloutAliases> = {
29
44
  caution: "warning",
30
45
  error: "danger",
31
46
  important: "note",
@@ -38,48 +53,239 @@ export const calloutTypeFor = (name: string): string | null => {
38
53
  if (CALLOUT_TYPES.has(lower)) {
39
54
  return lower;
40
55
  }
41
- return ALIASES[lower] ?? null;
56
+ return CALLOUT_ALIASES[lower] ?? null;
57
+ };
58
+
59
+ /** The markers that open a text (`:name`) and a leaf (`::name`) directive. */
60
+ const LITERAL_MARKERS = new Map([
61
+ ["leafDirective", "::"],
62
+ ["textDirective", ":"],
63
+ ]);
64
+
65
+ /** A directive's `[label]` rebuilt from its label's text, or nothing. */
66
+ const labelSource = (label: MdastNode[]): string =>
67
+ label.length > 0
68
+ ? `[${mdastToString(label, { includeImageAlt: false })}]`
69
+ : "";
70
+
71
+ /** A directive's `{attributes}` rebuilt from its node, or nothing. */
72
+ const attributeSource = (node: DirectiveNode): string => {
73
+ const attributes = Object.entries(node.attributes ?? {}).map(
74
+ ([key, value]) => (value ? `${key}="${value}"` : key)
75
+ );
76
+ return attributes.length > 0 ? `{${attributes.join(" ")}}` : "";
77
+ };
78
+
79
+ /** The source a directive node spans, when its offsets point into `source`. */
80
+ const spannedSource = (node: DirectiveNode, source: string): string | null => {
81
+ const start = node.position?.start?.offset;
82
+ const end = node.position?.end?.offset;
83
+ return start !== undefined && end !== undefined
84
+ ? source.slice(start, end)
85
+ : null;
42
86
  };
43
87
 
44
88
  /**
45
- * Satteri MDAST plugin mapping container directives (`:::note`, `:::warning`,
46
- * `:::tip`, …) onto Blume's `<Callout>` component. The title comes from a
47
- * `[label]` or a `{title="…"}` attribute; the body becomes the callout content.
48
- * Directive names that are not callouts are left untouched.
89
+ * A text or leaf directive exactly as the author wrote it. The slice by
90
+ * offsets is the exact text — `[label]` and `{attrs}` included — and is
91
+ * trusted only when it opens with the directive's own marker and name;
92
+ * content an `<include>` spliced in carries no offsets into this page, so it
93
+ * is rebuilt from the node instead.
49
94
  */
50
- export const directiveToCalloutPlugin = () => ({
51
- containerDirective(node: DirectiveNode, ctx: MdastVisitorContext) {
52
- const type = calloutTypeFor(node.name);
53
- if (type === null) {
54
- return;
95
+ export const directiveSource = (
96
+ node: DirectiveNode,
97
+ marker: string,
98
+ source: string
99
+ ): string => {
100
+ const opening = `${marker}${node.name}`;
101
+ const slice = spannedSource(node, source);
102
+ if (slice?.startsWith(opening)) {
103
+ return slice;
104
+ }
105
+ return `${opening}${labelSource(node.children ?? [])}${attributeSource(node)}`;
106
+ };
107
+
108
+ /**
109
+ * The node a text or leaf directive renders as: its literal source, as text
110
+ * in place of a text directive and as a paragraph in place of a leaf one.
111
+ */
112
+ const literalDirective = (
113
+ node: DirectiveNode,
114
+ marker: string,
115
+ source: string
116
+ ): MdastNode => {
117
+ const text = { type: "text", value: directiveSource(node, marker, source) };
118
+ return marker === ":" ? text : { children: [text], type: "paragraph" };
119
+ };
120
+
121
+ const FENCE = /^:{3,}/u;
122
+
123
+ // The line that closes a container: a colon fence, after whatever quote or
124
+ // list indentation the container sits in.
125
+ const CLOSING_FENCE = /\n[\t >]*(?<fence>:{3,})\s*$/u;
126
+
127
+ /** A paragraph holding one line of literal text. */
128
+ const literalLine = (value: string): MdastNode => ({
129
+ children: [{ type: "text", value }],
130
+ type: "paragraph",
131
+ });
132
+
133
+ /**
134
+ * The fence lines of a container directive as the author wrote them — the
135
+ * opening one with its `[label]` and `{attrs}` — or, without offsets into
136
+ * this page (an `<include>`), rebuilt from the node. A container left
137
+ * unclosed runs to the end of its parent and has no closing line.
138
+ */
139
+ const containerFences = (
140
+ node: DirectiveNode,
141
+ label: MdastNode | undefined,
142
+ source: string
143
+ ): string[] => {
144
+ const slice = spannedSource(node, source) ?? "";
145
+ const fence = FENCE.exec(slice)?.[0];
146
+ if (fence === undefined || !slice.startsWith(node.name, fence.length)) {
147
+ const labelText = labelSource(label ? [label] : []);
148
+ return [`:::${node.name}${labelText}${attributeSource(node)}`, ":::"];
149
+ }
150
+ const [opening = ""] = slice.split("\n", 1);
151
+ const closing = CLOSING_FENCE.exec(slice.slice(opening.length))?.groups
152
+ ?.fence;
153
+ // A shorter fence can't close the container; it is the body's last line.
154
+ return closing !== undefined && closing.length >= fence.length
155
+ ? [opening.trimEnd(), closing]
156
+ : [opening.trimEnd()];
157
+ };
158
+
159
+ /**
160
+ * What a container directive renders as. A callout name becomes a
161
+ * `<Callout>`: the title comes from a `[label]` or a `{title="…"}` attribute,
162
+ * and the body becomes the callout content. Blume has no other container, and
163
+ * dropping one would drop its body with it, so any other name — `:::details`,
164
+ * a `:::warnig` typo — renders its body between its fence lines, as written.
165
+ * The directives inside render here too, since Satteri reaches them only
166
+ * after this replacement has taken their original.
167
+ */
168
+ const renderContainer = (
169
+ node: DirectiveNode,
170
+ source: string
171
+ ): MdastNode | MdastNode[] => {
172
+ const children = (node.children ?? []).flatMap((child) =>
173
+ // oxlint-disable-next-line no-use-before-define -- mutual recursion: a container's body holds directives, containers included
174
+ literalize(child, source)
175
+ );
176
+
177
+ // A leading `:::name[Label]` parses to a paragraph flagged `directiveLabel`.
178
+ // SAFETY: Satteri stamps `directiveLabel` on that paragraph's `data`; any
179
+ // other node reads undefined and fails the check.
180
+ const labelIndex = children.findIndex(
181
+ (child) =>
182
+ child.type === "paragraph" &&
183
+ (child.data as { directiveLabel?: boolean } | undefined)?.directiveLabel
184
+ );
185
+ const [label] = labelIndex === -1 ? [] : children.splice(labelIndex, 1);
186
+
187
+ const type = calloutTypeFor(node.name);
188
+ if (type === null) {
189
+ const [opening = "", closing] = containerFences(node, label, source);
190
+ return [
191
+ literalLine(opening),
192
+ ...children,
193
+ ...(closing === undefined ? [] : [literalLine(closing)]),
194
+ ];
195
+ }
196
+
197
+ // Flatten the label's phrasing children so `:::note[Read **this**]` yields
198
+ // `Read this`; image alt is excluded (an image is not label text), matching
199
+ // the historical child-values-only behavior.
200
+ const title =
201
+ node.attributes?.title ??
202
+ (label ? mdastToString(label, { includeImageAlt: false }) : undefined);
203
+ const attributes = [jsxAttribute("type", type)];
204
+ if (title) {
205
+ attributes.push(jsxAttribute("title", title));
206
+ }
207
+ return jsxFlowElement("Callout", attributes, children);
208
+ };
209
+
210
+ /**
211
+ * `node` with every directive in it rendered: text and leaf directives as
212
+ * their literal source, containers as {@link renderContainer}.
213
+ */
214
+ const literalize = (node: MdastNode, source: string): MdastNode[] => {
215
+ if (node.type === "containerDirective") {
216
+ // SAFETY: a `containerDirective` carries a `name` plus optional
217
+ // attributes, children, and position — the `DirectiveNode` shape.
218
+ return [renderContainer(node as DirectiveNode, source)].flat();
219
+ }
220
+ const marker = LITERAL_MARKERS.get(node.type);
221
+ if (marker) {
222
+ // SAFETY: only Satteri's `textDirective`/`leafDirective` nodes have a
223
+ // marker, and they carry a `name` plus optional attributes, children,
224
+ // and position — the `DirectiveNode` shape.
225
+ return [literalDirective(node as DirectiveNode, marker, source)];
226
+ }
227
+ // SAFETY: a parent's `children` is always a node list; leaves carry none.
228
+ const children = node.children as MdastNode[] | null | undefined;
229
+ if (!children) {
230
+ return [node];
231
+ }
232
+ return [
233
+ {
234
+ ...node,
235
+ children: children.flatMap((child) => literalize(child, source)),
236
+ },
237
+ ];
238
+ };
239
+
240
+ /** Whether a container directive (already rendered, body and all) holds `node`. */
241
+ const insideContainer = (node: MdastNode, ctx: DirectiveVisitorContext) => {
242
+ for (
243
+ let parent = ctx.parent?.(node);
244
+ parent !== undefined;
245
+ parent = ctx.parent?.(parent)
246
+ ) {
247
+ if (parent.type === "containerDirective") {
248
+ return true;
55
249
  }
250
+ }
251
+ return false;
252
+ };
56
253
 
57
- const children = [...(node.children ?? [])];
58
- let title = node.attributes?.title ?? undefined;
59
-
60
- // A leading `:::name[Label]` parses to a paragraph flagged `directiveLabel`.
61
- // SAFETY: Satteri stamps `directiveLabel` on that paragraph's `data`; any
62
- // other node reads undefined and fails the check.
63
- const labelIndex = children.findIndex(
64
- (child) =>
65
- child.type === "paragraph" &&
66
- (child.data as { directiveLabel?: boolean } | undefined)?.directiveLabel
67
- );
68
- if (labelIndex !== -1) {
69
- const [label] = children.splice(labelIndex, 1);
70
- if (label) {
71
- // Flatten the label's phrasing children so `:::note[Read **this**]`
72
- // yields `Read this`; image alt is excluded (an image is not label
73
- // text), matching the historical child-values-only behavior.
74
- title ??= mdastToString(label, { includeImageAlt: false }) || undefined;
75
- }
254
+ /** The text and leaf visitor: render the directive as its literal source. */
255
+ const renderLiteral =
256
+ (marker: string) => (node: DirectiveNode, ctx: DirectiveVisitorContext) => {
257
+ // A container's body was already rendered into its replacement; a
258
+ // transform queued on the replaced original would be dropped with a
259
+ // warning.
260
+ if (insideContainer(node, ctx)) {
261
+ return;
76
262
  }
263
+ ctx.replaceNode(node, literalDirective(node, marker, ctx.source ?? ""));
264
+ };
77
265
 
78
- const attributes = [jsxAttribute("type", type)];
79
- if (title) {
80
- attributes.push(jsxAttribute("title", title));
266
+ /**
267
+ * Satteri MDAST plugin for directives. Container directives (`:::note`,
268
+ * `:::warning`, `:::tip`, …) map onto Blume's `<Callout>` component, and any
269
+ * other container renders its body between its literal fence lines (see
270
+ * {@link renderContainer}).
271
+ *
272
+ * Blume handles no text (`:name`) or leaf (`::name`) directives, and prose is
273
+ * full of text that parses as one — `16:9`, `10:30am`, `og:image`,
274
+ * `pets:read` — so both render as the literal source the author wrote instead
275
+ * of vanishing.
276
+ */
277
+ export const directiveToCalloutPlugin = () => ({
278
+ containerDirective(node: DirectiveNode, ctx: DirectiveVisitorContext) {
279
+ // A container inside another was rendered with its enclosing one.
280
+ if (insideContainer(node, ctx)) {
281
+ return;
81
282
  }
82
- ctx.replaceNode(node, jsxFlowElement("Callout", attributes, children));
283
+ ctx.replaceNode(node, renderContainer(node, ctx.source ?? ""));
83
284
  },
285
+ leafDirective: renderLiteral("::"),
84
286
  name: "blume-directive-callout",
287
+ // Positions are opt-in since satteri 0.10; the literal fallback slices
288
+ // directives out of the source by offset.
289
+ options: { position: true },
290
+ textDirective: renderLiteral(":"),
85
291
  });
@@ -19,3 +19,20 @@ export const MDX_FEATURES = {
19
19
  directive: true,
20
20
  math: { singleDollarTextMath: false },
21
21
  } satisfies Features;
22
+
23
+ /**
24
+ * The feature sets for a page body, once its front matter is off. Astro reads
25
+ * a page's front matter itself and hands the renderer the body alone, and the
26
+ * search extractor strips it first too — so front matter parsing is off here:
27
+ * left on, a body that opens with a `---` rule read everything up to the next
28
+ * `---` line as front matter and dropped it from the page.
29
+ */
30
+ export const MARKDOWN_BODY_FEATURES = {
31
+ ...MARKDOWN_FEATURES,
32
+ frontmatter: false,
33
+ } satisfies Features;
34
+
35
+ export const MDX_BODY_FEATURES = {
36
+ ...MDX_FEATURES,
37
+ frontmatter: false,
38
+ } satisfies Features;
@@ -13,7 +13,7 @@ import { baseLinksPlugin } from "./base-links.ts";
13
13
  import { codeTitleTransformer } from "./code-title.ts";
14
14
  import { directiveToCalloutPlugin } from "./directives.ts";
15
15
  import { externalLinksPlugin } from "./external-links.ts";
16
- import { MARKDOWN_FEATURES, MDX_FEATURES } from "./features.ts";
16
+ import { MARKDOWN_BODY_FEATURES, MDX_BODY_FEATURES } from "./features.ts";
17
17
  import { headingAnchorPlugin } from "./heading-anchors.ts";
18
18
  import { includePlugin } from "./include.ts";
19
19
  import { inlineCodeHighlightPlugin } from "./inline-code.ts";
@@ -286,17 +286,18 @@ export interface BlumeMarkdownOptions {
286
286
  contentRoot?: string;
287
287
  /**
288
288
  * The `blume:data` snapshot file an ejected app aliases. Relative page links
289
- * resolve through the snapshot the CLI publishes; with no CLI in the
290
- * process, they read this file instead.
289
+ * (and the base-path rewrite of dotted page routes) resolve through the
290
+ * snapshot the CLI publishes; with no CLI in the process, they read this
291
+ * file instead.
291
292
  */
292
293
  dataFile?: string;
293
294
  }
294
295
 
295
296
  /**
296
297
  * MDAST plugins that apply to both `.md` and `.mdx`: relative page links
297
- * rewritten to the root-relative route they mean, then the base-path link
298
- * rewrite (added only when a `basePath` or `deployBase` is configured), which
299
- * layers `deployment.base` over the rewritten routes.
298
+ * rewritten to the served URL of the route they mean, then the base-path link
299
+ * rewrite for root-relative links (added only when a `basePath` or
300
+ * `deployBase` is configured).
300
301
  */
301
302
  const blumeSharedMdastPlugins = (
302
303
  options: BlumeMarkdownOptions
@@ -305,12 +306,15 @@ const blumeSharedMdastPlugins = (
305
306
  relativeLinksPlugin({
306
307
  contentRoot: options.contentRoot,
307
308
  dataFile: options.dataFile,
309
+ deployBase: options.deployBase,
308
310
  })
309
311
  ),
310
312
  ...(options.basePath || options.deployBase
311
313
  ? [
312
314
  asMdastPlugin(
313
- baseLinksPlugin(options.deployBase ?? "", options.basePath ?? "")
315
+ baseLinksPlugin(options.deployBase ?? "", options.basePath ?? "", {
316
+ dataFile: options.dataFile,
317
+ })
314
318
  ),
315
319
  ]
316
320
  : []),
@@ -327,7 +331,7 @@ const blumeIncludePlugin = (options: BlumeMarkdownOptions): MdastPlugin =>
327
331
  /** Sätteri processor for plain `.md`, with Blume's curated feature set. */
328
332
  export const blumeMarkdownProcessor = (options: BlumeMarkdownOptions = {}) =>
329
333
  satteri({
330
- features: { ...MARKDOWN_FEATURES },
334
+ features: { ...MARKDOWN_BODY_FEATURES },
331
335
  hastPlugins: blumeHastPlugins(options),
332
336
  mdastPlugins: [
333
337
  blumeIncludePlugin(options),
@@ -356,7 +360,7 @@ export type BlumeMdxOptions = BlumeMarkdownOptions;
356
360
  */
357
361
  export const blumeMdxProcessor = (options: BlumeMdxOptions = {}) =>
358
362
  satteri({
359
- features: { ...MDX_FEATURES },
363
+ features: { ...MDX_BODY_FEATURES },
360
364
  hastPlugins: blumeHastPlugins(options),
361
365
  mdastPlugins: [
362
366
  blumeIncludePlugin(options),
@@ -19,9 +19,12 @@ export type MdastValue =
19
19
  | MdastValue[]
20
20
  | { [key: string]: MdastValue };
21
21
 
22
- /** The visitor context Blume's plugins use to mutate the tree. */
22
+ /**
23
+ * The visitor context Blume's plugins use to mutate the tree. A list of
24
+ * nodes takes the replaced node's place in order.
25
+ */
23
26
  export interface MdastVisitorContext {
24
- replaceNode: (node: MdastNode, replacement: MdastNode) => void;
27
+ replaceNode: (node: MdastNode, replacement: MdastNode | MdastNode[]) => void;
25
28
  }
26
29
 
27
30
  /** Any MDAST node, keyed loosely since we build a small subset by hand. */
@@ -1,12 +1,12 @@
1
- import { readFileSync, statSync } from "node:fs";
2
1
  import { fileURLToPath } from "node:url";
3
2
 
4
3
  import { dirname, normalize, relative, resolve } from "pathe";
5
4
 
6
- import { readRuntimeModule } from "../astro/runtime-modules.ts";
5
+ import { mountBasePath } from "../core/base-path.ts";
7
6
  import { isIndexFileName, resolveRelativeHref } from "../core/links.ts";
8
7
  import type { RelativeLinkBase } from "../core/links.ts";
9
8
  import type { MdastNode, MdastValue } from "./mdast.ts";
9
+ import { routeSnapshotReader } from "./route-snapshot.ts";
10
10
 
11
11
  interface UrlNode extends MdastNode {
12
12
  url?: string | null;
@@ -131,6 +131,8 @@ export interface RelativeLinksPluginOptions {
131
131
  * to publish the snapshot, the plugin reads the file eject writes instead.
132
132
  */
133
133
  dataFile?: string;
134
+ /** Astro's `deployment.base` subdirectory (`""` or `/seg`). */
135
+ deployBase?: string;
134
136
  }
135
137
 
136
138
  /**
@@ -146,9 +148,11 @@ export interface RelativeLinksPluginOptions {
146
148
  * entries (remote sources) by the longest trailing path that names one. A file
147
149
  * the snapshot doesn't know keeps its links as written.
148
150
  *
149
- * Runs before the base-links plugin, whose `basePath` layering is idempotent,
150
- * so a rewritten route (which already carries `basePath`) only gains the
151
- * `deployment.base` prefix.
151
+ * A rewritten route already carries `basePath`, and the `deployment.base`
152
+ * prefix goes on here, unconditionally: the route is one Blume serves, so
153
+ * `guides/setup.md` under base `/guides` is linked at `/guides/guides/setup`.
154
+ * The base-links plugin that runs next is idempotent per layer, so it leaves
155
+ * the based route alone.
152
156
  */
153
157
  export const relativeLinksPlugin = (
154
158
  options: RelativeLinksPluginOptions = {}
@@ -156,34 +160,16 @@ export const relativeLinksPlugin = (
156
160
  const contentRoot = options.contentRoot
157
161
  ? resolve(options.contentRoot)
158
162
  : undefined;
159
- const dataFile = options.dataFile ? resolve(options.dataFile) : undefined;
163
+ const deployBase = options.deployBase ?? "";
164
+ const readSnapshot = routeSnapshotReader(options.dataFile);
160
165
 
161
166
  // Parsed once per published snapshot: the CLI republishes on regeneration,
162
167
  // and an unchanged snapshot is the same string, so every page compiled
163
168
  // between regenerations reuses one index.
164
169
  let cached: { index: RouteIndex; text: string } | undefined;
165
- let fileStamp: number | undefined;
166
- let fileText: string | undefined;
167
-
168
- /** The ejected snapshot file's text, re-read only when it changes. */
169
- const readDataFile = (path: string): string | undefined => {
170
- let stamp: number;
171
- try {
172
- stamp = statSync(path).mtimeMs;
173
- } catch {
174
- return undefined;
175
- }
176
- if (stamp !== fileStamp) {
177
- fileStamp = stamp;
178
- fileText = readFileSync(path, "utf-8");
179
- }
180
- return fileText;
181
- };
182
170
 
183
171
  const routeIndex = (): RouteIndex | undefined => {
184
- const text =
185
- readRuntimeModule("blume:data") ??
186
- (dataFile ? readDataFile(dataFile) : undefined);
172
+ const text = readSnapshot();
187
173
  if (text === undefined) {
188
174
  return undefined;
189
175
  }
@@ -265,7 +251,9 @@ export const relativeLinksPlugin = (
265
251
  const next = resolveRelativeHref(url, page, page.routeOf, (route) =>
266
252
  page.routes.has(route)
267
253
  );
268
- return next === url ? undefined : next;
254
+ return next === undefined || next === url
255
+ ? undefined
256
+ : mountBasePath(deployBase, next);
269
257
  };
270
258
 
271
259
  const rewrite = (node: UrlNode, ctx: RelativeLinksContext): void => {
@@ -0,0 +1,37 @@
1
+ import { readFileSync, statSync } from "node:fs";
2
+
3
+ import { resolve } from "pathe";
4
+
5
+ import { readRuntimeModule } from "../astro/runtime-modules.ts";
6
+
7
+ /**
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.
12
+ */
13
+ export const routeSnapshotReader = (
14
+ dataFile?: string
15
+ ): (() => string | undefined) => {
16
+ const file = dataFile ? resolve(dataFile) : undefined;
17
+ let fileStamp: number | undefined;
18
+ let fileText: string | undefined;
19
+
20
+ /** The ejected snapshot file's text, re-read only when it changes. */
21
+ const readDataFile = (path: string): string | undefined => {
22
+ let stamp: number;
23
+ try {
24
+ stamp = statSync(path).mtimeMs;
25
+ } catch {
26
+ return undefined;
27
+ }
28
+ if (stamp !== fileStamp) {
29
+ fileStamp = stamp;
30
+ fileText = readFileSync(path, "utf-8");
31
+ }
32
+ return fileText;
33
+ };
34
+
35
+ return () =>
36
+ readRuntimeModule("blume:data") ?? (file ? readDataFile(file) : undefined);
37
+ };