blume 2.0.3 → 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 (607) hide show
  1. package/AGENTS.md +1 -0
  2. package/CHANGELOG.md +142 -0
  3. package/README.md +1 -1
  4. package/dist/cli/{chunk-5f86nr5m.js → chunk-073rdvgb.js} +21 -48
  5. package/dist/cli/{chunk-5f86nr5m.js.map → chunk-073rdvgb.js.map} +4 -5
  6. package/dist/cli/{chunk-ga0pf4aj.js → chunk-0n9xkp3d.js} +12 -3
  7. package/dist/cli/{chunk-ga0pf4aj.js.map → chunk-0n9xkp3d.js.map} +3 -3
  8. package/dist/cli/chunk-150jk7km.js +52 -0
  9. package/dist/cli/chunk-150jk7km.js.map +10 -0
  10. package/dist/cli/{chunk-9t7a85s3.js → chunk-313vnn40.js} +30 -19
  11. package/dist/cli/chunk-313vnn40.js.map +10 -0
  12. package/dist/cli/{chunk-tkacnehg.js → chunk-3v3ffbrj.js} +2 -2
  13. package/dist/cli/{chunk-q58y5e6a.js → chunk-3x6x3pph.js} +46 -24
  14. package/dist/cli/chunk-3x6x3pph.js.map +10 -0
  15. package/dist/cli/{chunk-1d7ve1dm.js → chunk-42t4jxdx.js} +37 -5
  16. package/dist/cli/chunk-42t4jxdx.js.map +12 -0
  17. package/dist/cli/{chunk-8ktnccpt.js → chunk-54ztw1nj.js} +21 -2
  18. package/dist/cli/chunk-54ztw1nj.js.map +10 -0
  19. package/dist/cli/{chunk-hm3vjy5s.js → chunk-5vjtgs6e.js} +28 -10
  20. package/dist/cli/{chunk-hm3vjy5s.js.map → chunk-5vjtgs6e.js.map} +4 -4
  21. package/dist/cli/chunk-66w9j7p6.js +322 -0
  22. package/dist/cli/chunk-66w9j7p6.js.map +14 -0
  23. package/dist/cli/{chunk-2hsdwb9n.js → chunk-7ppf1rva.js} +34 -18
  24. package/dist/cli/chunk-7ppf1rva.js.map +10 -0
  25. package/dist/cli/{chunk-r9rcc4w7.js → chunk-8y2kmjv8.js} +7 -7
  26. package/dist/cli/{chunk-r20tn01b.js → chunk-c1y538kd.js} +8 -2
  27. package/dist/cli/{chunk-r20tn01b.js.map → chunk-c1y538kd.js.map} +3 -3
  28. package/dist/cli/{chunk-2eytanqx.js → chunk-c887fj0s.js} +2 -2
  29. package/dist/cli/{chunk-p3v96n38.js → chunk-cfyg47zg.js} +16 -6
  30. package/dist/cli/chunk-cfyg47zg.js.map +10 -0
  31. package/dist/cli/chunk-cm89scdv.js +443 -0
  32. package/dist/cli/chunk-cm89scdv.js.map +11 -0
  33. package/dist/cli/{chunk-akbpwfxc.js → chunk-dzwehpy7.js} +2 -2
  34. package/dist/cli/{chunk-wrr3j9w9.js → chunk-e6atmn4h.js} +299 -72
  35. package/dist/cli/chunk-e6atmn4h.js.map +36 -0
  36. package/dist/cli/{chunk-tzmab476.js → chunk-e8xw9qkh.js} +8 -6
  37. package/dist/cli/{chunk-tzmab476.js.map → chunk-e8xw9qkh.js.map} +3 -3
  38. package/dist/cli/{chunk-6dsbexzp.js → chunk-e9q7a2ak.js} +17 -15
  39. package/dist/cli/{chunk-6dsbexzp.js.map → chunk-e9q7a2ak.js.map} +2 -2
  40. package/dist/cli/{chunk-pv29h0wf.js → chunk-ehweg4fr.js} +5155 -1249
  41. package/dist/cli/chunk-ehweg4fr.js.map +218 -0
  42. package/dist/cli/{chunk-c8chx29p.js → chunk-ep6nd0rn.js} +390 -318
  43. package/dist/cli/chunk-ep6nd0rn.js.map +36 -0
  44. package/dist/cli/{chunk-p73c0m7w.js → chunk-fdrkhpjy.js} +18 -15
  45. package/dist/cli/{chunk-p73c0m7w.js.map → chunk-fdrkhpjy.js.map} +3 -3
  46. package/dist/cli/{chunk-2z928egk.js → chunk-ft9vfys6.js} +5 -5
  47. package/dist/cli/{chunk-5m5nmvyq.js → chunk-gb420vvz.js} +316 -79
  48. package/dist/cli/chunk-gb420vvz.js.map +17 -0
  49. package/dist/cli/{chunk-vg9r4eb9.js → chunk-hebn0tpx.js} +86 -13
  50. package/dist/cli/chunk-hebn0tpx.js.map +14 -0
  51. package/dist/cli/{chunk-35d4wj9f.js → chunk-htrwgabr.js} +41 -23
  52. package/dist/cli/chunk-htrwgabr.js.map +15 -0
  53. package/dist/cli/{chunk-g4hq16wv.js → chunk-hx3xqnk9.js} +16 -14
  54. package/dist/cli/{chunk-g4hq16wv.js.map → chunk-hx3xqnk9.js.map} +2 -2
  55. package/dist/cli/chunk-j134vrp2.js +65 -0
  56. package/dist/cli/chunk-j134vrp2.js.map +10 -0
  57. package/dist/cli/{chunk-a58773jm.js → chunk-kma9f6sf.js} +2 -2
  58. package/dist/cli/chunk-kwrg63tm.js +31 -0
  59. package/dist/cli/chunk-kwrg63tm.js.map +10 -0
  60. package/dist/cli/{chunk-ey84smr6.js → chunk-mrcccgnh.js} +6 -6
  61. package/dist/cli/{chunk-ey84smr6.js.map → chunk-mrcccgnh.js.map} +1 -1
  62. package/dist/cli/{chunk-yfyb25rh.js → chunk-n0v7m8ry.js} +24 -8
  63. package/dist/cli/chunk-n0v7m8ry.js.map +14 -0
  64. package/dist/cli/{chunk-j85vccga.js → chunk-ncj7w596.js} +576 -367
  65. package/dist/cli/chunk-ncj7w596.js.map +35 -0
  66. package/dist/cli/{chunk-5xvm6tfj.js → chunk-nq1d20w2.js} +17 -15
  67. package/dist/cli/{chunk-5xvm6tfj.js.map → chunk-nq1d20w2.js.map} +2 -2
  68. package/dist/cli/{chunk-pehfxfta.js → chunk-nrns10mj.js} +6 -98
  69. package/dist/cli/chunk-nrns10mj.js.map +10 -0
  70. package/dist/cli/{chunk-crgn1q09.js → chunk-sds923fa.js} +4 -4
  71. package/dist/cli/chunk-t2m5j4pr.js +104 -0
  72. package/dist/cli/chunk-t2m5j4pr.js.map +11 -0
  73. package/dist/cli/{chunk-acanzt5p.js → chunk-tdqhyxfz.js} +6 -6
  74. package/dist/cli/chunk-tphac74f.js +126 -0
  75. package/dist/cli/chunk-tphac74f.js.map +11 -0
  76. package/dist/cli/chunk-w1r4vase.js +18 -0
  77. package/dist/cli/chunk-w1r4vase.js.map +10 -0
  78. package/dist/cli/{chunk-3em5wd2y.js → chunk-zccbjcj0.js} +4 -3
  79. package/dist/cli/{chunk-3em5wd2y.js.map → chunk-zccbjcj0.js.map} +2 -2
  80. package/dist/cli/index.js +31 -20
  81. package/dist/cli/index.js.map +5 -5
  82. package/dist/types/ai/agent-surface.d.ts +8 -7
  83. package/dist/types/ai/ai-catalog.d.ts +7 -0
  84. package/dist/types/ai/ask-context.d.ts +26 -0
  85. package/dist/types/ai/ask.d.ts +271 -58
  86. package/dist/types/ai/component-markdown.d.ts +2 -0
  87. package/dist/types/ai/index.d.ts +2 -2
  88. package/dist/types/ai/openapi-components.d.ts +4 -1
  89. package/dist/types/ai/site-skill.d.ts +26 -0
  90. package/dist/types/ai/skills.d.ts +16 -0
  91. package/dist/types/astro/integration.d.ts +9 -0
  92. package/dist/types/astro/markdown-negotiation.d.ts +3 -0
  93. package/dist/types/astro/pages.d.ts +10 -0
  94. package/dist/types/captcha/hcaptcha.d.ts +27 -0
  95. package/dist/types/captcha/index.d.ts +28 -0
  96. package/dist/types/captcha/schema.d.ts +29 -0
  97. package/dist/types/captcha/turnstile.d.ts +28 -0
  98. package/dist/types/cli/init/scaffold.d.ts +1 -1
  99. package/dist/types/components/content/api-field.d.ts +62 -0
  100. package/dist/types/components/content/api-page.d.ts +100 -0
  101. package/dist/types/components/layout/nav-utils.d.ts +26 -0
  102. package/dist/types/components/openapi/code-samples.d.ts +29 -0
  103. package/dist/types/components/openapi/helpers.d.ts +158 -0
  104. package/dist/types/components/openapi/security.d.ts +95 -0
  105. package/dist/types/components/openapi/snippets.d.ts +60 -0
  106. package/dist/types/consent/ethyca.d.ts +48 -0
  107. package/dist/types/consent/index.d.ts +37 -0
  108. package/dist/types/consent/native.d.ts +28 -0
  109. package/dist/types/consent/osano.d.ts +42 -0
  110. package/dist/types/consent/schema.d.ts +110 -0
  111. package/dist/types/core/changelog-index.d.ts +8 -0
  112. package/dist/types/core/config-input.d.ts +217 -23
  113. package/dist/types/core/data.d.ts +53 -4
  114. package/dist/types/core/footer.d.ts +8 -0
  115. package/dist/types/core/i18n-ui.d.ts +88 -0
  116. package/dist/types/core/includes.d.ts +8 -2
  117. package/dist/types/core/links.d.ts +8 -0
  118. package/dist/types/core/localizable.d.ts +7 -0
  119. package/dist/types/core/page-modes.d.ts +29 -0
  120. package/dist/types/core/probe.d.ts +2 -0
  121. package/dist/types/core/project-graph.d.ts +6 -0
  122. package/dist/types/core/redirect-patterns.d.ts +152 -0
  123. package/dist/types/core/related.d.ts +40 -0
  124. package/dist/types/core/schema.d.ts +670 -56
  125. package/dist/types/core/sources/cache.d.ts +3 -0
  126. package/dist/types/core/sources/contentful-rich-text.d.ts +11 -2
  127. package/dist/types/core/sources/contentful.d.ts +17 -1
  128. package/dist/types/core/sources/lexical.d.ts +5 -0
  129. package/dist/types/core/sources/normalize.d.ts +20 -1
  130. package/dist/types/core/sources/read.d.ts +5 -0
  131. package/dist/types/core/sources/remote.d.ts +37 -10
  132. package/dist/types/core/sources/sanity.d.ts +1 -1
  133. package/dist/types/core/types.d.ts +18 -1
  134. package/dist/types/core/variables.d.ts +42 -0
  135. package/dist/types/deploy/adapter-output.d.ts +2 -1
  136. package/dist/types/deploy/adapters/cloudflare.d.ts +7 -5
  137. package/dist/types/deploy/adapters/node.d.ts +58 -23
  138. package/dist/types/deploy/adapters/registry.d.ts +10 -0
  139. package/dist/types/deploy/adapters/types.d.ts +3 -2
  140. package/dist/types/deploy/node-headers.d.ts +13 -2
  141. package/dist/types/deploy/platforms/cloudflare.d.ts +15 -7
  142. package/dist/types/deploy/platforms/netlify.d.ts +7 -0
  143. package/dist/types/deploy/platforms/node.d.ts +3 -1
  144. package/dist/types/deploy/platforms/types.d.ts +18 -7
  145. package/dist/types/deploy/platforms/vercel.d.ts +26 -1
  146. package/dist/types/deploy/redirects.d.ts +55 -11
  147. package/dist/types/deploy/sitemap.d.ts +2 -1
  148. package/dist/types/deploy/vercel-negotiation.d.ts +51 -21
  149. package/dist/types/markdown/code-title.d.ts +17 -0
  150. package/dist/types/markdown/features.d.ts +3 -2
  151. package/dist/types/narration/build.d.ts +70 -0
  152. package/dist/types/narration/provider.d.ts +26 -0
  153. package/dist/types/narration/script.d.ts +154 -0
  154. package/dist/types/openapi/checks.d.ts +27 -0
  155. package/dist/types/openapi/graphql.d.ts +6 -0
  156. package/dist/types/openapi/model.d.ts +27 -10
  157. package/dist/types/openapi/overlay.d.ts +54 -0
  158. package/dist/types/openapi/parse.d.ts +16 -0
  159. package/dist/types/openapi/references.d.ts +30 -4
  160. package/dist/types/ratelimit/cloudflare.d.ts +39 -0
  161. package/dist/types/ratelimit/index.d.ts +27 -0
  162. package/dist/types/ratelimit/memory.d.ts +36 -0
  163. package/dist/types/ratelimit/schema.d.ts +119 -0
  164. package/dist/types/ratelimit/upstash.d.ts +46 -0
  165. package/dist/types/ratelimit/wrangler.d.ts +23 -0
  166. package/dist/types/reference/asyncapi.d.ts +10 -10
  167. package/dist/types/reference/graphql.d.ts +89 -11
  168. package/dist/types/reference/index.d.ts +1 -1
  169. package/dist/types/reference/openapi.d.ts +33 -12
  170. package/dist/types/reference/options.d.ts +82 -6
  171. package/dist/types/reference/scalar.d.ts +13 -0
  172. package/dist/types/reference/schema.d.ts +144 -36
  173. package/dist/types/search/adapters/registry.d.ts +3 -0
  174. package/dist/types/search/adapters/typesense.d.ts +14 -4
  175. package/dist/types/search/build.d.ts +3 -1
  176. package/dist/types/search/documents.d.ts +12 -1
  177. package/dist/types/search/orama-index.d.ts +13 -9
  178. package/dist/types/search/sync/algolia.d.ts +5 -2
  179. package/dist/types/search/sync/index.d.ts +3 -1
  180. package/dist/types/search/sync/typesense.d.ts +11 -7
  181. package/dist/types/seo/metatags.d.ts +20 -0
  182. package/dist/types/sources/contentful.d.ts +16 -0
  183. package/dist/types/sources/github-releases.d.ts +8 -1
  184. package/dist/types/sources/payload.d.ts +2 -0
  185. package/dist/types/sources/registry.d.ts +54 -0
  186. package/dist/types/sources/shared.d.ts +3 -0
  187. package/dist/types/sources/strapi.d.ts +2 -0
  188. package/docs/01-quickstart.mdx +4 -2
  189. package/docs/02-deployment.mdx +71 -17
  190. package/docs/03-upgrading.mdx +4 -4
  191. package/docs/04-migrating.mdx +1 -1
  192. package/docs/advanced/changelog.mdx +13 -2
  193. package/docs/advanced/skills.mdx +15 -1
  194. package/docs/cli/audit.mdx +51 -8
  195. package/docs/cli/doctor.mdx +5 -1
  196. package/docs/cli/evals.mdx +4 -4
  197. package/docs/cli/index.mdx +1 -0
  198. package/docs/cli/translate.mdx +1 -0
  199. package/docs/cli/validate.mdx +4 -3
  200. package/docs/configuration/analytics.mdx +3 -1
  201. package/docs/configuration/assistant.mdx +194 -38
  202. package/docs/configuration/consent.mdx +100 -0
  203. package/docs/configuration/customization.mdx +8 -6
  204. package/docs/configuration/export.mdx +5 -3
  205. package/docs/configuration/index.mdx +64 -5
  206. package/docs/configuration/meta.ts +3 -0
  207. package/docs/configuration/narration.mdx +119 -0
  208. package/docs/configuration/rate-limiting.mdx +90 -0
  209. package/docs/configuration/search.mdx +63 -12
  210. package/docs/configuration/theming.mdx +3 -1
  211. package/docs/content/components.mdx +108 -2
  212. package/docs/content/frontmatter.mdx +80 -2
  213. package/docs/content/i18n.mdx +39 -4
  214. package/docs/content/includes.mdx +16 -0
  215. package/docs/content/meta.mdx +1 -0
  216. package/docs/content/meta.ts +1 -0
  217. package/docs/content/navigation.mdx +32 -10
  218. package/docs/content/sources/contentful.mdx +35 -0
  219. package/docs/content/sources/custom.mdx +43 -0
  220. package/docs/content/sources/github-releases.mdx +39 -0
  221. package/docs/content/sources/index.mdx +91 -0
  222. package/docs/content/sources/meta.ts +16 -0
  223. package/docs/content/sources/notion.mdx +32 -0
  224. package/docs/content/sources/obsidian.mdx +37 -0
  225. package/docs/content/sources/payload.mdx +31 -0
  226. package/docs/content/sources/remote-mdx.mdx +44 -0
  227. package/docs/content/sources/sanity.mdx +29 -0
  228. package/docs/content/sources/strapi.mdx +32 -0
  229. package/docs/content/syntax.mdx +69 -4
  230. package/docs/content/variables.mdx +41 -0
  231. package/docs/discoverability/agent-discovery.mdx +29 -2
  232. package/docs/discoverability/index.mdx +2 -2
  233. package/docs/discoverability/json-api.mdx +2 -0
  234. package/docs/discoverability/llms-txt.mdx +2 -2
  235. package/docs/discoverability/markdown.mdx +3 -1
  236. package/docs/discoverability/mcp.mdx +4 -0
  237. package/docs/discoverability/metadata.mdx +17 -0
  238. package/docs/discoverability/sitemap-and-robots.mdx +1 -1
  239. package/docs/references/api-pages.mdx +74 -0
  240. package/docs/references/asyncapi.mdx +4 -2
  241. package/docs/references/graphql.mdx +14 -2
  242. package/docs/references/meta.ts +1 -1
  243. package/docs/references/openapi.mdx +148 -7
  244. package/docs/references/scalar.mdx +3 -1
  245. package/package.json +43 -8
  246. package/skills/blume/SKILL.md +3 -0
  247. package/skills/blume-migrate/SKILL.md +6 -6
  248. package/skills/blume-migrate/references/docusaurus.md +7 -7
  249. package/skills/blume-migrate/references/fumadocs.md +17 -13
  250. package/skills/blume-migrate/references/mintlify.md +37 -19
  251. package/skills/blume-migrate/references/nextra.md +10 -7
  252. package/skills/blume-migrate/references/starlight.md +11 -8
  253. package/skills/blume-migrate/scripts/mintlify-codemod.mjs +60 -14
  254. package/skills/blume-write-skill/SKILL.md +82 -0
  255. package/src/ai/agent-readability.ts +2 -1
  256. package/src/ai/agent-surface.ts +9 -7
  257. package/src/ai/ai-catalog.ts +6 -3
  258. package/src/ai/ask-context.ts +103 -20
  259. package/src/ai/ask-data.ts +6 -0
  260. package/src/ai/ask-limits.ts +13 -0
  261. package/src/ai/ask-tools.ts +142 -0
  262. package/src/ai/ask.ts +354 -76
  263. package/src/ai/changelog-markdown.ts +11 -6
  264. package/src/ai/component-markdown.ts +76 -1
  265. package/src/ai/cors.ts +14 -3
  266. package/src/ai/index.ts +12 -0
  267. package/src/ai/llms.ts +6 -2
  268. package/src/ai/markdown.ts +2 -1
  269. package/src/ai/mcp/data.ts +6 -0
  270. package/src/ai/mcp/query.ts +14 -6
  271. package/src/ai/openapi-components.ts +36 -6
  272. package/src/ai/relative-links.ts +4 -38
  273. package/src/ai/site-skill.ts +274 -0
  274. package/src/ai/skills.ts +35 -2
  275. package/src/ai/write-skill.ts +97 -0
  276. package/src/astro/generate.ts +210 -66
  277. package/src/astro/index.ts +1 -0
  278. package/src/astro/integration.ts +42 -4
  279. package/src/astro/markdown-negotiation.ts +3 -0
  280. package/src/astro/module-types.ts +9 -1
  281. package/src/astro/pages.ts +23 -0
  282. package/src/astro/templates.ts +406 -108
  283. package/src/astro/variables.ts +31 -0
  284. package/src/audit/catalog.ts +38 -3
  285. package/src/audit/checks/contrast.ts +183 -0
  286. package/src/audit/checks/i18n.ts +2 -3
  287. package/src/audit/checks/indexability.ts +2 -3
  288. package/src/audit/checks/links.ts +19 -13
  289. package/src/audit/checks/network.ts +110 -6
  290. package/src/audit/checks/redirects.ts +6 -4
  291. package/src/audit/checks/robots.ts +58 -2
  292. package/src/audit/checks/sitemap.ts +2 -3
  293. package/src/audit/redirects.ts +46 -15
  294. package/src/audit/run.ts +5 -0
  295. package/src/audit/types.ts +4 -1
  296. package/src/audit/url.ts +14 -5
  297. package/src/blume-modules.d.ts +12 -1
  298. package/src/captcha/client.ts +152 -0
  299. package/src/captcha/hcaptcha.ts +36 -0
  300. package/src/captcha/index.ts +28 -0
  301. package/src/captcha/schema.ts +32 -0
  302. package/src/captcha/turnstile.ts +38 -0
  303. package/src/captcha/verify.ts +71 -0
  304. package/src/cli/args.ts +51 -0
  305. package/src/cli/build-failure.ts +23 -0
  306. package/src/cli/command-meta.ts +5 -0
  307. package/src/cli/commands/audit.ts +10 -1
  308. package/src/cli/commands/dev.ts +18 -2
  309. package/src/cli/commands/doctor.ts +67 -28
  310. package/src/cli/commands/eval.ts +36 -4
  311. package/src/cli/commands/init.ts +6 -4
  312. package/src/cli/commands/skill.ts +96 -0
  313. package/src/cli/commands/validate.ts +10 -1
  314. package/src/cli/index.ts +5 -0
  315. package/src/cli/init/scaffold.ts +3 -3
  316. package/src/cli/prepare.ts +6 -3
  317. package/src/cli/required-secrets.ts +72 -30
  318. package/src/components/BlumePage.astro +2 -0
  319. package/src/components/content/ApiEndpoint.astro +18 -0
  320. package/src/components/content/ApiField.astro +79 -0
  321. package/src/components/content/ApiPlayground.astro +66 -0
  322. package/src/components/content/ApiRail.astro +19 -0
  323. package/src/components/content/Color.astro +1 -0
  324. package/src/components/content/FileTree.astro +1 -0
  325. package/src/components/content/GithubInfo.astro +1 -0
  326. package/src/components/content/Panel.astro +1 -0
  327. package/src/components/content/ParamField.astro +25 -0
  328. package/src/components/content/RequestExample.astro +17 -0
  329. package/src/components/content/ResponseExample.astro +17 -0
  330. package/src/components/content/ResponseField.astro +32 -0
  331. package/src/components/content/Step.astro +1 -0
  332. package/src/components/content/Tile.astro +1 -0
  333. package/src/components/content/Tooltip.astro +1 -0
  334. package/src/components/content/Tree.astro +1 -0
  335. package/src/components/content/TypeTable.astro +4 -1
  336. package/src/components/content/View.astro +21 -0
  337. package/src/components/content/ViewSwitcher.astro +168 -0
  338. package/src/components/content/api-field.ts +103 -0
  339. package/src/components/content/api-page.ts +248 -0
  340. package/src/components/content/mermaid-element.ts +15 -1
  341. package/src/components/content/view-switcher.ts +71 -0
  342. package/src/components/index.ts +2 -0
  343. package/src/components/islands/Assistant.astro +10 -1
  344. package/src/components/islands/assistant.tsx +104 -14
  345. package/src/components/islands/code-question.ts +62 -0
  346. package/src/components/islands/hooks.ts +136 -21
  347. package/src/components/islands/support-link.ts +68 -0
  348. package/src/components/layout/Analytics.astro +11 -2
  349. package/src/components/layout/Banner.astro +14 -10
  350. package/src/components/layout/Breadcrumbs.astro +6 -2
  351. package/src/components/layout/ConsentBanner.astro +62 -0
  352. package/src/components/layout/ConsentHead.astro +38 -0
  353. package/src/components/layout/Directory.astro +94 -0
  354. package/src/components/layout/DrawerSwitchers.astro +51 -0
  355. package/src/components/layout/Empty.astro +2 -2
  356. package/src/components/layout/Header.astro +122 -80
  357. package/src/components/layout/LanguageSwitcher.astro +48 -15
  358. package/src/components/layout/NarrationPlayer.astro +1085 -0
  359. package/src/components/layout/NavSelector.astro +33 -7
  360. package/src/components/layout/PageActions.astro +139 -24
  361. package/src/components/layout/PageFeedback.astro +54 -49
  362. package/src/components/layout/PageLayout.astro +71 -3
  363. package/src/components/layout/ReferenceLayout.astro +17 -6
  364. package/src/components/layout/RelatedPages.astro +42 -0
  365. package/src/components/layout/RootLayout.astro +318 -47
  366. package/src/components/layout/Search.astro +47 -19
  367. package/src/components/layout/SiteFooter.astro +124 -0
  368. package/src/components/layout/SiteMetatags.astro +16 -0
  369. package/src/components/layout/TableOfContents.astro +24 -17
  370. package/src/components/layout/consent/native.ts +79 -0
  371. package/src/components/layout/consent/types.ts +37 -0
  372. package/src/components/layout/footer-links.ts +53 -0
  373. package/src/components/layout/footer-socials.ts +87 -0
  374. package/src/components/layout/head-scripts.ts +6 -1
  375. package/src/components/layout/locale-redirect.ts +107 -0
  376. package/src/components/layout/nav-utils.ts +83 -0
  377. package/src/components/layout/page-feedback.ts +96 -0
  378. package/src/components/layout/search/algolia.ts +10 -1
  379. package/src/components/layout/search/analytics.ts +126 -0
  380. package/src/components/layout/search/endpoint.ts +48 -5
  381. package/src/components/layout/search/flexsearch.ts +17 -5
  382. package/src/components/layout/search/orama-cloud.ts +26 -14
  383. package/src/components/layout/search/pagefind.ts +114 -5
  384. package/src/components/layout/search/types.ts +32 -0
  385. package/src/components/layout/search/typesense.ts +19 -3
  386. package/src/components/openapi/AsyncApiOperation.astro +14 -7
  387. package/src/components/openapi/Authorization.astro +10 -0
  388. package/src/components/openapi/GraphqlOperation.astro +5 -0
  389. package/src/components/openapi/Operation.astro +184 -13
  390. package/src/components/openapi/ParametersTable.astro +8 -4
  391. package/src/components/openapi/Playground.astro +7 -1
  392. package/src/components/openapi/RequestBody.astro +24 -5
  393. package/src/components/openapi/RequestPanel.astro +59 -7
  394. package/src/components/openapi/Responses.astro +7 -4
  395. package/src/components/openapi/SchemaTable.astro +15 -3
  396. package/src/components/openapi/async-snippets.ts +65 -9
  397. package/src/components/openapi/async.ts +123 -29
  398. package/src/components/openapi/code-samples.ts +97 -0
  399. package/src/components/openapi/graphql-helpers.ts +35 -12
  400. package/src/components/openapi/helpers.ts +36 -2
  401. package/src/components/openapi/message-model.ts +54 -11
  402. package/src/components/openapi/message.ts +14 -4
  403. package/src/components/openapi/operation-model.ts +16 -7
  404. package/src/components/openapi/playground-client.ts +24 -5
  405. package/src/components/openapi/playground-schema.ts +1 -1
  406. package/src/components/openapi/schema-tree.ts +44 -14
  407. package/src/components/openapi/security.ts +16 -3
  408. package/src/components/openapi/snippets.ts +873 -10
  409. package/src/components/openapi/validate-json.ts +4 -3
  410. package/src/components/props.ts +6 -0
  411. package/src/consent/client.ts +95 -0
  412. package/src/consent/clients.ts +19 -0
  413. package/src/consent/ethyca.ts +71 -0
  414. package/src/consent/head.ts +24 -0
  415. package/src/consent/index.ts +37 -0
  416. package/src/consent/init.ts +29 -0
  417. package/src/consent/native.ts +36 -0
  418. package/src/consent/osano.ts +59 -0
  419. package/src/consent/schema.ts +30 -0
  420. package/src/core/builtin-tags.ts +5 -0
  421. package/src/core/changelog-index.ts +31 -1
  422. package/src/core/client-address.ts +19 -0
  423. package/src/core/component-diagnostics.ts +53 -0
  424. package/src/core/component-overrides.ts +32 -0
  425. package/src/core/config-input.ts +225 -23
  426. package/src/core/data.ts +46 -4
  427. package/src/core/diagnostics.ts +8 -2
  428. package/src/core/footer.ts +26 -0
  429. package/src/core/graph.ts +39 -36
  430. package/src/core/i18n-ui.ts +84 -2
  431. package/src/core/includes.ts +21 -4
  432. package/src/core/links.ts +63 -19
  433. package/src/core/localizable.ts +26 -0
  434. package/src/core/navigation.ts +43 -12
  435. package/src/core/page-modes.ts +56 -0
  436. package/src/core/probe.ts +26 -6
  437. package/src/core/project-graph.ts +163 -16
  438. package/src/core/redirect-patterns.ts +520 -0
  439. package/src/core/related.ts +131 -0
  440. package/src/core/schema.ts +339 -66
  441. package/src/core/sources/cache.ts +6 -0
  442. package/src/core/sources/contentful-rich-text.ts +29 -6
  443. package/src/core/sources/contentful.ts +135 -25
  444. package/src/core/sources/filesystem.ts +26 -4
  445. package/src/core/sources/github-releases.ts +12 -1
  446. package/src/core/sources/lexical.ts +15 -10
  447. package/src/core/sources/mdx-remote.ts +62 -10
  448. package/src/core/sources/normalize.ts +228 -41
  449. package/src/core/sources/notion.ts +7 -1
  450. package/src/core/sources/obsidian.ts +16 -3
  451. package/src/core/sources/payload.ts +38 -15
  452. package/src/core/sources/read.ts +8 -3
  453. package/src/core/sources/remote.ts +72 -15
  454. package/src/core/sources/resolve.ts +11 -1
  455. package/src/core/sources/sanity.ts +43 -25
  456. package/src/core/sources/strapi.ts +2 -2
  457. package/src/core/types.ts +19 -1
  458. package/src/core/ui-packs/ar.ts +44 -0
  459. package/src/core/ui-packs/bg.ts +46 -0
  460. package/src/core/ui-packs/bn.ts +44 -0
  461. package/src/core/ui-packs/ca.ts +47 -0
  462. package/src/core/ui-packs/cs.ts +45 -0
  463. package/src/core/ui-packs/da.ts +46 -0
  464. package/src/core/ui-packs/de.ts +47 -0
  465. package/src/core/ui-packs/el.ts +46 -0
  466. package/src/core/ui-packs/es.ts +47 -0
  467. package/src/core/ui-packs/fa.ts +46 -0
  468. package/src/core/ui-packs/fi.ts +46 -0
  469. package/src/core/ui-packs/fr.ts +47 -0
  470. package/src/core/ui-packs/he.ts +44 -0
  471. package/src/core/ui-packs/hi.ts +45 -0
  472. package/src/core/ui-packs/hr.ts +46 -0
  473. package/src/core/ui-packs/hu.ts +45 -0
  474. package/src/core/ui-packs/id.ts +46 -0
  475. package/src/core/ui-packs/it.ts +46 -0
  476. package/src/core/ui-packs/ja.ts +46 -0
  477. package/src/core/ui-packs/ko.ts +45 -0
  478. package/src/core/ui-packs/nl.ts +47 -0
  479. package/src/core/ui-packs/no.ts +45 -0
  480. package/src/core/ui-packs/pl.ts +46 -0
  481. package/src/core/ui-packs/pt-br.ts +47 -0
  482. package/src/core/ui-packs/pt.ts +46 -0
  483. package/src/core/ui-packs/ro.ts +46 -0
  484. package/src/core/ui-packs/ru.ts +46 -0
  485. package/src/core/ui-packs/sk.ts +45 -0
  486. package/src/core/ui-packs/sr.ts +46 -0
  487. package/src/core/ui-packs/sv.ts +45 -0
  488. package/src/core/ui-packs/th.ts +44 -0
  489. package/src/core/ui-packs/tr.ts +46 -0
  490. package/src/core/ui-packs/uk.ts +46 -0
  491. package/src/core/ui-packs/vi.ts +46 -0
  492. package/src/core/ui-packs/zh-tw.ts +44 -0
  493. package/src/core/ui-packs/zh.ts +44 -0
  494. package/src/core/variables.ts +103 -0
  495. package/src/deploy/adapter-output.ts +7 -2
  496. package/src/deploy/adapters/cloudflare.ts +7 -5
  497. package/src/deploy/adapters/node.ts +47 -9
  498. package/src/deploy/adapters/types.ts +7 -3
  499. package/src/deploy/artifacts.ts +79 -9
  500. package/src/deploy/cloudflare-negotiation.ts +35 -5
  501. package/src/deploy/headers.ts +2 -1
  502. package/src/deploy/node-headers.ts +63 -9
  503. package/src/deploy/platforms/cloudflare.ts +45 -9
  504. package/src/deploy/platforms/netlify.ts +35 -2
  505. package/src/deploy/platforms/node.ts +12 -2
  506. package/src/deploy/platforms/types.ts +21 -7
  507. package/src/deploy/platforms/vercel.ts +149 -17
  508. package/src/deploy/redirects.ts +151 -24
  509. package/src/deploy/rss.ts +3 -1
  510. package/src/deploy/sitemap.ts +39 -5
  511. package/src/deploy/vercel-negotiation.ts +326 -102
  512. package/src/eval/agents.ts +18 -1
  513. package/src/eval/prompts.ts +6 -3
  514. package/src/eval/report.ts +34 -13
  515. package/src/eval/run.ts +5 -0
  516. package/src/markdown/api-rail.ts +259 -0
  517. package/src/markdown/code-title.ts +35 -9
  518. package/src/markdown/features.ts +3 -2
  519. package/src/markdown/fence-meta.ts +2 -0
  520. package/src/markdown/include.ts +11 -1
  521. package/src/markdown/index.ts +28 -5
  522. package/src/markdown/math.ts +4 -4
  523. package/src/markdown/relative-links.ts +3 -2
  524. package/src/markdown/variables.ts +56 -0
  525. package/src/markdown/views.ts +82 -0
  526. package/src/narration/build.ts +412 -0
  527. package/src/narration/player.ts +249 -0
  528. package/src/narration/provider.ts +29 -0
  529. package/src/narration/script.ts +592 -0
  530. package/src/og/card.ts +7 -6
  531. package/src/openapi/checks.ts +273 -0
  532. package/src/openapi/graphql-build.ts +21 -0
  533. package/src/openapi/graphql.ts +11 -0
  534. package/src/openapi/model.ts +72 -27
  535. package/src/openapi/overlay.ts +256 -0
  536. package/src/openapi/parse.ts +182 -4
  537. package/src/openapi/references.ts +64 -8
  538. package/src/openapi/render-mdx.ts +5 -1
  539. package/src/openapi/scalar.ts +40 -6
  540. package/src/openapi/source.ts +40 -15
  541. package/src/ratelimit/cloudflare.ts +54 -0
  542. package/src/ratelimit/index.ts +30 -0
  543. package/src/ratelimit/memory.ts +45 -0
  544. package/src/ratelimit/runtime.ts +247 -0
  545. package/src/ratelimit/schema.ts +28 -0
  546. package/src/ratelimit/upstash.ts +60 -0
  547. package/src/ratelimit/wrangler.ts +70 -0
  548. package/src/reference/asyncapi.ts +2 -2
  549. package/src/reference/graphql.ts +20 -3
  550. package/src/reference/index.ts +1 -0
  551. package/src/reference/openapi.ts +21 -7
  552. package/src/reference/options.ts +80 -8
  553. package/src/reference/scalar.ts +33 -8
  554. package/src/registry/eject.ts +23 -5
  555. package/src/registry/registry.ts +7 -0
  556. package/src/search/adapters/locale-scope.ts +30 -0
  557. package/src/search/adapters/typesense.ts +14 -4
  558. package/src/search/build.ts +21 -3
  559. package/src/search/documents.ts +26 -2
  560. package/src/search/orama-index.ts +191 -34
  561. package/src/search/pagefind-ranking.ts +39 -0
  562. package/src/search/source-pages.ts +29 -0
  563. package/src/search/sync/algolia.ts +111 -10
  564. package/src/search/sync/index.ts +52 -13
  565. package/src/search/sync/orama-cloud.ts +3 -0
  566. package/src/search/sync/typesense.ts +76 -26
  567. package/src/seo/metatags.ts +67 -0
  568. package/src/sources/contentful.ts +12 -0
  569. package/src/sources/github-releases.ts +7 -1
  570. package/src/sources/shared.ts +3 -0
  571. package/src/theme/chrome-icons.ts +1 -0
  572. package/src/theme/color.ts +500 -0
  573. package/src/theme/entry.ts +85 -6
  574. package/src/theme/palette.ts +121 -45
  575. package/src/translate/prompts.ts +1 -0
  576. package/src/translate/validate.ts +31 -3
  577. package/src/upgrade/upgrade.ts +4 -171
  578. package/dist/cli/chunk-1d7ve1dm.js.map +0 -12
  579. package/dist/cli/chunk-2hsdwb9n.js.map +0 -10
  580. package/dist/cli/chunk-35d4wj9f.js.map +0 -15
  581. package/dist/cli/chunk-364znk6q.js +0 -75
  582. package/dist/cli/chunk-364znk6q.js.map +0 -11
  583. package/dist/cli/chunk-5m5nmvyq.js.map +0 -13
  584. package/dist/cli/chunk-8ktnccpt.js.map +0 -10
  585. package/dist/cli/chunk-9t7a85s3.js.map +0 -10
  586. package/dist/cli/chunk-b07cmahc.js +0 -145
  587. package/dist/cli/chunk-b07cmahc.js.map +0 -11
  588. package/dist/cli/chunk-c8chx29p.js.map +0 -36
  589. package/dist/cli/chunk-e04dxsz1.js +0 -39
  590. package/dist/cli/chunk-e04dxsz1.js.map +0 -10
  591. package/dist/cli/chunk-j85vccga.js.map +0 -36
  592. package/dist/cli/chunk-p3v96n38.js.map +0 -10
  593. package/dist/cli/chunk-pehfxfta.js.map +0 -10
  594. package/dist/cli/chunk-pv29h0wf.js.map +0 -190
  595. package/dist/cli/chunk-q58y5e6a.js.map +0 -10
  596. package/dist/cli/chunk-vg9r4eb9.js.map +0 -14
  597. package/dist/cli/chunk-wrr3j9w9.js.map +0 -35
  598. package/dist/cli/chunk-yfyb25rh.js.map +0 -14
  599. package/docs/content/sources.mdx +0 -334
  600. /package/dist/cli/{chunk-tkacnehg.js.map → chunk-3v3ffbrj.js.map} +0 -0
  601. /package/dist/cli/{chunk-r9rcc4w7.js.map → chunk-8y2kmjv8.js.map} +0 -0
  602. /package/dist/cli/{chunk-2eytanqx.js.map → chunk-c887fj0s.js.map} +0 -0
  603. /package/dist/cli/{chunk-akbpwfxc.js.map → chunk-dzwehpy7.js.map} +0 -0
  604. /package/dist/cli/{chunk-2z928egk.js.map → chunk-ft9vfys6.js.map} +0 -0
  605. /package/dist/cli/{chunk-a58773jm.js.map → chunk-kma9f6sf.js.map} +0 -0
  606. /package/dist/cli/{chunk-crgn1q09.js.map → chunk-sds923fa.js.map} +0 -0
  607. /package/dist/cli/{chunk-acanzt5p.js.map → chunk-tdqhyxfz.js.map} +0 -0
@@ -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 {
@@ -29,8 +32,17 @@ import {
29
32
  blumeDependencyNames,
30
33
  functionBundleVerdict,
31
34
  } from "../function-bundle.ts";
32
- import { buildVercelConfig } from "../redirects.ts";
33
- import { injectNegotiationRoutes } from "../vercel-negotiation.ts";
35
+ import {
36
+ buildVercelConfig,
37
+ platformRedirects,
38
+ vercelPatternRoutes,
39
+ } from "../redirects.ts";
40
+ import {
41
+ injectNegotiationRoutes,
42
+ injectRedirectRoutes,
43
+ rebaseAdapterRoutes,
44
+ } from "../vercel-negotiation.ts";
45
+ import type { VercelRoute } from "../vercel-negotiation.ts";
34
46
  import { adapterRoot, toSiteUrl } from "./paths.ts";
35
47
  import type { BuildLog, DeployPlatform, RedirectFile } from "./types.ts";
36
48
 
@@ -49,6 +61,14 @@ export const VERCEL_JSON_FILE: RedirectFile = {
49
61
  const buildOutputDir = (context: ProjectContext): string =>
50
62
  join(adapterRoot(context), ".vercel", "output");
51
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
+
52
72
  /**
53
73
  * Refuse to ship a function bundle that would crash at runtime: a bare import
54
74
  * the adapter's dependency trace silently dropped (see
@@ -92,18 +112,21 @@ export const emitVercelNegotiation = async (
92
112
  log: BuildLog
93
113
  ): Promise<void> => {
94
114
  const { config, context } = project;
95
- const outputDir = buildOutputDir(context);
96
- const configPath = join(outputDir, "config.json");
115
+ const configPath = join(buildOutputDir(context), "config.json");
97
116
  if (!existsSync(configPath)) {
98
117
  return;
99
118
  }
119
+ const base = deploymentBase(project);
100
120
  const routePaths = markdownRoutePaths(project);
121
+ // Keyed by the file's path in `static/`, under the base's directory.
101
122
  const overrides: Record<string, string> = {};
102
123
  if (hasApiCatalog(config)) {
103
- overrides[API_CATALOG_PATH.slice(1)] = API_CATALOG_TYPE;
124
+ overrides[mountBasePath(base, API_CATALOG_PATH).slice(1)] =
125
+ API_CATALOG_TYPE;
104
126
  }
105
127
  if (config.agents.webBotAuth.keys.length > 0) {
106
- overrides[SIGNATURES_DIRECTORY_PATH.slice(1)] = SIGNATURES_DIRECTORY_TYPE;
128
+ overrides[mountBasePath(base, SIGNATURES_DIRECTORY_PATH).slice(1)] =
129
+ SIGNATURES_DIRECTORY_TYPE;
107
130
  }
108
131
  // The homepage rewrite serves `/index.md` from the static layer, so its
109
132
  // `x-markdown-tokens` estimate has to ride the routing config; the runtime
@@ -113,7 +136,7 @@ export const emitVercelNegotiation = async (
113
136
  // The Markdown and JSON 404 routes point at the prerendered twins; only
114
137
  // wire each when the build actually emitted it (a project that owns `/404`
115
138
  // gets none).
116
- const staticDir = join(outputDir, "static");
139
+ const builtDir = staticDir(context, base);
117
140
  const injected = injectNegotiationRoutes(
118
141
  await readFile(configPath, "utf-8"),
119
142
  routePaths,
@@ -121,13 +144,14 @@ export const emitVercelNegotiation = async (
121
144
  overrides,
122
145
  home ? markdownTokenCount(agentMarkdown(home)) : undefined,
123
146
  {
124
- json: existsSync(join(staticDir, "404.json")),
125
- markdown: existsSync(join(staticDir, "404.md")),
147
+ json: existsSync(join(builtDir, "404.json")),
148
+ markdown: existsSync(join(builtDir, "404.md")),
126
149
  },
127
150
  crossOriginDiscoveryPaths(config),
128
151
  // Downloaded content assets are prerendered static files; only a build
129
152
  // that has them needs the SVG sandbox route.
130
- existsSync(join(staticDir, "blume-assets"))
153
+ existsSync(join(builtDir, "blume-assets")),
154
+ base
131
155
  );
132
156
  if (injected === null) {
133
157
  log.warn(
@@ -141,6 +165,103 @@ export const emitVercelNegotiation = async (
141
165
  );
142
166
  };
143
167
 
168
+ /**
169
+ * Route the pattern redirects (`/beta/:slug*`) in the adapter's Build Output
170
+ * config: Astro's `redirects` carry only the exact ones, since it can't
171
+ * prerender a pattern's pages, so the adapter never sees these.
172
+ */
173
+ export const emitVercelPatternRedirects = async (
174
+ project: BlumeProject,
175
+ log: BuildLog
176
+ ): Promise<void> => {
177
+ const routes = vercelPatternRoutes(platformRedirects(project));
178
+ const configPath = join(buildOutputDir(project.context), "config.json");
179
+ if (routes.length === 0 || !existsSync(configPath)) {
180
+ return;
181
+ }
182
+ const injected = injectRedirectRoutes(
183
+ await readFile(configPath, "utf-8"),
184
+ routes
185
+ );
186
+ if (injected === null) {
187
+ log.warn(
188
+ "Could not route the pattern redirects in .vercel/output/config.json, so paths they cover answer 404."
189
+ );
190
+ return;
191
+ }
192
+ await writeFile(configPath, injected, "utf-8");
193
+ };
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
+
144
265
  /**
145
266
  * Vercel. A server build's Build Output tree lands at `.vercel/output` — at
146
267
  * the project root, because the adapter is handed that root up front: its
@@ -149,11 +270,14 @@ export const emitVercelNegotiation = async (
149
270
  * Static assets are served from the tree's `static/` half, so the deploy
150
271
  * artifacts are written there; headers (the discovery files', the sandbox on
151
272
  * downloaded SVGs) arrive through the routing config rather than a
152
- * `_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.
153
276
  */
154
277
  export const vercelPlatform: DeployPlatform = {
155
278
  astro: {
156
- config: {},
279
+ config: () => ({}),
280
+ configOptions: [],
157
281
  options: () => ({}),
158
282
  package: VERCEL_ADAPTER_PACKAGE,
159
283
  },
@@ -176,11 +300,19 @@ export const vercelPlatform: DeployPlatform = {
176
300
  if (!ok) {
177
301
  return false;
178
302
  }
303
+ // A verify build moves its static files too, where the budget gate reads
304
+ // them (`serverStaticDir`).
305
+ await moveStaticUnderBase(context, deploymentBase(project));
179
306
  // An isolated verify only proves the bundle would ship; the routing
180
307
  // config is a deploy artifact and stays untouched.
181
- if (!isolated) {
182
- await emitVercelNegotiation(project, log);
308
+ if (isolated) {
309
+ return true;
310
+ }
311
+ if (!(await rebaseVercelRoutes(project, log))) {
312
+ return false;
183
313
  }
314
+ await emitVercelNegotiation(project, log);
315
+ await emitVercelPatternRedirects(project, log);
184
316
  return true;
185
317
  },
186
318
  hiddenRuntime: {
@@ -196,5 +328,5 @@ export const vercelPlatform: DeployPlatform = {
196
328
  redirectFiles: [VERCEL_JSON_FILE],
197
329
  serverClientUnderBase: false,
198
330
  serverOutputDir: buildOutputDir,
199
- serverStaticDir: (context) => join(buildOutputDir(context), "static"),
331
+ serverStaticDir: staticDir,
200
332
  };
@@ -1,5 +1,7 @@
1
1
  import {
2
2
  normalizeBasePath,
3
+ normalizePath,
4
+ stripBasePath,
3
5
  withAuthoredBasePath,
4
6
  withBasePath,
5
7
  withComposedBasePath,
@@ -7,16 +9,29 @@ import {
7
9
  import { routeSetFor, servesRoute } from "../core/locale-links.ts";
8
10
  import type { RouteSet } from "../core/locale-links.ts";
9
11
  import type { BlumeProject } from "../core/project-graph.ts";
12
+ import {
13
+ compileRedirects,
14
+ exactFirst,
15
+ expandRedirect,
16
+ isPatternPath,
17
+ underscoreRedirect,
18
+ vercelRedirect,
19
+ } from "../core/redirect-patterns.ts";
20
+ import type { RedirectRule } from "../core/redirect-patterns.ts";
10
21
  import type { ResolvedConfig } from "../core/schema.ts";
11
22
  import { escapeVercelSource } from "./headers.ts";
12
23
  import type { VercelHeader } from "./headers.ts";
24
+ import type { VercelRoute } from "./vercel-negotiation.ts";
13
25
 
14
26
  /**
15
27
  * Platform redirect files for a static build. Astro already emits redirect HTML
16
28
  * (meta-refresh) pages for static output, but that's a soft client redirect.
17
29
  * These give the host a real HTTP 3xx: Netlify/Cloudflare read `_redirects`,
18
30
  * Vercel reads `vercel.json`, and `blume-redirects.json` is a structured
19
- * manifest for anything else (Apache/nginx rules, an edge worker).
31
+ * manifest for anything else (Apache/nginx rules, an edge worker). A pattern
32
+ * redirect (`/beta/:slug*`) gets no redirect page, since Astro can't
33
+ * prerender one for paths it doesn't know; each host file carries it in that
34
+ * host's syntax (see `core/redirect-patterns.ts`).
20
35
  */
21
36
 
22
37
  type Redirect = ResolvedConfig["redirects"][number];
@@ -97,39 +112,116 @@ export const applyBaseToPlatformRedirects = (
97
112
  : redirects;
98
113
  };
99
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
+
100
165
  /**
101
166
  * The configured redirects as the host platform matches them, via
102
- * {@link applyBaseToPlatformRedirects}. The one basing every consumer must
103
- * share: the emitted redirect files and the Cloudflare worker-first redirect
104
- * exemptions both compare these paths against real served URLs.
167
+ * {@link applyBaseToPlatformRedirects} plus the Markdown copies of each moved
168
+ * page ({@link withMirrorRedirects}), exact paths ahead of patterns: hosts
169
+ * try rules in order, and an exact redirect wins over a pattern that also
170
+ * covers its path. The one basing every consumer must share: the emitted
171
+ * redirect files and the server wrappers all compare these paths against
172
+ * real served URLs.
105
173
  */
106
174
  export const platformRedirects = (project: {
107
175
  config: ResolvedConfig;
108
176
  manifest: Pick<BlumeProject["manifest"], "routes">;
109
177
  }): Redirect[] => {
110
178
  const { config } = project;
111
- return applyBaseToPlatformRedirects(
112
- config.redirects,
113
- config.basePath,
114
- config.deployment.options.base ?? "",
115
- routeSetFor(project.manifest.routes)
179
+ const base = normalizeBasePath(config.deployment.options.base);
180
+ const pages = routeSetFor(project.manifest.routes);
181
+ return exactFirst(
182
+ withMirrorRedirects(
183
+ applyBaseToPlatformRedirects(
184
+ config.redirects,
185
+ config.basePath,
186
+ base,
187
+ pages
188
+ ),
189
+ pages,
190
+ { from: base, to: base }
191
+ )
116
192
  );
117
193
  };
118
194
 
119
195
  /**
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.
196
+ * A redirect as the entries a host file lists: itself when its path is
197
+ * exact, else the rules its pattern expands to, in the host's syntax.
198
+ */
199
+ const hostEntries = <Entry>(
200
+ redirect: Redirect,
201
+ exact: (redirect: Redirect) => Entry,
202
+ pattern: (rule: RedirectRule) => Entry
203
+ ): Entry[] => {
204
+ const rules = expandRedirect(redirect);
205
+ return rules.length > 0 ? rules.map(pattern) : [exact(redirect)];
206
+ };
207
+
208
+ /**
209
+ * `_redirects` text (Netlify + Cloudflare Pages): `from to status` per line,
210
+ * a pattern as `:placeholder`s and a `*` splat the destination reads as
211
+ * `:splat`. `force` appends Netlify's `!` to each status (`301!`), so the rule
212
+ * wins over the redirect page Astro writes at `from`; Cloudflare, which always
213
+ * applies its rules first, rejects a line carrying it.
124
214
  */
125
215
  export const buildNetlifyRedirects = (
126
216
  redirects: Redirect[],
127
217
  force = false
128
218
  ): string =>
129
219
  `${redirects
130
- .map(
131
- (redirect) =>
132
- `${redirect.from} ${redirect.to} ${redirect.status}${force ? "!" : ""}`
220
+ .flatMap((redirect) =>
221
+ hostEntries(redirect, (exact) => exact, underscoreRedirect).map(
222
+ (entry) =>
223
+ `${entry.from} ${entry.to} ${redirect.status}${force ? "!" : ""}`
224
+ )
133
225
  )
134
226
  .join("\n")}\n`;
135
227
 
@@ -145,19 +237,25 @@ interface VercelConfig {
145
237
  * alternative to the boolean `permanent`) so the configured code ships exactly:
146
238
  * `permanent` would silently coerce a 301 to 308 and a 302 to 307, diverging
147
239
  * 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.
240
+ * `path-to-regexp` pattern, so each literal run of a `from` path is escaped:
241
+ * unescaped, `/c++-guide` fails the whole config and `/faq(old)` never
242
+ * matches. A pattern's captures are `path-to-regexp` params.
150
243
  */
151
244
  export const buildVercelConfig = (
152
245
  redirects: Redirect[],
153
246
  headers: readonly VercelHeader[] = []
154
247
  ): string => {
155
248
  const config: VercelConfig = {
156
- redirects: redirects.map((redirect) => ({
157
- destination: redirect.to,
158
- source: escapeVercelSource(redirect.from),
159
- statusCode: redirect.status,
160
- })),
249
+ redirects: redirects.flatMap((redirect) =>
250
+ hostEntries(
251
+ redirect,
252
+ (exact) => ({
253
+ destination: exact.to,
254
+ source: escapeVercelSource(exact.from),
255
+ }),
256
+ (rule) => vercelRedirect(rule, escapeVercelSource)
257
+ ).map((entry) => ({ ...entry, statusCode: redirect.status }))
258
+ ),
161
259
  };
162
260
  if (headers.length > 0) {
163
261
  config.headers = headers;
@@ -165,7 +263,36 @@ export const buildVercelConfig = (
165
263
  return `${JSON.stringify(config, null, 2)}\n`;
166
264
  };
167
265
 
168
- /** Structured manifest for hosts that need manual wiring. */
266
+ /**
267
+ * The pattern redirects as `_redirects` entries, for the `redirects` of a
268
+ * Netlify server build's Frameworks API config (see
269
+ * {@link buildNetlifyRedirects} for the syntax).
270
+ */
271
+ export const netlifyPatternRedirects = (
272
+ redirects: Redirect[]
273
+ ): { from: string; status: number; to: string }[] =>
274
+ redirects.flatMap(expandRedirect).map((rule) => ({
275
+ ...underscoreRedirect(rule),
276
+ status: rule.status,
277
+ }));
278
+
279
+ /**
280
+ * The pattern redirects as Build Output API routes, for a Vercel server
281
+ * build: the adapter routes only the exact redirects Astro's config carries.
282
+ * Each matches the served path, a trailing slash optional, and fills its
283
+ * `Location` from numbered captures, as the adapter's own redirect routes do.
284
+ */
285
+ export const vercelPatternRoutes = (redirects: Redirect[]): VercelRoute[] =>
286
+ compileRedirects(redirects).map(([src, location, status]) => ({
287
+ headers: { Location: location },
288
+ src,
289
+ status,
290
+ }));
291
+
292
+ /**
293
+ * Structured manifest for hosts that need manual wiring. A pattern stays as
294
+ * configured (`/beta/:slug*`), for the rule it becomes to be written by hand.
295
+ */
169
296
  export const buildRedirectManifest = (redirects: Redirect[]): string =>
170
297
  `${JSON.stringify(
171
298
  redirects.map((redirect) => ({
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)}`),
@@ -6,7 +6,11 @@ import {
6
6
  discoverPagesSync,
7
7
  hasGeneratedChangelog,
8
8
  } from "../astro/pages.ts";
9
- import { mountBasePath, normalizeBasePath } from "../core/base-path.ts";
9
+ import {
10
+ mountBasePath,
11
+ normalizeBasePath,
12
+ normalizePath,
13
+ } from "../core/base-path.ts";
10
14
  import type { BlumeProject } from "../core/project-graph.ts";
11
15
  import { siteRoot } from "../core/site-url.ts";
12
16
 
@@ -30,6 +34,30 @@ const lastmodTag = (value: string | undefined): string => {
30
34
  : `<lastmod>${date.toISOString().slice(0, 10)}</lastmod>`;
31
35
  };
32
36
 
37
+ /**
38
+ * Whether a page's own `seo.canonical` names another URL. The sitemap lists
39
+ * canonical URLs only, so such a page stays out — `blume audit` reports a
40
+ * listed one as NON_CANONICAL_IN_SITEMAP and expects it unlisted. Compared by
41
+ * decoded path, as the audit does: `served` is the page's path with the
42
+ * deployment base mounted, which the canonical carries too.
43
+ */
44
+ const canonicalElsewhere = (
45
+ canonical: string | undefined,
46
+ served: string
47
+ ): boolean => {
48
+ if (!canonical) {
49
+ return false;
50
+ }
51
+ const { pathname } = new URL(canonical);
52
+ let path = pathname;
53
+ try {
54
+ path = decodeURI(pathname);
55
+ } catch {
56
+ // A malformed escape stays as written; it can't match the served path.
57
+ }
58
+ return normalizePath(path) !== normalizePath(served);
59
+ };
60
+
33
61
  /** One emitted sitemap artifact: its dist-root filename and XML body. */
34
62
  export interface SitemapFile {
35
63
  name: string;
@@ -62,7 +90,8 @@ ${urls.join("\n")}
62
90
  * can't see: custom `.astro` pages (most importantly a custom landing `/`) and
63
91
  * the generated `/changelog` index. Returns null when the sitemap is disabled
64
92
  * or no `site` is configured (absolute URLs are required for a valid sitemap).
65
- * Drafts, hidden, and `noindex` pages are excluded.
93
+ * Drafts, hidden, and `noindex` pages are excluded, and so is a page whose
94
+ * `seo.canonical` points elsewhere.
66
95
  *
67
96
  * Sites within the per-file URL cap get the single classic `sitemap.xml`;
68
97
  * larger sites get `sitemap.xml` as a sitemap index over numbered
@@ -87,7 +116,9 @@ export const buildSitemapFiles = (
87
116
  // or when their canonical points at a still-existing latest equivalent —
88
117
  // listing a URL whose canonical says "index the other page" invites the
89
118
  // noindexed-page-in-sitemap incoherence Docusaurus is known for. A page
90
- // that exists only in an archived version stays listed (self-canonical).
119
+ // that exists only in an archived version stays listed (self-canonical),
120
+ // and a page's own `seo.canonical` wins over the version's default, as it
121
+ // does in the page head (see `canonicalElsewhere`).
91
122
  const { versions } = project.config;
92
123
  const archivedById = new Map(
93
124
  (versions?.archived ?? []).map((version) => [version.id, version])
@@ -109,6 +140,7 @@ export const buildSitemapFiles = (
109
140
  return Boolean(
110
141
  archived?.noindex ||
111
142
  (archived?.canonical === "latest" &&
143
+ !page.meta.seo.canonical &&
112
144
  currentKeys?.has(`${page.versionKey}\u0000${page.locale}`))
113
145
  );
114
146
  };
@@ -128,16 +160,18 @@ export const buildSitemapFiles = (
128
160
  urls.push(` <url><loc>${loc}</loc>${lastmodTag(lastModified)}</url>`);
129
161
  };
130
162
  for (const page of project.graph.pages) {
163
+ const served = mountBasePath(deployBase, page.route);
131
164
  if (
132
165
  page.meta.draft ||
133
166
  page.meta.sidebar.hidden ||
134
167
  page.meta.seo.noindex ||
135
168
  ERROR_ROUTES.has(page.route) ||
136
- archivedExcluded(page)
169
+ archivedExcluded(page) ||
170
+ canonicalElsewhere(page.meta.seo.canonical, served)
137
171
  ) {
138
172
  continue;
139
173
  }
140
- pushUrl(mountBasePath(deployBase, page.route), page.lastModified);
174
+ pushUrl(served, page.lastModified);
141
175
  }
142
176
  // Custom `.astro` pages and the generated changelog index mount outside
143
177
  // `basePath` (they're injected at their pattern — see `blumeIntegration`), so