blume 2.1.0 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (397) hide show
  1. package/CHANGELOG.md +94 -0
  2. package/dist/cli/{chunk-5cg2rzgf.js → chunk-073rdvgb.js} +19 -48
  3. package/dist/cli/{chunk-5cg2rzgf.js.map → chunk-073rdvgb.js.map} +4 -5
  4. package/dist/cli/{chunk-ga0pf4aj.js → chunk-0n9xkp3d.js} +12 -3
  5. package/dist/cli/{chunk-ga0pf4aj.js.map → chunk-0n9xkp3d.js.map} +3 -3
  6. package/dist/cli/chunk-150jk7km.js +52 -0
  7. package/dist/cli/chunk-150jk7km.js.map +10 -0
  8. package/dist/cli/{chunk-rekqz4qg.js → chunk-313vnn40.js} +16 -15
  9. package/dist/cli/chunk-313vnn40.js.map +10 -0
  10. package/dist/cli/{chunk-gp84za0h.js → chunk-3v3ffbrj.js} +2 -2
  11. package/dist/cli/{chunk-9xr4dgee.js → chunk-3x6x3pph.js} +29 -16
  12. package/dist/cli/chunk-3x6x3pph.js.map +10 -0
  13. package/dist/cli/{chunk-0gz36akr.js → chunk-42t4jxdx.js} +21 -5
  14. package/dist/cli/chunk-42t4jxdx.js.map +12 -0
  15. package/dist/cli/{chunk-ta27feev.js → chunk-54ztw1nj.js} +21 -2
  16. package/dist/cli/chunk-54ztw1nj.js.map +10 -0
  17. package/dist/cli/{chunk-jnwj5kjj.js → chunk-5vjtgs6e.js} +20 -7
  18. package/dist/cli/{chunk-jnwj5kjj.js.map → chunk-5vjtgs6e.js.map} +3 -3
  19. package/dist/cli/chunk-66w9j7p6.js +322 -0
  20. package/dist/cli/chunk-66w9j7p6.js.map +14 -0
  21. package/dist/cli/{chunk-nd1821sk.js → chunk-7ppf1rva.js} +32 -18
  22. package/dist/cli/chunk-7ppf1rva.js.map +10 -0
  23. package/dist/cli/{chunk-p804f3pe.js → chunk-8y2kmjv8.js} +6 -6
  24. package/dist/cli/{chunk-2f7n4qzt.js → chunk-c1y538kd.js} +1 -1
  25. package/dist/cli/{chunk-1n228xqb.js → chunk-c887fj0s.js} +2 -2
  26. package/dist/cli/{chunk-y5h4r2y0.js → chunk-cfyg47zg.js} +15 -5
  27. package/dist/cli/chunk-cfyg47zg.js.map +10 -0
  28. package/dist/cli/{chunk-cmtcnh9f.js → chunk-dzwehpy7.js} +2 -2
  29. package/dist/cli/{chunk-h2a48yhd.js → chunk-e6atmn4h.js} +119 -15
  30. package/dist/cli/{chunk-h2a48yhd.js.map → chunk-e6atmn4h.js.map} +10 -10
  31. package/dist/cli/{chunk-5ffywsmb.js → chunk-e8xw9qkh.js} +8 -6
  32. package/dist/cli/{chunk-5ffywsmb.js.map → chunk-e8xw9qkh.js.map} +3 -3
  33. package/dist/cli/{chunk-ntv4qrt7.js → chunk-e9q7a2ak.js} +14 -14
  34. package/dist/cli/{chunk-xkz5xbtv.js → chunk-ehweg4fr.js} +2366 -1114
  35. package/dist/cli/chunk-ehweg4fr.js.map +218 -0
  36. package/dist/cli/{chunk-zf748avv.js → chunk-ep6nd0rn.js} +176 -281
  37. package/dist/cli/{chunk-zf748avv.js.map → chunk-ep6nd0rn.js.map} +11 -12
  38. package/dist/cli/{chunk-yxmk4pec.js → chunk-fdrkhpjy.js} +16 -15
  39. package/dist/cli/{chunk-yxmk4pec.js.map → chunk-fdrkhpjy.js.map} +3 -3
  40. package/dist/cli/{chunk-5wj2qwn2.js → chunk-ft9vfys6.js} +4 -4
  41. package/dist/cli/{chunk-4h8z9vv5.js → chunk-gb420vvz.js} +89 -46
  42. package/dist/cli/chunk-gb420vvz.js.map +17 -0
  43. package/dist/cli/{chunk-713mngdq.js → chunk-hebn0tpx.js} +76 -10
  44. package/dist/cli/chunk-hebn0tpx.js.map +14 -0
  45. package/dist/cli/{chunk-2n4ps1b3.js → chunk-htrwgabr.js} +40 -22
  46. package/dist/cli/chunk-htrwgabr.js.map +15 -0
  47. package/dist/cli/{chunk-zhvd6adm.js → chunk-hx3xqnk9.js} +13 -13
  48. package/dist/cli/chunk-j134vrp2.js +65 -0
  49. package/dist/cli/chunk-j134vrp2.js.map +10 -0
  50. package/dist/cli/{chunk-y4ykbark.js → chunk-kma9f6sf.js} +2 -2
  51. package/dist/cli/{chunk-py62fewh.js → chunk-mrcccgnh.js} +3 -3
  52. package/dist/cli/{chunk-2f3ndkpw.js → chunk-n0v7m8ry.js} +3 -3
  53. package/dist/cli/{chunk-43zcf4gz.js → chunk-ncj7w596.js} +522 -353
  54. package/dist/cli/chunk-ncj7w596.js.map +35 -0
  55. package/dist/cli/{chunk-9geymqns.js → chunk-nq1d20w2.js} +14 -14
  56. package/dist/cli/{chunk-g2z9x01v.js → chunk-nrns10mj.js} +3 -3
  57. package/dist/cli/{chunk-e7vvk5ya.js → chunk-sds923fa.js} +3 -3
  58. package/dist/cli/{chunk-cy5bm0j0.js → chunk-t2m5j4pr.js} +2 -2
  59. package/dist/cli/{chunk-cy5bm0j0.js.map → chunk-t2m5j4pr.js.map} +1 -1
  60. package/dist/cli/{chunk-yj17vs39.js → chunk-tdqhyxfz.js} +5 -5
  61. package/dist/cli/{chunk-q3wxxq39.js → chunk-tphac74f.js} +5 -5
  62. package/dist/cli/{chunk-6bm0xpgd.js → chunk-w1r4vase.js} +2 -2
  63. package/dist/cli/index.js +24 -19
  64. package/dist/cli/index.js.map +3 -3
  65. package/dist/types/ai/ask-context.d.ts +6 -2
  66. package/dist/types/ai/component-markdown.d.ts +2 -0
  67. package/dist/types/ai/openapi-components.d.ts +4 -1
  68. package/dist/types/astro/integration.d.ts +5 -4
  69. package/dist/types/astro/markdown-negotiation.d.ts +3 -0
  70. package/dist/types/astro/pages.d.ts +10 -0
  71. package/dist/types/cli/init/scaffold.d.ts +1 -1
  72. package/dist/types/components/content/api-field.d.ts +4 -1
  73. package/dist/types/components/content/api-page.d.ts +6 -1
  74. package/dist/types/components/openapi/code-samples.d.ts +29 -0
  75. package/dist/types/components/openapi/helpers.d.ts +22 -1
  76. package/dist/types/components/openapi/security.d.ts +10 -0
  77. package/dist/types/components/openapi/snippets.d.ts +60 -0
  78. package/dist/types/core/changelog-index.d.ts +8 -0
  79. package/dist/types/core/config-input.d.ts +30 -3
  80. package/dist/types/core/data.d.ts +4 -0
  81. package/dist/types/core/i18n-ui.d.ts +2 -0
  82. package/dist/types/core/links.d.ts +8 -0
  83. package/dist/types/core/probe.d.ts +2 -0
  84. package/dist/types/core/project-graph.d.ts +6 -0
  85. package/dist/types/core/redirect-patterns.d.ts +11 -3
  86. package/dist/types/core/schema.d.ts +119 -0
  87. package/dist/types/core/sources/cache.d.ts +3 -0
  88. package/dist/types/core/sources/contentful-rich-text.d.ts +11 -2
  89. package/dist/types/core/sources/contentful.d.ts +17 -1
  90. package/dist/types/core/sources/lexical.d.ts +5 -0
  91. package/dist/types/core/sources/normalize.d.ts +20 -1
  92. package/dist/types/core/sources/remote.d.ts +37 -10
  93. package/dist/types/core/sources/sanity.d.ts +1 -1
  94. package/dist/types/core/types.d.ts +17 -0
  95. package/dist/types/deploy/adapter-output.d.ts +2 -1
  96. package/dist/types/deploy/adapters/cloudflare.d.ts +7 -5
  97. package/dist/types/deploy/adapters/node.d.ts +58 -23
  98. package/dist/types/deploy/adapters/registry.d.ts +10 -0
  99. package/dist/types/deploy/adapters/types.d.ts +3 -2
  100. package/dist/types/deploy/platforms/cloudflare.d.ts +10 -7
  101. package/dist/types/deploy/platforms/node.d.ts +3 -1
  102. package/dist/types/deploy/platforms/types.d.ts +18 -7
  103. package/dist/types/deploy/platforms/vercel.d.ts +20 -1
  104. package/dist/types/deploy/redirects.d.ts +17 -1
  105. package/dist/types/deploy/sitemap.d.ts +2 -1
  106. package/dist/types/deploy/vercel-negotiation.d.ts +42 -21
  107. package/dist/types/openapi/checks.d.ts +27 -0
  108. package/dist/types/openapi/graphql.d.ts +6 -0
  109. package/dist/types/openapi/model.d.ts +15 -4
  110. package/dist/types/openapi/parse.d.ts +3 -0
  111. package/dist/types/openapi/references.d.ts +16 -0
  112. package/dist/types/ratelimit/index.d.ts +1 -1
  113. package/dist/types/ratelimit/schema.d.ts +8 -0
  114. package/dist/types/ratelimit/upstash.d.ts +39 -14
  115. package/dist/types/reference/graphql.d.ts +76 -1
  116. package/dist/types/reference/index.d.ts +1 -1
  117. package/dist/types/reference/openapi.d.ts +2 -2
  118. package/dist/types/reference/options.d.ts +37 -1
  119. package/dist/types/reference/schema.d.ts +84 -0
  120. package/dist/types/search/adapters/registry.d.ts +3 -0
  121. package/dist/types/search/adapters/typesense.d.ts +14 -4
  122. package/dist/types/search/build.d.ts +3 -1
  123. package/dist/types/search/orama-index.d.ts +9 -9
  124. package/dist/types/search/sync/algolia.d.ts +5 -2
  125. package/dist/types/search/sync/index.d.ts +3 -1
  126. package/dist/types/search/sync/typesense.d.ts +11 -7
  127. package/dist/types/sources/contentful.d.ts +16 -0
  128. package/dist/types/sources/github-releases.d.ts +8 -1
  129. package/dist/types/sources/payload.d.ts +2 -0
  130. package/dist/types/sources/registry.d.ts +54 -0
  131. package/dist/types/sources/shared.d.ts +3 -0
  132. package/dist/types/sources/strapi.d.ts +2 -0
  133. package/docs/01-quickstart.mdx +4 -2
  134. package/docs/02-deployment.mdx +31 -13
  135. package/docs/advanced/changelog.mdx +11 -0
  136. package/docs/advanced/skills.mdx +1 -1
  137. package/docs/cli/audit.mdx +30 -7
  138. package/docs/cli/doctor.mdx +5 -1
  139. package/docs/cli/evals.mdx +4 -4
  140. package/docs/cli/translate.mdx +1 -0
  141. package/docs/cli/validate.mdx +4 -3
  142. package/docs/configuration/analytics.mdx +1 -1
  143. package/docs/configuration/assistant.mdx +6 -2
  144. package/docs/configuration/customization.mdx +4 -2
  145. package/docs/configuration/export.mdx +5 -3
  146. package/docs/configuration/index.mdx +3 -2
  147. package/docs/configuration/rate-limiting.mdx +23 -2
  148. package/docs/configuration/search.mdx +37 -14
  149. package/docs/content/components.mdx +2 -2
  150. package/docs/content/frontmatter.mdx +5 -3
  151. package/docs/content/i18n.mdx +2 -2
  152. package/docs/content/includes.mdx +1 -1
  153. package/docs/content/navigation.mdx +4 -4
  154. package/docs/content/sources/contentful.mdx +7 -4
  155. package/docs/content/sources/custom.mdx +7 -3
  156. package/docs/content/sources/github-releases.mdx +10 -2
  157. package/docs/content/sources/index.mdx +3 -3
  158. package/docs/content/sources/notion.mdx +1 -1
  159. package/docs/content/sources/obsidian.mdx +1 -1
  160. package/docs/content/sources/payload.mdx +3 -2
  161. package/docs/content/sources/remote-mdx.mdx +4 -0
  162. package/docs/content/sources/sanity.mdx +2 -2
  163. package/docs/content/sources/strapi.mdx +3 -2
  164. package/docs/content/syntax.mdx +2 -2
  165. package/docs/discoverability/agent-discovery.mdx +1 -1
  166. package/docs/discoverability/index.mdx +1 -1
  167. package/docs/discoverability/json-api.mdx +2 -0
  168. package/docs/discoverability/llms-txt.mdx +1 -1
  169. package/docs/discoverability/markdown.mdx +3 -1
  170. package/docs/discoverability/mcp.mdx +4 -0
  171. package/docs/discoverability/sitemap-and-robots.mdx +1 -1
  172. package/docs/references/api-pages.mdx +1 -1
  173. package/docs/references/asyncapi.mdx +3 -1
  174. package/docs/references/graphql.mdx +13 -1
  175. package/docs/references/openapi.mdx +50 -8
  176. package/package.json +9 -9
  177. package/skills/blume-migrate/SKILL.md +1 -1
  178. package/skills/blume-migrate/references/docusaurus.md +4 -4
  179. package/skills/blume-migrate/references/fumadocs.md +17 -13
  180. package/skills/blume-migrate/references/nextra.md +8 -5
  181. package/skills/blume-migrate/references/starlight.md +9 -7
  182. package/src/ai/ask-context.ts +33 -3
  183. package/src/ai/ask-limits.ts +13 -0
  184. package/src/ai/changelog-markdown.ts +11 -6
  185. package/src/ai/component-markdown.ts +1 -1
  186. package/src/ai/cors.ts +12 -3
  187. package/src/ai/mcp/query.ts +14 -6
  188. package/src/ai/openapi-components.ts +28 -4
  189. package/src/ai/relative-links.ts +4 -38
  190. package/src/astro/generate.ts +78 -48
  191. package/src/astro/integration.ts +16 -13
  192. package/src/astro/markdown-negotiation.ts +3 -0
  193. package/src/astro/module-types.ts +3 -1
  194. package/src/astro/pages.ts +23 -0
  195. package/src/astro/templates.ts +125 -80
  196. package/src/audit/catalog.ts +19 -3
  197. package/src/audit/checks/links.ts +14 -1
  198. package/src/audit/checks/network.ts +110 -6
  199. package/src/audit/checks/redirects.ts +6 -4
  200. package/src/audit/checks/robots.ts +58 -2
  201. package/src/audit/run.ts +3 -0
  202. package/src/audit/types.ts +2 -0
  203. package/src/audit/url.ts +14 -5
  204. package/src/blume-modules.d.ts +5 -1
  205. package/src/cli/args.ts +51 -0
  206. package/src/cli/build-failure.ts +23 -0
  207. package/src/cli/commands/audit.ts +10 -1
  208. package/src/cli/commands/dev.ts +18 -2
  209. package/src/cli/commands/doctor.ts +45 -12
  210. package/src/cli/commands/eval.ts +36 -4
  211. package/src/cli/commands/init.ts +6 -4
  212. package/src/cli/commands/validate.ts +10 -1
  213. package/src/cli/init/scaffold.ts +3 -3
  214. package/src/cli/prepare.ts +6 -3
  215. package/src/cli/required-secrets.ts +1 -1
  216. package/src/components/content/ApiPlayground.astro +6 -2
  217. package/src/components/content/api-field.ts +4 -1
  218. package/src/components/content/api-page.ts +31 -7
  219. package/src/components/content/mermaid-element.ts +15 -1
  220. package/src/components/islands/hooks.ts +27 -1
  221. package/src/components/layout/PageActions.astro +139 -24
  222. package/src/components/layout/RootLayout.astro +10 -2
  223. package/src/components/layout/Search.astro +17 -3
  224. package/src/components/layout/TableOfContents.astro +24 -17
  225. package/src/components/layout/head-scripts.ts +6 -1
  226. package/src/components/layout/search/algolia.ts +10 -1
  227. package/src/components/layout/search/analytics.ts +42 -14
  228. package/src/components/layout/search/endpoint.ts +48 -5
  229. package/src/components/layout/search/pagefind.ts +114 -5
  230. package/src/components/layout/search/types.ts +12 -0
  231. package/src/components/layout/search/typesense.ts +18 -6
  232. package/src/components/openapi/AsyncApiOperation.astro +14 -7
  233. package/src/components/openapi/Authorization.astro +10 -0
  234. package/src/components/openapi/GraphqlOperation.astro +5 -0
  235. package/src/components/openapi/Operation.astro +24 -1
  236. package/src/components/openapi/ParametersTable.astro +8 -4
  237. package/src/components/openapi/Playground.astro +7 -1
  238. package/src/components/openapi/RequestBody.astro +19 -2
  239. package/src/components/openapi/Responses.astro +1 -0
  240. package/src/components/openapi/SchemaTable.astro +15 -3
  241. package/src/components/openapi/async-snippets.ts +61 -5
  242. package/src/components/openapi/async.ts +123 -29
  243. package/src/components/openapi/graphql-helpers.ts +35 -12
  244. package/src/components/openapi/helpers.ts +36 -2
  245. package/src/components/openapi/message-model.ts +54 -11
  246. package/src/components/openapi/message.ts +14 -4
  247. package/src/components/openapi/operation-model.ts +16 -7
  248. package/src/components/openapi/playground-client.ts +24 -5
  249. package/src/components/openapi/playground-schema.ts +1 -1
  250. package/src/components/openapi/schema-tree.ts +44 -14
  251. package/src/components/openapi/security.ts +10 -0
  252. package/src/components/openapi/snippets.ts +5 -3
  253. package/src/components/openapi/validate-json.ts +4 -3
  254. package/src/core/changelog-index.ts +31 -1
  255. package/src/core/component-diagnostics.ts +53 -0
  256. package/src/core/component-overrides.ts +32 -0
  257. package/src/core/config-input.ts +31 -3
  258. package/src/core/data.ts +2 -0
  259. package/src/core/diagnostics.ts +5 -0
  260. package/src/core/i18n-ui.ts +2 -0
  261. package/src/core/includes.ts +1 -1
  262. package/src/core/links.ts +47 -17
  263. package/src/core/probe.ts +26 -6
  264. package/src/core/project-graph.ts +59 -18
  265. package/src/core/redirect-patterns.ts +36 -7
  266. package/src/core/schema.ts +21 -2
  267. package/src/core/sources/cache.ts +6 -0
  268. package/src/core/sources/contentful-rich-text.ts +29 -6
  269. package/src/core/sources/contentful.ts +135 -25
  270. package/src/core/sources/filesystem.ts +26 -4
  271. package/src/core/sources/github-releases.ts +12 -1
  272. package/src/core/sources/lexical.ts +15 -10
  273. package/src/core/sources/mdx-remote.ts +62 -10
  274. package/src/core/sources/normalize.ts +227 -41
  275. package/src/core/sources/notion.ts +7 -1
  276. package/src/core/sources/obsidian.ts +16 -3
  277. package/src/core/sources/payload.ts +38 -15
  278. package/src/core/sources/remote.ts +72 -15
  279. package/src/core/sources/resolve.ts +11 -1
  280. package/src/core/sources/sanity.ts +43 -25
  281. package/src/core/sources/strapi.ts +2 -2
  282. package/src/core/types.ts +18 -0
  283. package/src/core/ui-packs/ar.ts +1 -0
  284. package/src/core/ui-packs/bg.ts +1 -0
  285. package/src/core/ui-packs/bn.ts +1 -0
  286. package/src/core/ui-packs/ca.ts +1 -0
  287. package/src/core/ui-packs/cs.ts +1 -0
  288. package/src/core/ui-packs/da.ts +1 -0
  289. package/src/core/ui-packs/de.ts +1 -0
  290. package/src/core/ui-packs/el.ts +1 -0
  291. package/src/core/ui-packs/es.ts +1 -0
  292. package/src/core/ui-packs/fa.ts +1 -0
  293. package/src/core/ui-packs/fi.ts +1 -0
  294. package/src/core/ui-packs/fr.ts +1 -0
  295. package/src/core/ui-packs/he.ts +1 -0
  296. package/src/core/ui-packs/hi.ts +1 -0
  297. package/src/core/ui-packs/hr.ts +1 -0
  298. package/src/core/ui-packs/hu.ts +1 -0
  299. package/src/core/ui-packs/id.ts +1 -0
  300. package/src/core/ui-packs/it.ts +1 -0
  301. package/src/core/ui-packs/ja.ts +1 -0
  302. package/src/core/ui-packs/ko.ts +1 -0
  303. package/src/core/ui-packs/nl.ts +1 -0
  304. package/src/core/ui-packs/no.ts +1 -0
  305. package/src/core/ui-packs/pl.ts +1 -0
  306. package/src/core/ui-packs/pt-br.ts +1 -0
  307. package/src/core/ui-packs/pt.ts +1 -0
  308. package/src/core/ui-packs/ro.ts +1 -0
  309. package/src/core/ui-packs/ru.ts +1 -0
  310. package/src/core/ui-packs/sk.ts +1 -0
  311. package/src/core/ui-packs/sr.ts +1 -0
  312. package/src/core/ui-packs/sv.ts +1 -0
  313. package/src/core/ui-packs/th.ts +1 -0
  314. package/src/core/ui-packs/tr.ts +1 -0
  315. package/src/core/ui-packs/uk.ts +1 -0
  316. package/src/core/ui-packs/vi.ts +1 -0
  317. package/src/core/ui-packs/zh-tw.ts +1 -0
  318. package/src/core/ui-packs/zh.ts +1 -0
  319. package/src/deploy/adapter-output.ts +7 -2
  320. package/src/deploy/adapters/cloudflare.ts +7 -5
  321. package/src/deploy/adapters/node.ts +47 -9
  322. package/src/deploy/adapters/types.ts +7 -3
  323. package/src/deploy/artifacts.ts +2 -1
  324. package/src/deploy/platforms/cloudflare.ts +13 -9
  325. package/src/deploy/platforms/netlify.ts +2 -1
  326. package/src/deploy/platforms/node.ts +12 -2
  327. package/src/deploy/platforms/types.ts +21 -7
  328. package/src/deploy/platforms/vercel.ts +113 -16
  329. package/src/deploy/redirects.ts +66 -6
  330. package/src/deploy/rss.ts +3 -1
  331. package/src/deploy/sitemap.ts +39 -5
  332. package/src/deploy/vercel-negotiation.ts +290 -102
  333. package/src/eval/agents.ts +18 -1
  334. package/src/eval/prompts.ts +6 -3
  335. package/src/eval/report.ts +34 -13
  336. package/src/eval/run.ts +5 -0
  337. package/src/markdown/relative-links.ts +3 -2
  338. package/src/openapi/checks.ts +273 -0
  339. package/src/openapi/graphql-build.ts +21 -0
  340. package/src/openapi/graphql.ts +11 -0
  341. package/src/openapi/model.ts +15 -3
  342. package/src/openapi/parse.ts +127 -2
  343. package/src/openapi/references.ts +43 -3
  344. package/src/openapi/source.ts +39 -14
  345. package/src/ratelimit/index.ts +1 -1
  346. package/src/ratelimit/runtime.ts +37 -19
  347. package/src/ratelimit/upstash.ts +40 -10
  348. package/src/reference/graphql.ts +15 -1
  349. package/src/reference/index.ts +1 -0
  350. package/src/reference/openapi.ts +2 -2
  351. package/src/reference/options.ts +29 -1
  352. package/src/registry/eject.ts +3 -1
  353. package/src/search/adapters/locale-scope.ts +30 -0
  354. package/src/search/adapters/typesense.ts +14 -4
  355. package/src/search/build.ts +21 -3
  356. package/src/search/orama-index.ts +163 -31
  357. package/src/search/source-pages.ts +29 -0
  358. package/src/search/sync/algolia.ts +99 -9
  359. package/src/search/sync/index.ts +52 -13
  360. package/src/search/sync/typesense.ts +72 -27
  361. package/src/sources/contentful.ts +12 -0
  362. package/src/sources/github-releases.ts +7 -1
  363. package/src/sources/shared.ts +3 -0
  364. package/src/theme/entry.ts +29 -3
  365. package/src/translate/validate.ts +23 -1
  366. package/dist/cli/chunk-0gz36akr.js.map +0 -12
  367. package/dist/cli/chunk-2n4ps1b3.js.map +0 -15
  368. package/dist/cli/chunk-43zcf4gz.js.map +0 -36
  369. package/dist/cli/chunk-4ejxbzee.js +0 -39
  370. package/dist/cli/chunk-4ejxbzee.js.map +0 -10
  371. package/dist/cli/chunk-4h8z9vv5.js.map +0 -16
  372. package/dist/cli/chunk-713mngdq.js.map +0 -14
  373. package/dist/cli/chunk-9xr4dgee.js.map +0 -10
  374. package/dist/cli/chunk-amrb6hgh.js +0 -145
  375. package/dist/cli/chunk-amrb6hgh.js.map +0 -11
  376. package/dist/cli/chunk-nd1821sk.js.map +0 -10
  377. package/dist/cli/chunk-rekqz4qg.js.map +0 -10
  378. package/dist/cli/chunk-ta27feev.js.map +0 -10
  379. package/dist/cli/chunk-xkz5xbtv.js.map +0 -213
  380. package/dist/cli/chunk-y5h4r2y0.js.map +0 -10
  381. /package/dist/cli/{chunk-gp84za0h.js.map → chunk-3v3ffbrj.js.map} +0 -0
  382. /package/dist/cli/{chunk-p804f3pe.js.map → chunk-8y2kmjv8.js.map} +0 -0
  383. /package/dist/cli/{chunk-2f7n4qzt.js.map → chunk-c1y538kd.js.map} +0 -0
  384. /package/dist/cli/{chunk-1n228xqb.js.map → chunk-c887fj0s.js.map} +0 -0
  385. /package/dist/cli/{chunk-cmtcnh9f.js.map → chunk-dzwehpy7.js.map} +0 -0
  386. /package/dist/cli/{chunk-ntv4qrt7.js.map → chunk-e9q7a2ak.js.map} +0 -0
  387. /package/dist/cli/{chunk-5wj2qwn2.js.map → chunk-ft9vfys6.js.map} +0 -0
  388. /package/dist/cli/{chunk-zhvd6adm.js.map → chunk-hx3xqnk9.js.map} +0 -0
  389. /package/dist/cli/{chunk-y4ykbark.js.map → chunk-kma9f6sf.js.map} +0 -0
  390. /package/dist/cli/{chunk-py62fewh.js.map → chunk-mrcccgnh.js.map} +0 -0
  391. /package/dist/cli/{chunk-2f3ndkpw.js.map → chunk-n0v7m8ry.js.map} +0 -0
  392. /package/dist/cli/{chunk-9geymqns.js.map → chunk-nq1d20w2.js.map} +0 -0
  393. /package/dist/cli/{chunk-g2z9x01v.js.map → chunk-nrns10mj.js.map} +0 -0
  394. /package/dist/cli/{chunk-e7vvk5ya.js.map → chunk-sds923fa.js.map} +0 -0
  395. /package/dist/cli/{chunk-yj17vs39.js.map → chunk-tdqhyxfz.js.map} +0 -0
  396. /package/dist/cli/{chunk-q3wxxq39.js.map → chunk-tphac74f.js.map} +0 -0
  397. /package/dist/cli/{chunk-6bm0xpgd.js.map → chunk-w1r4vase.js.map} +0 -0
@@ -50,6 +50,7 @@ export const uk: UIStringsOverride = {
50
50
  banner: { dismiss: "Закрити оголошення" },
51
51
  changelog: {
52
52
  description: "Оновлення продукту та примітки до випусків.",
53
+ empty: "У журналі змін поки немає записів.",
53
54
  title: "Журнал змін",
54
55
  },
55
56
  consent: {
@@ -49,6 +49,7 @@ export const vi: UIStringsOverride = {
49
49
  banner: { dismiss: "Đóng thông báo" },
50
50
  changelog: {
51
51
  description: "Cập nhật sản phẩm và ghi chú phát hành.",
52
+ empty: "Chưa có mục nào trong nhật ký thay đổi.",
52
53
  title: "Nhật ký thay đổi",
53
54
  },
54
55
  consent: {
@@ -48,6 +48,7 @@ export const zhTW: UIStringsOverride = {
48
48
  banner: { dismiss: "關閉公告" },
49
49
  changelog: {
50
50
  description: "產品更新與版本說明。",
51
+ empty: "尚無更新日誌項目。",
51
52
  title: "更新日誌",
52
53
  },
53
54
  consent: {
@@ -48,6 +48,7 @@ export const zh: UIStringsOverride = {
48
48
  banner: { dismiss: "关闭公告" },
49
49
  changelog: {
50
50
  description: "产品更新与版本说明。",
51
+ empty: "暂无更新日志条目。",
51
52
  title: "更新日志",
52
53
  },
53
54
  consent: {
@@ -3,6 +3,7 @@ import { cp, mkdir, rm } from "node:fs/promises";
3
3
 
4
4
  import { dirname, join } from "pathe";
5
5
 
6
+ import { normalizeBasePath } from "../core/base-path.ts";
6
7
  import type { ResolvedConfig } from "../core/schema.ts";
7
8
  import type { ProjectContext } from "../core/types.ts";
8
9
  import type { ResolvedDeployment } from "./adapters/registry.ts";
@@ -30,7 +31,8 @@ export const deployOutputDir = (
30
31
  * served. A static build serves `dist/` wherever it lands; a server build
31
32
  * serves what its platform declares — the Build Output tree's `static/` half
32
33
  * on Vercel, Astro's `build.client` dir (`dist/client/`) on Node and
33
- * Cloudflare, `dist/` on Netlify.
34
+ * Cloudflare, `dist/` on Netlify. On Vercel and Cloudflare that's its
35
+ * `<base>/` directory under `deployment.base`, where the files land.
34
36
  */
35
37
  export const deployStaticDir = (
36
38
  config: ResolvedConfig,
@@ -38,7 +40,10 @@ export const deployStaticDir = (
38
40
  ): string => {
39
41
  const { deployment } = config;
40
42
  return deployment.options.output === "server"
41
- ? deployPlatform(deployment).serverStaticDir(context)
43
+ ? deployPlatform(deployment).serverStaticDir(
44
+ context,
45
+ normalizeBasePath(deployment.options.base)
46
+ )
42
47
  : distDir(context);
43
48
  };
44
49
 
@@ -23,11 +23,13 @@ export const cloudflareAdapterSchema = adapterDescriptorSchema(
23
23
  );
24
24
 
25
25
  /**
26
- * Cloudflare Workers and Pages. A server build emits a Worker into
27
- * `dist/server` (with a generated wrapper that answers `Accept:
28
- * text/markdown`) and serves `dist/client` through the ASSETS binding.
29
- * `@astrojs/cloudflare` is an optional peer: install it in the project
30
- * (`npm install @astrojs/cloudflare`).
26
+ * Cloudflare. A server build targets Workers only (`@astrojs/cloudflare`
27
+ * dropped Pages in v13): it emits a Worker into `dist/server` (with a
28
+ * generated wrapper that answers `Accept: text/markdown`) and serves
29
+ * `dist/client` through the ASSETS binding. A static build
30
+ * (`output: "static"`) never loads the adapter, and its `dist/` deploys to
31
+ * Pages or to Workers static assets. `@astrojs/cloudflare` is an optional
32
+ * peer: install it in the project (`npm install @astrojs/cloudflare`).
31
33
  */
32
34
  export const cloudflare = (
33
35
  options: CloudflareOptions = {}
@@ -1,3 +1,5 @@
1
+ import { z } from "zod";
2
+
1
3
  import { adapterDescriptorSchema } from "../../core/adapter.ts";
2
4
  import type { DeployAdapter, DeployOptions } from "./types.ts";
3
5
  import { deployOptionsSchema, serverRuntimeDeps } from "./types.ts";
@@ -6,18 +8,54 @@ import { deployOptionsSchema, serverRuntimeDeps } from "./types.ts";
6
8
  export const NODE_ADAPTER_PACKAGE = "@astrojs/node";
7
9
 
8
10
  /**
9
- * Options for {@link node}: `output`, `site`, and `base`, plus any option of
10
- * `@astrojs/node` forwarded verbatim (`mode`, `staticHeaders`, …). Blume
11
- * defaults `mode` to `"standalone"`; an option you pass wins. `host` and
12
- * `port` are forwarded too, but `@astrojs/node` replaces both with Astro's
13
- * own server settings, so the standalone server listens on `localhost:4321`
14
- * unless the `HOST` and `PORT` environment variables are set when it starts.
11
+ * A host the server trusts forwarded headers for, in Astro's
12
+ * `security.allowedDomains` shape: each field set must match.
15
13
  */
16
- export type NodeOptions = DeployOptions;
14
+ // oxlint-disable-next-line typescript/consistent-type-definitions -- interface loses the implicit index signature a deploy option's JsonValue needs
15
+ export type NodeAllowedDomain = {
16
+ /** The host, like `docs.example.com`; `*.example.com` matches subdomains. */
17
+ hostname?: string;
18
+ /** The port, as a string. */
19
+ port?: string;
20
+ /** The scheme, like `https`. */
21
+ protocol?: string;
22
+ };
17
23
 
18
- export type NodeAdapter = DeployAdapter<"node">;
24
+ /**
25
+ * Options for {@link node}: `output`, `site`, `base`, and `allowedDomains`,
26
+ * plus any option of `@astrojs/node` forwarded verbatim (`mode`,
27
+ * `staticHeaders`, …). Blume defaults `mode` to `"standalone"`; an option you
28
+ * pass wins. `host` and `port` are forwarded too, but `@astrojs/node`
29
+ * replaces both with Astro's own server settings, so the standalone server
30
+ * listens on `localhost:4321` unless the `HOST` and `PORT` environment
31
+ * variables are set when it starts.
32
+ */
33
+ export type NodeOptions = DeployOptions & {
34
+ /**
35
+ * The hosts a reverse proxy in front of the server answers for, set as
36
+ * Astro's `security.allowedDomains`. A request whose `Host` (or
37
+ * `X-Forwarded-Host`) matches one is trusted to name the reader in
38
+ * `X-Forwarded-For`, which the rate limit counts by; otherwise every
39
+ * reader counts as the proxy's address. Only list them when a proxy sets
40
+ * that header on every request, since a reader could otherwise send their
41
+ * own.
42
+ */
43
+ allowedDomains?: NodeAllowedDomain[];
44
+ };
19
45
 
20
- export const nodeOptionsSchema = deployOptionsSchema;
46
+ export type NodeAdapter = DeployAdapter<"node", NodeOptions>;
47
+
48
+ export const nodeOptionsSchema = deployOptionsSchema.extend({
49
+ allowedDomains: z
50
+ .array(
51
+ z.strictObject({
52
+ hostname: z.string().min(1).optional(),
53
+ port: z.string().min(1).optional(),
54
+ protocol: z.string().min(1).optional(),
55
+ })
56
+ )
57
+ .optional(),
58
+ });
21
59
 
22
60
  export const nodeAdapterSchema = adapterDescriptorSchema(
23
61
  "node",
@@ -84,13 +84,17 @@ const NAMED_OPTIONS = new Set(["base", "output", "site"]);
84
84
 
85
85
  /**
86
86
  * The options Blume does not name — what reaches the `@astrojs/*` adapter's
87
- * constructor verbatim.
87
+ * constructor verbatim. `configOptions` are the ones a host adapter names
88
+ * itself, for the Astro config rather than the constructor.
88
89
  */
89
90
  export const deployPassthrough = (
90
- options: DeployOptions
91
+ options: DeployOptions,
92
+ configOptions: readonly string[] = []
91
93
  ): Record<string, JsonValue> =>
92
94
  Object.fromEntries(
93
- Object.entries(options).filter(([key]) => !NAMED_OPTIONS.has(key))
95
+ Object.entries(options).filter(
96
+ ([key]) => !(NAMED_OPTIONS.has(key) || configOptions.includes(key))
97
+ )
94
98
  );
95
99
 
96
100
  /**
@@ -445,7 +445,8 @@ export const publishBuildArtifacts = async (
445
445
  await buildNarration(project, distDir, logger);
446
446
 
447
447
  // Upload the index to a hosted provider (Algolia, Orama Cloud, Typesense).
448
- // Skipped with a warning when its admin key isn't configured.
448
+ // Skipped with a warning when its admin key isn't configured; with the key
449
+ // set, a failed sync fails the build.
449
450
  await syncSearchProvider(project, {
450
451
  start: logger.info.bind(logger),
451
452
  success: logger.info.bind(logger),
@@ -305,17 +305,21 @@ export const bindCloudflareRateLimit = async (
305
305
  };
306
306
 
307
307
  /**
308
- * Cloudflare Workers and Pages. A server build emits the Worker into
309
- * `dist/server` and serves `dist/client` through the ASSETS binding, which
310
- * honors `_headers` from that directory exactly as Pages does — so the file
311
- * applies to both output modes here. Without a configured driver the adapter
312
- * force-enables KV-backed sessions and declares a `SESSION` binding that
313
- * `wrangler deploy` then demands a namespace for, even though Blume never
314
- * reads `Astro.session`; `session: false` opts the project out.
308
+ * Cloudflare. A server build targets Workers only (`@astrojs/cloudflare`
309
+ * dropped Pages in v13): it emits the Worker into `dist/server` and serves
310
+ * `dist/client` through the ASSETS binding, which honors `_headers` from that
311
+ * directory exactly as Pages does — so the file applies to both output modes
312
+ * here. A static build never loads the adapter; its `dist/` deploys to Pages,
313
+ * whose build environment `env` detects, or to Workers static assets (Workers
314
+ * Builds exposes no site URL to detect). Without a configured driver the
315
+ * adapter force-enables KV-backed sessions and declares a `SESSION` binding
316
+ * that `wrangler deploy` then demands a namespace for, even though Blume
317
+ * never reads `Astro.session`; `session: false` opts the project out.
315
318
  */
316
319
  export const cloudflarePlatform: DeployPlatform = {
317
320
  astro: {
318
- config: { session: false },
321
+ config: () => ({ session: false }),
322
+ configOptions: [],
319
323
  options: adapterOptions,
320
324
  package: CLOUDFLARE_ADAPTER_PACKAGE,
321
325
  },
@@ -350,5 +354,5 @@ export const cloudflarePlatform: DeployPlatform = {
350
354
  // build, and hoists its own `_headers`/`_redirects` back up to `dist/client`.
351
355
  serverClientUnderBase: true,
352
356
  serverOutputDir: distDir,
353
- serverStaticDir: clientDir,
357
+ serverStaticDir: (context, base) => join(clientDir(context), base),
354
358
  };
@@ -118,7 +118,8 @@ export const emitNetlifyPatternRedirects = async (
118
118
  */
119
119
  export const netlifyPlatform: DeployPlatform = {
120
120
  astro: {
121
- config: {},
121
+ config: () => ({}),
122
+ configOptions: [],
122
123
  options: () => ({}),
123
124
  package: NETLIFY_ADAPTER_PACKAGE,
124
125
  },
@@ -1,3 +1,4 @@
1
+ import type { JsonValue } from "../../core/adapter.ts";
1
2
  import { NODE_ADAPTER_PACKAGE } from "../adapters/node.ts";
2
3
  import { wrapNodeEntry } from "../node-headers.ts";
3
4
  import { clientDir, distDir } from "./paths.ts";
@@ -11,11 +12,20 @@ import type { DeployPlatform } from "./types.ts";
11
12
  * discovery files and downloaded SVGs need instead, and answers the
12
13
  * configured redirects with their exact status, which Astro's own handler
13
14
  * would default to 301 or 308. No platform env to detect: the site URL has to
14
- * be configured.
15
+ * be configured. `allowedDomains` becomes Astro's `security.allowedDomains`,
16
+ * which a server behind a reverse proxy needs before Astro reads the reader's
17
+ * address from `X-Forwarded-For`.
15
18
  */
16
19
  export const nodePlatform: DeployPlatform = {
17
20
  astro: {
18
- config: {},
21
+ config: ({ allowedDomains }) => {
22
+ const config: Record<string, JsonValue> = {};
23
+ if (allowedDomains) {
24
+ config.security = { allowedDomains };
25
+ }
26
+ return config;
27
+ },
28
+ configOptions: ["allowedDomains"],
19
29
  options: () => ({ mode: "standalone" }),
20
30
  package: NODE_ADAPTER_PACKAGE,
21
31
  },
@@ -2,7 +2,10 @@ import type { JsonValue } from "../../core/adapter.ts";
2
2
  import type { BlumeProject } from "../../core/project-graph.ts";
3
3
  import type { ResolvedConfig } from "../../core/schema.ts";
4
4
  import type { ProjectContext } from "../../core/types.ts";
5
- import type { DeployAdapterKind } from "../adapters/registry.ts";
5
+ import type {
6
+ DeployAdapterKind,
7
+ ResolvedDeployOptions,
8
+ } from "../adapters/registry.ts";
6
9
  import type { DeployOutput } from "../adapters/types.ts";
7
10
 
8
11
  /**
@@ -15,8 +18,16 @@ import type { DeployOutput } from "../adapters/types.ts";
15
18
 
16
19
  /** How the generated `astro.config.mjs` constructs the `@astrojs/*` adapter. */
17
20
  export interface AstroAdapterSpec {
18
- /** Top-level `defineConfig` entries the adapter needs beside itself. */
19
- config: Record<string, JsonValue>;
21
+ /**
22
+ * Top-level `defineConfig` entries the adapter needs beside itself, some
23
+ * of them set from the deployment's own options.
24
+ */
25
+ config: (options: ResolvedDeployOptions) => Record<string, JsonValue>;
26
+ /**
27
+ * The deployment options `config` reads, which therefore stay out of the
28
+ * passthrough to the `@astrojs/*` constructor.
29
+ */
30
+ configOptions: string[];
20
31
  /** Blume's own constructor options; the user's passthrough is spread over them. */
21
32
  options: (context: ProjectContext) => Record<string, JsonValue>;
22
33
  /** The package the config imports the adapter from. */
@@ -115,13 +126,16 @@ export interface DeployPlatform {
115
126
  /**
116
127
  * Whether the adapter moves a server build's client output under
117
128
  * `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`,
129
+ * `astro:build:done`) while the platform keeps serving `dist/client` as
130
+ * the assets root. The files the platform reads there, like `_headers`,
120
131
  * belong above the directory Astro reports.
121
132
  */
122
133
  serverClientUnderBase: boolean;
123
134
  /** Where a server build's deploy bundle lands. */
124
135
  serverOutputDir: (context: ProjectContext) => string;
125
- /** The directory a server build serves as static files. */
126
- serverStaticDir: (context: ProjectContext) => string;
136
+ /**
137
+ * The directory a server build serves as static files, given the normalized
138
+ * `deployment.base` for a platform that serves them from `<dir>/<base>/`.
139
+ */
140
+ serverStaticDir: (context: ProjectContext, base: string) => string;
127
141
  }
@@ -1,7 +1,7 @@
1
1
  import { existsSync } from "node:fs";
2
- import { readFile, writeFile } from "node:fs/promises";
2
+ import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
3
3
 
4
- import { join } from "pathe";
4
+ import { dirname, join } from "pathe";
5
5
 
6
6
  import { crossOriginDiscoveryPaths } from "../../ai/ai-catalog.ts";
7
7
  import {
@@ -20,7 +20,10 @@ import {
20
20
  SIGNATURES_DIRECTORY_PATH,
21
21
  SIGNATURES_DIRECTORY_TYPE,
22
22
  } from "../../ai/web-bot-auth.ts";
23
+ import { mountBasePath, normalizeBasePath } from "../../core/base-path.ts";
23
24
  import type { BlumeProject } from "../../core/project-graph.ts";
25
+ import { isPatternPath, regexRedirect } from "../../core/redirect-patterns.ts";
26
+ import type { ResolvedConfig } from "../../core/schema.ts";
24
27
  import type { ProjectContext } from "../../core/types.ts";
25
28
  import { VERCEL_ADAPTER_PACKAGE } from "../adapters/vercel.ts";
26
29
  import {
@@ -37,7 +40,9 @@ import {
37
40
  import {
38
41
  injectNegotiationRoutes,
39
42
  injectRedirectRoutes,
43
+ rebaseAdapterRoutes,
40
44
  } from "../vercel-negotiation.ts";
45
+ import type { VercelRoute } from "../vercel-negotiation.ts";
41
46
  import { adapterRoot, toSiteUrl } from "./paths.ts";
42
47
  import type { BuildLog, DeployPlatform, RedirectFile } from "./types.ts";
43
48
 
@@ -56,6 +61,14 @@ export const VERCEL_JSON_FILE: RedirectFile = {
56
61
  const buildOutputDir = (context: ProjectContext): string =>
57
62
  join(adapterRoot(context), ".vercel", "output");
58
63
 
64
+ /** The static files a server build serves, under the base's directory. */
65
+ const staticDir = (context: ProjectContext, base: string): string =>
66
+ join(buildOutputDir(context), "static", base);
67
+
68
+ /** The project's normalized `deployment.base`, `""` for none. */
69
+ const deploymentBase = (project: BlumeProject): string =>
70
+ normalizeBasePath(project.config.deployment.options.base);
71
+
59
72
  /**
60
73
  * Refuse to ship a function bundle that would crash at runtime: a bare import
61
74
  * the adapter's dependency trace silently dropped (see
@@ -99,18 +112,21 @@ export const emitVercelNegotiation = async (
99
112
  log: BuildLog
100
113
  ): Promise<void> => {
101
114
  const { config, context } = project;
102
- const outputDir = buildOutputDir(context);
103
- const configPath = join(outputDir, "config.json");
115
+ const configPath = join(buildOutputDir(context), "config.json");
104
116
  if (!existsSync(configPath)) {
105
117
  return;
106
118
  }
119
+ const base = deploymentBase(project);
107
120
  const routePaths = markdownRoutePaths(project);
121
+ // Keyed by the file's path in `static/`, under the base's directory.
108
122
  const overrides: Record<string, string> = {};
109
123
  if (hasApiCatalog(config)) {
110
- overrides[API_CATALOG_PATH.slice(1)] = API_CATALOG_TYPE;
124
+ overrides[mountBasePath(base, API_CATALOG_PATH).slice(1)] =
125
+ API_CATALOG_TYPE;
111
126
  }
112
127
  if (config.agents.webBotAuth.keys.length > 0) {
113
- overrides[SIGNATURES_DIRECTORY_PATH.slice(1)] = SIGNATURES_DIRECTORY_TYPE;
128
+ overrides[mountBasePath(base, SIGNATURES_DIRECTORY_PATH).slice(1)] =
129
+ SIGNATURES_DIRECTORY_TYPE;
114
130
  }
115
131
  // The homepage rewrite serves `/index.md` from the static layer, so its
116
132
  // `x-markdown-tokens` estimate has to ride the routing config; the runtime
@@ -120,7 +136,7 @@ export const emitVercelNegotiation = async (
120
136
  // The Markdown and JSON 404 routes point at the prerendered twins; only
121
137
  // wire each when the build actually emitted it (a project that owns `/404`
122
138
  // gets none).
123
- const staticDir = join(outputDir, "static");
139
+ const builtDir = staticDir(context, base);
124
140
  const injected = injectNegotiationRoutes(
125
141
  await readFile(configPath, "utf-8"),
126
142
  routePaths,
@@ -128,13 +144,14 @@ export const emitVercelNegotiation = async (
128
144
  overrides,
129
145
  home ? markdownTokenCount(agentMarkdown(home)) : undefined,
130
146
  {
131
- json: existsSync(join(staticDir, "404.json")),
132
- markdown: existsSync(join(staticDir, "404.md")),
147
+ json: existsSync(join(builtDir, "404.json")),
148
+ markdown: existsSync(join(builtDir, "404.md")),
133
149
  },
134
150
  crossOriginDiscoveryPaths(config),
135
151
  // Downloaded content assets are prerendered static files; only a build
136
152
  // that has them needs the SVG sandbox route.
137
- existsSync(join(staticDir, "blume-assets"))
153
+ existsSync(join(builtDir, "blume-assets")),
154
+ base
138
155
  );
139
156
  if (injected === null) {
140
157
  log.warn(
@@ -175,6 +192,76 @@ export const emitVercelPatternRedirects = async (
175
192
  await writeFile(configPath, injected, "utf-8");
176
193
  };
177
194
 
195
+ type Redirect = ResolvedConfig["redirects"][number];
196
+
197
+ /**
198
+ * The exact redirects as Build Output routes, matched the way the pattern ones
199
+ * are (see `regexRedirect`): the served path, a trailing slash optional.
200
+ */
201
+ const exactRedirectRoutes = (redirects: Redirect[]): VercelRoute[] =>
202
+ redirects
203
+ .filter((redirect) => !isPatternPath(redirect.from))
204
+ .map(({ from, status, to }) => ({
205
+ headers: { Location: encodeURI(to) },
206
+ src: regexRedirect({ parts: [{ kind: "text", text: from }], status, to })
207
+ .source,
208
+ status,
209
+ }));
210
+
211
+ /**
212
+ * Move a server build's static files under `deployment.base`.
213
+ * `@astrojs/vercel` copies them to the root of the Build Output tree's
214
+ * `static/`, while the pages request `<base>/_astro/…` and link to
215
+ * `<base>/…`, so nothing the site asks for would be found. Moved to
216
+ * `static/<base>/`, they're served at the base, as the routes that
217
+ * {@link rebaseVercelRoutes} moves there expect.
218
+ */
219
+ export const moveStaticUnderBase = async (
220
+ context: ProjectContext,
221
+ base: string
222
+ ): Promise<void> => {
223
+ const root = staticDir(context, "");
224
+ if (!base || !existsSync(root)) {
225
+ return;
226
+ }
227
+ // Through a sibling, since the target lies inside the directory it moves.
228
+ const staging = join(buildOutputDir(context), "static-unbased");
229
+ await rename(root, staging);
230
+ const target = staticDir(context, base);
231
+ await mkdir(dirname(target), { recursive: true });
232
+ await rename(staging, target);
233
+ };
234
+
235
+ /**
236
+ * Move the adapter's routing config under `deployment.base` to match the
237
+ * static files (see `rebaseAdapterRoutes`), its exact redirects rebuilt from
238
+ * the configured ones. Resolves to false when the config can't be read: the
239
+ * adapter's routes would then miss every path under the base.
240
+ */
241
+ export const rebaseVercelRoutes = async (
242
+ project: BlumeProject,
243
+ log: BuildLog
244
+ ): Promise<boolean> => {
245
+ const base = deploymentBase(project);
246
+ const configPath = join(buildOutputDir(project.context), "config.json");
247
+ if (!base || !existsSync(configPath)) {
248
+ return true;
249
+ }
250
+ const rebased = rebaseAdapterRoutes(
251
+ await readFile(configPath, "utf-8"),
252
+ base,
253
+ exactRedirectRoutes(platformRedirects(project))
254
+ );
255
+ if (rebased === null) {
256
+ log.error(
257
+ `Could not move the routes in .vercel/output/config.json under the deployment base "${base}", so the site would not be served there.`
258
+ );
259
+ return false;
260
+ }
261
+ await writeFile(configPath, rebased, "utf-8");
262
+ return true;
263
+ };
264
+
178
265
  /**
179
266
  * Vercel. A server build's Build Output tree lands at `.vercel/output` — at
180
267
  * the project root, because the adapter is handed that root up front: its
@@ -183,11 +270,14 @@ export const emitVercelPatternRedirects = async (
183
270
  * Static assets are served from the tree's `static/` half, so the deploy
184
271
  * artifacts are written there; headers (the discovery files', the sandbox on
185
272
  * downloaded SVGs) arrive through the routing config rather than a
186
- * `_headers` file, which Vercel never reads.
273
+ * `_headers` file, which Vercel never reads. The adapter ignores
274
+ * `deployment.base`, so under one the build moves the static files and the
275
+ * routes beneath it.
187
276
  */
188
277
  export const vercelPlatform: DeployPlatform = {
189
278
  astro: {
190
- config: {},
279
+ config: () => ({}),
280
+ configOptions: [],
191
281
  options: () => ({}),
192
282
  package: VERCEL_ADAPTER_PACKAGE,
193
283
  },
@@ -210,12 +300,19 @@ export const vercelPlatform: DeployPlatform = {
210
300
  if (!ok) {
211
301
  return false;
212
302
  }
303
+ // A verify build moves its static files too, where the budget gate reads
304
+ // them (`serverStaticDir`).
305
+ await moveStaticUnderBase(context, deploymentBase(project));
213
306
  // An isolated verify only proves the bundle would ship; the routing
214
307
  // config is a deploy artifact and stays untouched.
215
- if (!isolated) {
216
- await emitVercelNegotiation(project, log);
217
- await emitVercelPatternRedirects(project, log);
308
+ if (isolated) {
309
+ return true;
310
+ }
311
+ if (!(await rebaseVercelRoutes(project, log))) {
312
+ return false;
218
313
  }
314
+ await emitVercelNegotiation(project, log);
315
+ await emitVercelPatternRedirects(project, log);
219
316
  return true;
220
317
  },
221
318
  hiddenRuntime: {
@@ -231,5 +328,5 @@ export const vercelPlatform: DeployPlatform = {
231
328
  redirectFiles: [VERCEL_JSON_FILE],
232
329
  serverClientUnderBase: false,
233
330
  serverOutputDir: buildOutputDir,
234
- serverStaticDir: (context) => join(buildOutputDir(context), "static"),
331
+ serverStaticDir: staticDir,
235
332
  };
@@ -1,5 +1,7 @@
1
1
  import {
2
2
  normalizeBasePath,
3
+ normalizePath,
4
+ stripBasePath,
3
5
  withAuthoredBasePath,
4
6
  withBasePath,
5
7
  withComposedBasePath,
@@ -11,6 +13,7 @@ import {
11
13
  compileRedirects,
12
14
  exactFirst,
13
15
  expandRedirect,
16
+ isPatternPath,
14
17
  underscoreRedirect,
15
18
  vercelRedirect,
16
19
  } from "../core/redirect-patterns.ts";
@@ -109,9 +112,60 @@ export const applyBaseToPlatformRedirects = (
109
112
  : redirects;
110
113
  };
111
114
 
115
+ /**
116
+ * The raw-Markdown copies Blume serves beside every page (`/guide.md`,
117
+ * `/guide.mdx`; see `rawMarkdownEndpointTemplate`).
118
+ */
119
+ const MIRROR_EXTENSIONS = [".md", ".mdx"] as const;
120
+
121
+ /**
122
+ * Where a page's Markdown copy is served, from the page's served path and the
123
+ * deployment base that path carries: the endpoints name the root `index`.
124
+ */
125
+ const mirrorPath = (path: string, base: string, extension: string): string => {
126
+ const route = stripBasePath(base, path);
127
+ return `${base}${route === "/" ? "/index" : route}${extension}`;
128
+ };
129
+
130
+ /**
131
+ * Based `redirects`, each exact one that moves a page followed by the same
132
+ * redirect for the page's Markdown copies: from a path no page is served at
133
+ * to one a page is, `/old.md` goes to `/new.md` (and `.mdx` alike), where it
134
+ * would otherwise 404 for an agent still reading the old URL. A pattern gets
135
+ * none; one that carries its capture over (`/beta/:slug*` → `/v2/:slug*`)
136
+ * already moves `/beta/guide.md` along. `pages` are the served page routes
137
+ * (carrying `basePath`, not `deployment.base`), and `bases` the deployment
138
+ * base each end carries: Astro's config leaves it off `from` (see
139
+ * {@link applyBaseToAstroRedirects}).
140
+ */
141
+ export const withMirrorRedirects = (
142
+ redirects: Redirect[],
143
+ pages: RouteSet,
144
+ bases: { from: string; to: string }
145
+ ): Redirect[] =>
146
+ redirects.flatMap((redirect) => {
147
+ const from = normalizePath(redirect.from);
148
+ const to = normalizePath(redirect.to);
149
+ const movesPage =
150
+ !isPatternPath(redirect.from) &&
151
+ servesRoute(pages, stripBasePath(bases.to, to)) &&
152
+ !servesRoute(pages, stripBasePath(bases.from, from));
153
+ return movesPage
154
+ ? [
155
+ redirect,
156
+ ...MIRROR_EXTENSIONS.map((extension) => ({
157
+ from: mirrorPath(from, bases.from, extension),
158
+ status: redirect.status,
159
+ to: mirrorPath(to, bases.to, extension),
160
+ })),
161
+ ]
162
+ : [redirect];
163
+ });
164
+
112
165
  /**
113
166
  * The configured redirects as the host platform matches them, via
114
- * {@link applyBaseToPlatformRedirects}, exact paths ahead of patterns: hosts
167
+ * {@link applyBaseToPlatformRedirects} plus the Markdown copies of each moved
168
+ * page ({@link withMirrorRedirects}), exact paths ahead of patterns: hosts
115
169
  * try rules in order, and an exact redirect wins over a pattern that also
116
170
  * covers its path. The one basing every consumer must share: the emitted
117
171
  * redirect files and the server wrappers all compare these paths against
@@ -122,12 +176,18 @@ export const platformRedirects = (project: {
122
176
  manifest: Pick<BlumeProject["manifest"], "routes">;
123
177
  }): Redirect[] => {
124
178
  const { config } = project;
179
+ const base = normalizeBasePath(config.deployment.options.base);
180
+ const pages = routeSetFor(project.manifest.routes);
125
181
  return exactFirst(
126
- applyBaseToPlatformRedirects(
127
- config.redirects,
128
- config.basePath,
129
- config.deployment.options.base ?? "",
130
- routeSetFor(project.manifest.routes)
182
+ withMirrorRedirects(
183
+ applyBaseToPlatformRedirects(
184
+ config.redirects,
185
+ config.basePath,
186
+ base,
187
+ pages
188
+ ),
189
+ pages,
190
+ { from: base, to: base }
131
191
  )
132
192
  );
133
193
  };
package/src/deploy/rss.ts CHANGED
@@ -78,7 +78,9 @@ export const buildRssFeeds = (project: BlumeProject): RssFeed[] => {
78
78
  const items: RssItem[] = pages
79
79
  .map((page) => ({
80
80
  date: pageDate(page),
81
- description: page.description,
81
+ // A page with only a meta description (a GitHub release, whose
82
+ // summary goes in `seo.description`) still describes its item.
83
+ description: page.description ?? page.meta.seo.description,
82
84
  // Encode like the sitemap does: a route with spaces or non-ASCII
83
85
  // must still yield a valid <link>/<guid> URL after XML decoding.
84
86
  link: encodeURI(`${base}${mountBasePath(deployBase, page.route)}`),