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,14 +1,81 @@
1
+ import { existsSync } from "node:fs";
2
+ import { readFile, writeFile } from "node:fs/promises";
3
+
4
+ import { join } from "pathe";
5
+
6
+ import { buildHomeLinkHeader } from "../../ai/link-headers.ts";
7
+ import { markdownRoutePaths } from "../../ai/markdown.ts";
8
+ import type { BlumeProject } from "../../core/project-graph.ts";
1
9
  import { NETLIFY_ADAPTER_PACKAGE } from "../adapters/netlify.ts";
10
+ import { buildNetlifyConfigHeaders } from "../headers.ts";
11
+ import type { NetlifyConfigHeaders } from "../headers.ts";
2
12
  import { buildNetlifyRedirects } from "../redirects.ts";
3
13
  import { distDir, toSiteUrl } from "./paths.ts";
4
- import type { DeployPlatform, RedirectFile } from "./types.ts";
14
+ import type { BuildLog, DeployPlatform, RedirectFile } from "./types.ts";
5
15
 
6
- /** `_redirects`, the format Netlify and Cloudflare Pages/Workers read. */
16
+ /**
17
+ * `_redirects`, the format Netlify and Cloudflare Pages/Workers read, in the
18
+ * form both accept: a static build for Cloudflare, or for no named host.
19
+ */
7
20
  export const REDIRECTS_FILE: RedirectFile = {
8
21
  build: buildNetlifyRedirects,
9
22
  name: "_redirects",
10
23
  };
11
24
 
25
+ /**
26
+ * `_redirects` for a `netlify()` static build, every rule forced (`301!`).
27
+ * Astro writes a redirect page at each `from` (`dist/old/index.html`), and
28
+ * Netlify serves a file that exists ahead of an unforced rule, so without the
29
+ * flag that page would answer with a 200 instead of the redirect.
30
+ */
31
+ export const NETLIFY_REDIRECTS_FILE: RedirectFile = {
32
+ build: (redirects) => buildNetlifyRedirects(redirects, true),
33
+ name: "_redirects",
34
+ };
35
+
36
+ /** The Frameworks API config, once the build surfaced it to the project root. */
37
+ export const NETLIFY_CONFIG_FILE = join(".netlify", "v1", "config.json");
38
+
39
+ /**
40
+ * Carry the static header rules into a server build. A static `netlify()`
41
+ * build gets them from `_headers` (see `deploy/artifacts.ts`), but a server
42
+ * build's header rules go in the Frameworks API config the adapter writes
43
+ * (`.netlify/v1/config.json`, beside its own `Cache-Control` rule for
44
+ * `/_astro/*`), which follows the same rules as `netlify.toml`'s
45
+ * `[[headers]]`: the charset on the raw Markdown and text files, the homepage
46
+ * `Link` header, the discovery files' media types and CORS header, and the
47
+ * sandbox on downloaded SVGs, which Netlify serves from the publish directory
48
+ * as static files, so the prerendered endpoint's own headers never ship.
49
+ */
50
+ export const emitNetlifyHeaders = async (
51
+ project: BlumeProject,
52
+ log: BuildLog
53
+ ): Promise<void> => {
54
+ const { config, context } = project;
55
+ const configPath = join(context.root, NETLIFY_CONFIG_FILE);
56
+ if (!existsSync(configPath)) {
57
+ log.warn(
58
+ `Could not find Netlify's deploy config at ${configPath}, so the site is served without its header rules (the homepage Link header, the discovery files' media types and CORS header, and the sandbox on downloaded SVGs).`
59
+ );
60
+ return;
61
+ }
62
+ // Only `headers` is read; every other key rides through as parsed.
63
+ const frameworks: { headers?: NetlifyConfigHeaders[] } = JSON.parse(
64
+ await readFile(configPath, "utf-8")
65
+ );
66
+ const ours = buildNetlifyConfigHeaders(
67
+ config,
68
+ buildHomeLinkHeader(config, markdownRoutePaths(project))
69
+ );
70
+ // A second pass over the same build replaces its own entries.
71
+ const paths = new Set(ours.map((entry) => entry.for));
72
+ frameworks.headers = [
73
+ ...(frameworks.headers ?? []).filter((entry) => !paths.has(entry.for)),
74
+ ...ours,
75
+ ];
76
+ await writeFile(configPath, JSON.stringify(frameworks), "utf-8");
77
+ };
78
+
12
79
  /**
13
80
  * Netlify. A server build runs on Netlify Functions from the Frameworks API
14
81
  * tree the adapter writes to `.netlify/v1`, relative to the Astro root —
@@ -34,7 +101,14 @@ export const netlifyPlatform: DeployPlatform = {
34
101
  toSiteUrl(env.DEPLOY_PRIME_URL) ??
35
102
  toSiteUrl(env.DEPLOY_URL),
36
103
  },
37
- finalizeBuild: null,
104
+ finalizeBuild: async ({ isolated, log, project }) => {
105
+ // The Frameworks API config is a deploy artifact; an isolated verify
106
+ // never surfaces it.
107
+ if (!isolated) {
108
+ await emitNetlifyHeaders(project, log);
109
+ }
110
+ return true;
111
+ },
38
112
  hiddenRuntime: {
39
113
  ignoreDir: ".netlify/",
40
114
  showProjectRoot: false,
@@ -42,11 +116,15 @@ export const netlifyPlatform: DeployPlatform = {
42
116
  },
43
117
  kind: "netlify",
44
118
  negotiatesMarkdown: false,
119
+ // @astrojs/netlify declares no preview entrypoint.
120
+ previewDeploy: "netlify deploy",
45
121
  // Netlify reads `_headers` from the publish directory of a static deploy.
46
122
  // A server build's static assets ride the Frameworks API tree instead,
47
- // where the file has never been applied.
123
+ // where the file has never been applied, so `finalizeBuild` writes the
124
+ // same rules into that tree's config.
48
125
  readsHeaderFiles: { server: false, static: true },
49
- redirectFiles: [REDIRECTS_FILE],
126
+ redirectFiles: [NETLIFY_REDIRECTS_FILE],
127
+ serverClientUnderBase: false,
50
128
  serverOutputDir: distDir,
51
129
  serverStaticDir: distDir,
52
130
  };
@@ -6,11 +6,12 @@ import type { DeployPlatform } from "./types.ts";
6
6
  /**
7
7
  * A self-hosted Node server. The standalone server in `dist/server` serves
8
8
  * `dist/client` through its own static handler, which has no `_headers` or
9
- * `_redirects` support — redirects are answered at request time from the
10
- * Astro config, and a header file written there would be inert. The headers
11
- * the discovery files need come from a wrapper around the server entry
12
- * instead (`deploy/node-headers.ts`). No platform env to detect: the site URL
13
- * has to be configured.
9
+ * `_redirects` support, so a file written there would be inert. A wrapper
10
+ * around the server entry (`deploy/node-headers.ts`) sets the headers the
11
+ * discovery files and downloaded SVGs need instead, and answers the
12
+ * configured redirects with their exact status, which Astro's own handler
13
+ * would default to 301 or 308. No platform env to detect: the site URL has to
14
+ * be configured.
14
15
  */
15
16
  export const nodePlatform: DeployPlatform = {
16
17
  astro: {
@@ -33,8 +34,10 @@ export const nodePlatform: DeployPlatform = {
33
34
  },
34
35
  kind: "node",
35
36
  negotiatesMarkdown: false,
37
+ previewDeploy: null,
36
38
  readsHeaderFiles: { server: false, static: false },
37
39
  redirectFiles: [],
40
+ serverClientUnderBase: false,
38
41
  serverOutputDir: distDir,
39
42
  serverStaticDir: clientDir,
40
43
  };
@@ -22,8 +22,10 @@ export const staticPlatform: DeployPlatform = {
22
22
  },
23
23
  kind: "static",
24
24
  negotiatesMarkdown: false,
25
+ previewDeploy: null,
25
26
  readsHeaderFiles: { server: false, static: true },
26
27
  redirectFiles: [REDIRECTS_FILE, VERCEL_JSON_FILE],
28
+ serverClientUnderBase: false,
27
29
  serverOutputDir: distDir,
28
30
  serverStaticDir: distDir,
29
31
  };
@@ -98,6 +98,13 @@ export interface DeployPlatform {
98
98
  kind: DeployAdapterKind;
99
99
  /** Whether a server build honors `Accept: text/markdown` at the content URLs. */
100
100
  negotiatesMarkdown: boolean;
101
+ /**
102
+ * The host CLI command that deploys a preview, for a platform whose
103
+ * `@astrojs/*` adapter has no local preview server: Astro's `preview`
104
+ * throws for its server build, so `blume preview` points here instead. Null
105
+ * when the adapter can preview a server build locally, or there is none.
106
+ */
107
+ previewDeploy: string | null;
101
108
  /**
102
109
  * Whether the platform applies a `_headers` file to the static assets it
103
110
  * serves, per output mode.
@@ -105,6 +112,14 @@ export interface DeployPlatform {
105
112
  readsHeaderFiles: Record<DeployOutput, boolean>;
106
113
  /** The redirect files a static build writes, beside the manifest. */
107
114
  redirectFiles: RedirectFile[];
115
+ /**
116
+ * Whether the adapter moves a server build's client output under
117
+ * `deployment.base` (`dist/client/<base>/`, the directory Astro then hands
118
+ * `astro:build:done`) while the platform keeps serving `serverStaticDir`
119
+ * as the assets root. The files the platform reads there, like `_headers`,
120
+ * belong above the directory Astro reports.
121
+ */
122
+ serverClientUnderBase: boolean;
108
123
  /** Where a server build's deploy bundle lands. */
109
124
  serverOutputDir: (context: ProjectContext) => string;
110
125
  /** The directory a server build serves as static files. */
@@ -124,7 +124,10 @@ export const emitVercelNegotiation = async (
124
124
  json: existsSync(join(staticDir, "404.json")),
125
125
  markdown: existsSync(join(staticDir, "404.md")),
126
126
  },
127
- crossOriginDiscoveryPaths(config)
127
+ crossOriginDiscoveryPaths(config),
128
+ // Downloaded content assets are prerendered static files; only a build
129
+ // that has them needs the SVG sandbox route.
130
+ existsSync(join(staticDir, "blume-assets"))
128
131
  );
129
132
  if (injected === null) {
130
133
  log.warn(
@@ -144,8 +147,9 @@ export const emitVercelNegotiation = async (
144
147
  * `@vercel/nft` dependency trace is rooted there too, and tracing from the
145
148
  * hidden runtime silently drops the function's chunks and `node_modules`.
146
149
  * Static assets are served from the tree's `static/` half, so the deploy
147
- * artifacts are written there; headers arrive through the routing config
148
- * rather than a `_headers` file, which Vercel never reads.
150
+ * artifacts are written there; headers (the discovery files', the sandbox on
151
+ * downloaded SVGs) arrive through the routing config rather than a
152
+ * `_headers` file, which Vercel never reads.
149
153
  */
150
154
  export const vercelPlatform: DeployPlatform = {
151
155
  astro: {
@@ -186,8 +190,11 @@ export const vercelPlatform: DeployPlatform = {
186
190
  },
187
191
  kind: "vercel",
188
192
  negotiatesMarkdown: true,
193
+ // @astrojs/vercel declares no preview entrypoint.
194
+ previewDeploy: "vercel deploy",
189
195
  readsHeaderFiles: { server: false, static: false },
190
196
  redirectFiles: [VERCEL_JSON_FILE],
197
+ serverClientUnderBase: false,
191
198
  serverOutputDir: buildOutputDir,
192
199
  serverStaticDir: (context) => join(buildOutputDir(context), "static"),
193
200
  };
@@ -1,9 +1,14 @@
1
1
  import {
2
2
  normalizeBasePath,
3
+ withAuthoredBasePath,
3
4
  withBasePath,
4
5
  withComposedBasePath,
5
6
  } from "../core/base-path.ts";
7
+ import { routeSetFor, servesRoute } from "../core/locale-links.ts";
8
+ import type { RouteSet } from "../core/locale-links.ts";
9
+ import type { BlumeProject } from "../core/project-graph.ts";
6
10
  import type { ResolvedConfig } from "../core/schema.ts";
11
+ import { escapeVercelSource } from "./headers.ts";
7
12
  import type { VercelHeader } from "./headers.ts";
8
13
 
9
14
  /**
@@ -16,6 +21,23 @@ import type { VercelHeader } from "./headers.ts";
16
21
 
17
22
  type Redirect = ResolvedConfig["redirects"][number];
18
23
 
24
+ /**
25
+ * Base a redirect destination authored as if mounted at root by what it
26
+ * names (see `withAuthoredBasePath`): a page gains the full
27
+ * `{deployment.base}{basePath}` stack, a public file (`/files/spec.pdf`, with
28
+ * no page in `routes`) only the deployment base — `public/` is served there
29
+ * whatever `basePath` is. External URLs pass through.
30
+ */
31
+ const baseRedirectTarget = (
32
+ to: string,
33
+ basePath: string,
34
+ base: string,
35
+ routes: RouteSet
36
+ ): string =>
37
+ withAuthoredBasePath(base, basePath, to, (route) =>
38
+ servesRoute(routes, route)
39
+ );
40
+
19
41
  /**
20
42
  * Base a redirect for Astro's `redirects` config, where the two sides are not
21
43
  * symmetric:
@@ -23,19 +45,23 @@ type Redirect = ResolvedConfig["redirects"][number];
23
45
  * - `from` gains only `basePath`. Astro builds the match pattern with
24
46
  * `deployment.base` already applied (`getPattern(segments, config.base)`), so
25
47
  * adding it here would serve the redirect at `{base}{base}/from`.
26
- * - `to` gains the full `{deployment.base}{basePath}` stack. Astro resolves a
27
- * destination that matches a known route by regenerating it from that route's
28
- * segments, which carry no base — and passes an unmatched destination through
29
- * verbatim. Neither path prepends `base`, so a root-relative `to` escapes the
30
- * base entirely (withastro/astro#7774, still the behavior in Astro 7).
48
+ * - `to` gains the full `{deployment.base}{basePath}` stack, or the deployment
49
+ * base alone for a public file (see {@link baseRedirectTarget}). Astro
50
+ * resolves a destination that matches a known route by regenerating it from
51
+ * that route's segments, which carry no base — and passes an unmatched
52
+ * destination through verbatim. Neither path prepends `base`, so a
53
+ * root-relative `to` escapes the base entirely (withastro/astro#7774, still
54
+ * the behavior in Astro 7).
31
55
  *
32
- * Both sides are authored as if mounted at root; external `to` URLs pass
33
- * through. Idempotent, so a hand-written base isn't doubled.
56
+ * Both sides are authored as if mounted at root; `routes` (the served page
57
+ * routes, carrying `basePath`) tell a dotted page route like `/releases/v1.2`
58
+ * from a file. Idempotent, so a hand-written base isn't doubled.
34
59
  */
35
60
  export const applyBaseToAstroRedirects = (
36
61
  redirects: Redirect[],
37
62
  basePath: string,
38
- deployBase: string
63
+ deployBase: string,
64
+ routes: RouteSet
39
65
  ): Redirect[] => {
40
66
  // `deployment.base` arrives as the user wrote it (Astro accepts `/base/`,
41
67
  // even `base`); normalizing here keeps the composed paths well-formed.
@@ -44,27 +70,29 @@ export const applyBaseToAstroRedirects = (
44
70
  ? redirects.map((redirect) => ({
45
71
  ...redirect,
46
72
  from: withBasePath(basePath, redirect.from),
47
- to: withComposedBasePath(base, basePath, redirect.to),
73
+ to: baseRedirectTarget(redirect.to, basePath, base, routes),
48
74
  }))
49
75
  : redirects;
50
76
  };
51
77
 
52
78
  /**
53
79
  * Base a redirect for the host platform files below. Unlike Astro's config,
54
- * these are matched against the real served URL, so both sides carry the full
55
- * `{deployment.base}{basePath}` stack.
80
+ * these are matched against the real served URL, so `from` carries the full
81
+ * `{deployment.base}{basePath}` stack, and so does `to` unless it names a
82
+ * public file (see {@link baseRedirectTarget}).
56
83
  */
57
84
  export const applyBaseToPlatformRedirects = (
58
85
  redirects: Redirect[],
59
86
  basePath: string,
60
- deployBase: string
87
+ deployBase: string,
88
+ routes: RouteSet
61
89
  ): Redirect[] => {
62
90
  const base = normalizeBasePath(deployBase);
63
91
  return basePath || base
64
92
  ? redirects.map((redirect) => ({
65
93
  ...redirect,
66
94
  from: withComposedBasePath(base, basePath, redirect.from),
67
- to: withComposedBasePath(base, basePath, redirect.to),
95
+ to: baseRedirectTarget(redirect.to, basePath, base, routes),
68
96
  }))
69
97
  : redirects;
70
98
  };
@@ -75,17 +103,34 @@ export const applyBaseToPlatformRedirects = (
75
103
  * share: the emitted redirect files and the Cloudflare worker-first redirect
76
104
  * exemptions both compare these paths against real served URLs.
77
105
  */
78
- export const platformRedirects = (config: ResolvedConfig): Redirect[] =>
79
- applyBaseToPlatformRedirects(
106
+ export const platformRedirects = (project: {
107
+ config: ResolvedConfig;
108
+ manifest: Pick<BlumeProject["manifest"], "routes">;
109
+ }): Redirect[] => {
110
+ const { config } = project;
111
+ return applyBaseToPlatformRedirects(
80
112
  config.redirects,
81
113
  config.basePath,
82
- config.deployment.options.base ?? ""
114
+ config.deployment.options.base ?? "",
115
+ routeSetFor(project.manifest.routes)
83
116
  );
117
+ };
84
118
 
85
- /** `_redirects` text (Netlify + Cloudflare Pages): `from to status` per line. */
86
- export const buildNetlifyRedirects = (redirects: Redirect[]): string =>
119
+ /**
120
+ * `_redirects` text (Netlify + Cloudflare Pages): `from to status` per line.
121
+ * `force` appends Netlify's `!` to each status (`301!`), so the rule wins over
122
+ * the redirect page Astro writes at `from`; Cloudflare, which always applies
123
+ * its rules first, rejects a line carrying it.
124
+ */
125
+ export const buildNetlifyRedirects = (
126
+ redirects: Redirect[],
127
+ force = false
128
+ ): string =>
87
129
  `${redirects
88
- .map((redirect) => `${redirect.from} ${redirect.to} ${redirect.status}`)
130
+ .map(
131
+ (redirect) =>
132
+ `${redirect.from} ${redirect.to} ${redirect.status}${force ? "!" : ""}`
133
+ )
89
134
  .join("\n")}\n`;
90
135
 
91
136
  /** The `vercel.json` Blume writes for a static deploy of `dist/`. */
@@ -99,7 +144,9 @@ interface VercelConfig {
99
144
  * header rules are given (see `buildVercelHeaders`). Uses `statusCode` (Vercel's
100
145
  * alternative to the boolean `permanent`) so the configured code ships exactly:
101
146
  * `permanent` would silently coerce a 301 to 308 and a 302 to 307, diverging
102
- * from the `_redirects` file, which preserves exact codes.
147
+ * from the `_redirects` file, which preserves exact codes. A `source` is a
148
+ * `path-to-regexp` pattern, so each exact `from` path is escaped: unescaped,
149
+ * `/c++-guide` fails the whole config and `/faq(old)` never matches.
103
150
  */
104
151
  export const buildVercelConfig = (
105
152
  redirects: Redirect[],
@@ -108,7 +155,7 @@ export const buildVercelConfig = (
108
155
  const config: VercelConfig = {
109
156
  redirects: redirects.map((redirect) => ({
110
157
  destination: redirect.to,
111
- source: redirect.from,
158
+ source: escapeVercelSource(redirect.from),
112
159
  statusCode: redirect.status,
113
160
  })),
114
161
  };
@@ -1,4 +1,4 @@
1
- import { normalizeBasePath, withBasePath } from "../core/base-path.ts";
1
+ import { mountBasePath, normalizeBasePath } from "../core/base-path.ts";
2
2
  import type { BlumeProject } from "../core/project-graph.ts";
3
3
  import type { ContentSignalPolicy, ContentSignals } from "../core/schema.ts";
4
4
  import { absoluteUrl } from "../core/site-url.ts";
@@ -49,7 +49,7 @@ export const buildRobots = (project: BlumeProject): string | null => {
49
49
 
50
50
  const { site } = config.deployment.options;
51
51
  if (site && config.seo.sitemap) {
52
- const sitemapPath = withBasePath(
52
+ const sitemapPath = mountBasePath(
53
53
  normalizeBasePath(config.deployment.options.base),
54
54
  "/sitemap.xml"
55
55
  );
package/src/deploy/rss.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  // `&#39;` reference rather than `&apos;`, which XML accepts equally.
3
3
  import { escape as escapeXml } from "html-escaper";
4
4
 
5
- import { normalizeBasePath, withBasePath } from "../core/base-path.ts";
5
+ import { mountBasePath, normalizeBasePath } from "../core/base-path.ts";
6
6
  import type { BlumeProject } from "../core/project-graph.ts";
7
7
  import { siteRoot } from "../core/site-url.ts";
8
8
  import type { PageRecord } from "../core/types.ts";
@@ -57,7 +57,8 @@ export const buildRssFeeds = (project: BlumeProject): RssFeed[] => {
57
57
  return [];
58
58
  }
59
59
  const base = siteRoot(site);
60
- // Routes carry `basePath`; a `deployment.base` subdirectory is layered on top.
60
+ // Routes carry `basePath`; a `deployment.base` subdirectory is mounted on top
61
+ // of every one, even a route whose first segment matches it (see sitemap.ts).
61
62
  // The feed's own `link`/self URL points at the docs root under that base, while
62
63
  // `path` stays base-less (it's also the on-disk output location).
63
64
  const deployBase = normalizeBasePath(config.deployment.options.base);
@@ -80,7 +81,7 @@ export const buildRssFeeds = (project: BlumeProject): RssFeed[] => {
80
81
  description: page.description,
81
82
  // Encode like the sitemap does: a route with spaces or non-ASCII
82
83
  // must still yield a valid <link>/<guid> URL after XML decoding.
83
- link: encodeURI(`${base}${withBasePath(deployBase, page.route)}`),
84
+ link: encodeURI(`${base}${mountBasePath(deployBase, page.route)}`),
84
85
  title: page.title,
85
86
  }))
86
87
  .toSorted((a, b) => (b.date?.getTime() ?? 0) - (a.date?.getTime() ?? 0))
@@ -6,7 +6,7 @@ import {
6
6
  discoverPagesSync,
7
7
  hasGeneratedChangelog,
8
8
  } from "../astro/pages.ts";
9
- import { normalizeBasePath, withBasePath } from "../core/base-path.ts";
9
+ import { mountBasePath, normalizeBasePath } from "../core/base-path.ts";
10
10
  import type { BlumeProject } from "../core/project-graph.ts";
11
11
  import { siteRoot } from "../core/site-url.ts";
12
12
 
@@ -78,7 +78,9 @@ export const buildSitemapFiles = (
78
78
  }
79
79
 
80
80
  const base = siteRoot(site);
81
- // Routes carry `basePath`; a `deployment.base` subdirectory is layered on top.
81
+ // Routes carry `basePath`; a `deployment.base` subdirectory is mounted on
82
+ // top of every one, even a route whose first segment matches it: under base
83
+ // `/guides`, `guides/setup.md` is served at `/guides/guides/setup`.
82
84
  const deployBase = normalizeBasePath(project.config.deployment.options.base);
83
85
 
84
86
  // Archived-version pages leave the sitemap when the version is noindexed,
@@ -135,7 +137,7 @@ export const buildSitemapFiles = (
135
137
  ) {
136
138
  continue;
137
139
  }
138
- pushUrl(withBasePath(deployBase, page.route), page.lastModified);
140
+ pushUrl(mountBasePath(deployBase, page.route), page.lastModified);
139
141
  }
140
142
  // Custom `.astro` pages and the generated changelog index mount outside
141
143
  // `basePath` (they're injected at their pattern — see `blumeIntegration`), so
@@ -150,7 +152,7 @@ export const buildSitemapFiles = (
150
152
  extraRoutes.push("/changelog");
151
153
  }
152
154
  for (const route of extraRoutes) {
153
- pushUrl(withBasePath(deployBase, route));
155
+ pushUrl(mountBasePath(deployBase, route));
154
156
  }
155
157
  urls.sort();
156
158
 
@@ -169,7 +171,7 @@ export const buildSitemapFiles = (
169
171
  // Chunks sit next to sitemap.xml, so their URLs layer the same deployment
170
172
  // base robots.txt uses for the index.
171
173
  const loc = escapeXml(
172
- encodeURI(`${base}${withBasePath(deployBase, `/${name}`)}`)
174
+ encodeURI(`${base}${mountBasePath(deployBase, `/${name}`)}`)
173
175
  );
174
176
  references.push(` <sitemap><loc>${loc}</loc></sitemap>`);
175
177
  }
@@ -16,6 +16,8 @@
16
16
  * prerendered problem-details 404.
17
17
  */
18
18
 
19
+ import { SVG_ASSET_HEADERS } from "./headers.ts";
20
+
19
21
  /**
20
22
  * Regex for the `accept` header condition. Written to hold under both matching
21
23
  * semantics a router may apply — full-string and substring — by anchoring the
@@ -248,6 +250,30 @@ const corsRoute = (path: string): VercelRoute => ({
248
250
  src: `^${routePattern(path)}$`,
249
251
  });
250
252
 
253
+ /**
254
+ * The `src` of the route stamping the content-source SVG headers: every `.svg`
255
+ * under `/blume-assets/`. The adapter copies the static files into the Build
256
+ * Output tree without the deployment base, like every path matched here.
257
+ */
258
+ const SVG_ASSET_SRC = String.raw`^/blume-assets/.+\.svg$`;
259
+
260
+ /**
261
+ * A main-phase `continue` route stamping the sandbox headers
262
+ * (`deploy/headers.ts`) on a downloaded SVG, which Vercel serves from the
263
+ * static layer — where the prerendered asset endpoint's own headers never
264
+ * reach.
265
+ */
266
+ const svgAssetRoute = (): VercelRoute => ({
267
+ continue: true,
268
+ headers: Object.fromEntries(
269
+ Object.entries(SVG_ASSET_HEADERS).map(([name, value]) => [
270
+ name.toLowerCase(),
271
+ value,
272
+ ])
273
+ ),
274
+ src: SVG_ASSET_SRC,
275
+ });
276
+
251
277
  /** Whether a route is a `corsRoute` — the same three-field shape test as the others. */
252
278
  const isCorsRoute = (route: VercelRoute): boolean =>
253
279
  route.continue === true &&
@@ -279,7 +305,8 @@ const isNegotiationRoute = (route: VercelRoute): boolean =>
279
305
  isString(route.headers?.link) &&
280
306
  route.src === HOME_SRC &&
281
307
  Object.keys(route).length === 3) ||
282
- isCorsRoute(route);
308
+ isCorsRoute(route) ||
309
+ (route.continue === true && route.src === SVG_ASSET_SRC);
283
310
 
284
311
  /**
285
312
  * Splice the negotiation routes into a Build Output `config.json`, plus — when
@@ -297,7 +324,8 @@ const isNegotiationRoute = (route: VercelRoute): boolean =>
297
324
  * `404.md`, `notFound.json` for `404.json`), its routes go into the miss
298
325
  * phase right before the adapter's `/404.html` fallback — and nowhere when
299
326
  * that fallback is absent, since a `dest` with no file behind it would serve
300
- * nothing. Returns the updated JSON
327
+ * nothing. With `svgAssets`, a main-phase route also sandboxes the SVGs a
328
+ * content source downloaded (see {@link svgAssetRoute}). Returns the updated JSON
301
329
  * text (tab-indented, like the adapter's own output), or `null` when there is
302
330
  * nowhere safe to splice: an unparsable config, no `routes` array, or no
303
331
  * `handle: "filesystem"` marker to anchor the splice.
@@ -309,7 +337,8 @@ export const injectNegotiationRoutes = (
309
337
  contentTypeOverrides?: Record<string, string>,
310
338
  homeTokens?: number,
311
339
  notFound: NotFoundVariants = {},
312
- corsPaths: readonly string[] = []
340
+ corsPaths: readonly string[] = [],
341
+ svgAssets = false
313
342
  ): string | null => {
314
343
  const overrideEntries = Object.entries(contentTypeOverrides ?? {});
315
344
  let config: {
@@ -348,6 +377,9 @@ export const injectNegotiationRoutes = (
348
377
  });
349
378
  }
350
379
  headerRoutes.push(...corsPaths.map(corsRoute));
380
+ if (svgAssets) {
381
+ headerRoutes.push(svgAssetRoute());
382
+ }
351
383
  // Headers first: `continue` routes accumulate, so a request the rewrite
352
384
  // route then terminates (Markdown negotiation on the homepage) still carries
353
385
  // the Link header.
@@ -124,6 +124,14 @@ export const runAgentHeadless = (
124
124
  }
125
125
  });
126
126
 
127
+ // An agent that exits before reading the whole prompt (it rejected a flag,
128
+ // say) closes the pipe mid-write, and the resulting EPIPE would otherwise
129
+ // be an uncaught exception that aborts the run. The exit itself is the
130
+ // outcome: `close` resolves with the agent's code and stderr, which
131
+ // `readAgentOutput` reports.
132
+ child.stdin?.on("error", () => {
133
+ // Deliberately ignored — see above.
134
+ });
127
135
  child.stdin?.end(options.prompt);
128
136
  });
129
137
 
@@ -208,7 +216,7 @@ const claudeArgs = (context: InvocationContext): string[] => {
208
216
 
209
217
  /**
210
218
  * Codex config overrides that leave an eval run nothing but its prompt and the
211
- * docs MCP tools. `--sandbox read-only` blocks writes and the network but still
219
+ * docs MCP tools (and a `blume translate` run nothing but its prompt). `--sandbox read-only` blocks writes and the network but still
212
220
  * lets the shell tool read any file the user can, and the docs a reader
213
221
  * searches — or the answer a judge grades — can come from a remote source, so
214
222
  * an instruction planted there could otherwise pull a local secret into the
@@ -216,7 +224,7 @@ const claudeArgs = (context: InvocationContext): string[] => {
216
224
  * local-image tool off, and any subprocess started with no inherited
217
225
  * environment, there is nothing on the machine for a run to read.
218
226
  */
219
- const CODEX_LOCKDOWN = [
227
+ export const CODEX_LOCKDOWN = [
220
228
  "-c",
221
229
  "features.shell_tool=false",
222
230
  "-c",
package/src/eval/run.ts CHANGED
@@ -181,6 +181,31 @@ const runQuestion = async (
181
181
  };
182
182
  };
183
183
 
184
+ /** The findings a missed question produces: failed, or its agent run errored. */
185
+ const MISS_CODES: ReadonlySet<string> = new Set([
186
+ "BLUME_EVAL_QUESTION_ERROR",
187
+ "BLUME_EVAL_QUESTION_FAILED",
188
+ ]);
189
+
190
+ /**
191
+ * The fraction of run (non-skipped) questions that pass the CI gate. A miss on
192
+ * a `severity: warning` question is reported as a warning and never counts
193
+ * against the gate — each miss's finding carries its question's severity, so
194
+ * the gate reads the same findings the JSON summary counts, and the exit code
195
+ * can't disagree with the summary's error total.
196
+ */
197
+ export const passFraction = (result: EvalResult): number => {
198
+ const ran = result.results.length - result.counts.skip;
199
+ if (ran === 0) {
200
+ return 1;
201
+ }
202
+ const misses = result.diagnostics.filter(
203
+ (diagnostic) =>
204
+ MISS_CODES.has(diagnostic.code) && diagnostic.severity === "error"
205
+ ).length;
206
+ return (ran - misses) / ran;
207
+ };
208
+
184
209
  /**
185
210
  * Run every question through the reader/judge pair, sequentially: each
186
211
  * question is already two agent sessions, and a serial run keeps progress