blume 2.0.2 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (601) hide show
  1. package/AGENTS.md +1 -0
  2. package/CHANGELOG.md +113 -0
  3. package/README.md +1 -1
  4. package/dist/cli/{chunk-6k4ftwze.js → chunk-0gz36akr.js} +18 -2
  5. package/dist/cli/{chunk-6k4ftwze.js.map → chunk-0gz36akr.js.map} +3 -3
  6. package/dist/cli/{chunk-zp79m0ts.js → chunk-1n228xqb.js} +2 -2
  7. package/dist/cli/{chunk-ddndchfr.js → chunk-2f3ndkpw.js} +57 -29
  8. package/dist/cli/chunk-2f3ndkpw.js.map +14 -0
  9. package/dist/cli/{chunk-fsmrqk8a.js → chunk-2f7n4qzt.js} +8 -2
  10. package/dist/cli/{chunk-fsmrqk8a.js.map → chunk-2f7n4qzt.js.map} +3 -3
  11. package/dist/cli/{chunk-ppzjqwx2.js → chunk-2n4ps1b3.js} +17 -18
  12. package/dist/cli/{chunk-ppzjqwx2.js.map → chunk-2n4ps1b3.js.map} +3 -3
  13. package/dist/cli/{chunk-5r8g91qn.js → chunk-43zcf4gz.js} +398 -159
  14. package/dist/cli/chunk-43zcf4gz.js.map +36 -0
  15. package/dist/cli/chunk-4ejxbzee.js +39 -0
  16. package/dist/cli/chunk-4ejxbzee.js.map +10 -0
  17. package/dist/cli/{chunk-hr8ne106.js → chunk-4h8z9vv5.js} +296 -77
  18. package/dist/cli/chunk-4h8z9vv5.js.map +16 -0
  19. package/dist/cli/{chunk-k7pj68a8.js → chunk-5cg2rzgf.js} +18 -16
  20. package/dist/cli/{chunk-k7pj68a8.js.map → chunk-5cg2rzgf.js.map} +2 -2
  21. package/dist/cli/{chunk-h2ez8dzb.js → chunk-5ffywsmb.js} +4 -4
  22. package/dist/cli/{chunk-g698a744.js → chunk-5wj2qwn2.js} +5 -5
  23. package/dist/cli/chunk-6bm0xpgd.js +18 -0
  24. package/dist/cli/chunk-6bm0xpgd.js.map +10 -0
  25. package/dist/cli/{chunk-3yce002v.js → chunk-713mngdq.js} +15 -8
  26. package/dist/cli/chunk-713mngdq.js.map +14 -0
  27. package/dist/cli/{chunk-91ws1n6j.js → chunk-9geymqns.js} +17 -15
  28. package/dist/cli/{chunk-91ws1n6j.js.map → chunk-9geymqns.js.map} +2 -2
  29. package/dist/cli/{chunk-6dtt0zfn.js → chunk-9xr4dgee.js} +27 -18
  30. package/dist/cli/chunk-9xr4dgee.js.map +10 -0
  31. package/dist/cli/{chunk-sqw4ekg1.js → chunk-amrb6hgh.js} +2 -2
  32. package/dist/cli/chunk-cm89scdv.js +443 -0
  33. package/dist/cli/chunk-cm89scdv.js.map +11 -0
  34. package/dist/cli/{chunk-wdrt2k2v.js → chunk-cmtcnh9f.js} +90 -26
  35. package/dist/cli/chunk-cmtcnh9f.js.map +10 -0
  36. package/dist/cli/chunk-cy5bm0j0.js +104 -0
  37. package/dist/cli/chunk-cy5bm0j0.js.map +11 -0
  38. package/dist/cli/{chunk-7mbqtmgb.js → chunk-e7vvk5ya.js} +21 -12
  39. package/dist/cli/chunk-e7vvk5ya.js.map +10 -0
  40. package/dist/cli/{chunk-qkb5a8sa.js → chunk-g2z9x01v.js} +6 -98
  41. package/dist/cli/chunk-g2z9x01v.js.map +10 -0
  42. package/dist/cli/{chunk-273ygyr4.js → chunk-ga0pf4aj.js} +7 -7
  43. package/dist/cli/{chunk-273ygyr4.js.map → chunk-ga0pf4aj.js.map} +3 -3
  44. package/dist/cli/{chunk-h7k3nq3v.js → chunk-gp84za0h.js} +2 -2
  45. package/dist/cli/{chunk-hqp2ajnh.js → chunk-h2a48yhd.js} +195 -73
  46. package/dist/cli/chunk-h2a48yhd.js.map +36 -0
  47. package/dist/cli/{chunk-pbg5a4s3.js → chunk-jnwj5kjj.js} +71 -47
  48. package/dist/cli/chunk-jnwj5kjj.js.map +19 -0
  49. package/dist/cli/chunk-kwrg63tm.js +31 -0
  50. package/dist/cli/chunk-kwrg63tm.js.map +10 -0
  51. package/dist/cli/{chunk-8g8ytmgx.js → chunk-nd1821sk.js} +21 -19
  52. package/dist/cli/{chunk-8g8ytmgx.js.map → chunk-nd1821sk.js.map} +2 -2
  53. package/dist/cli/{chunk-w4bxdvsa.js → chunk-ntv4qrt7.js} +17 -15
  54. package/dist/cli/{chunk-w4bxdvsa.js.map → chunk-ntv4qrt7.js.map} +2 -2
  55. package/dist/cli/{chunk-bfwp9vp6.js → chunk-p804f3pe.js} +7 -7
  56. package/dist/cli/{chunk-4e9b9ra6.js → chunk-py62fewh.js} +6 -6
  57. package/dist/cli/{chunk-4e9b9ra6.js.map → chunk-py62fewh.js.map} +1 -1
  58. package/dist/cli/chunk-q3wxxq39.js +126 -0
  59. package/dist/cli/chunk-q3wxxq39.js.map +11 -0
  60. package/dist/cli/{chunk-j85scx15.js → chunk-rekqz4qg.js} +142 -46
  61. package/dist/cli/chunk-rekqz4qg.js.map +10 -0
  62. package/dist/cli/{chunk-00gs3wqs.js → chunk-ta27feev.js} +1 -1
  63. package/dist/cli/{chunk-v6ya5kcb.js → chunk-xkz5xbtv.js} +4031 -1132
  64. package/dist/cli/chunk-xkz5xbtv.js.map +213 -0
  65. package/dist/cli/{chunk-bbnwccaz.js → chunk-y4ykbark.js} +2 -2
  66. package/dist/cli/{chunk-n1yg3tj3.js → chunk-y5h4r2y0.js} +6 -6
  67. package/dist/cli/{chunk-n1yg3tj3.js.map → chunk-y5h4r2y0.js.map} +3 -3
  68. package/dist/cli/{chunk-nfcyttvj.js → chunk-yj17vs39.js} +9 -9
  69. package/dist/cli/{chunk-nfcyttvj.js.map → chunk-yj17vs39.js.map} +1 -1
  70. package/dist/cli/{chunk-7vtckvaw.js → chunk-yxmk4pec.js} +16 -14
  71. package/dist/cli/{chunk-7vtckvaw.js.map → chunk-yxmk4pec.js.map} +2 -2
  72. package/dist/cli/{chunk-gs7r695n.js → chunk-zccbjcj0.js} +22 -8
  73. package/dist/cli/{chunk-gs7r695n.js.map → chunk-zccbjcj0.js.map} +3 -3
  74. package/dist/cli/{chunk-xh43dwgw.js → chunk-zf748avv.js} +415 -225
  75. package/dist/cli/chunk-zf748avv.js.map +37 -0
  76. package/dist/cli/{chunk-d1v5rhy0.js → chunk-zhvd6adm.js} +16 -14
  77. package/dist/cli/{chunk-d1v5rhy0.js.map → chunk-zhvd6adm.js.map} +2 -2
  78. package/dist/cli/index.js +28 -20
  79. package/dist/cli/index.js.map +5 -5
  80. package/dist/types/ai/agent-surface.d.ts +8 -7
  81. package/dist/types/ai/ai-catalog.d.ts +7 -0
  82. package/dist/types/ai/ask-context.d.ts +22 -0
  83. package/dist/types/ai/ask.d.ts +271 -58
  84. package/dist/types/ai/index.d.ts +2 -2
  85. package/dist/types/ai/link-headers.d.ts +8 -1
  86. package/dist/types/ai/openapi-components.d.ts +5 -2
  87. package/dist/types/ai/relative-links.d.ts +9 -7
  88. package/dist/types/ai/site-skill.d.ts +26 -0
  89. package/dist/types/ai/skills.d.ts +20 -1
  90. package/dist/types/ai/tar.d.ts +1 -3
  91. package/dist/types/analytics/index.d.ts +2 -0
  92. package/dist/types/analytics/one-dollar-stats.d.ts +48 -0
  93. package/dist/types/analytics/schema.d.ts +14 -0
  94. package/dist/types/astro/integration.d.ts +11 -2
  95. package/dist/types/captcha/hcaptcha.d.ts +27 -0
  96. package/dist/types/captcha/index.d.ts +28 -0
  97. package/dist/types/captcha/schema.d.ts +29 -0
  98. package/dist/types/captcha/turnstile.d.ts +28 -0
  99. package/dist/types/components/content/api-field.d.ts +59 -0
  100. package/dist/types/components/content/api-page.d.ts +95 -0
  101. package/dist/types/components/layout/nav-utils.d.ts +26 -0
  102. package/dist/types/components/openapi/helpers.d.ts +137 -0
  103. package/dist/types/components/openapi/security.d.ts +85 -0
  104. package/dist/types/consent/ethyca.d.ts +48 -0
  105. package/dist/types/consent/index.d.ts +37 -0
  106. package/dist/types/consent/native.d.ts +28 -0
  107. package/dist/types/consent/osano.d.ts +42 -0
  108. package/dist/types/consent/schema.d.ts +110 -0
  109. package/dist/types/core/base-path.d.ts +21 -7
  110. package/dist/types/core/config-input.d.ts +196 -27
  111. package/dist/types/core/data.d.ts +49 -4
  112. package/dist/types/core/directive-diagnostics.d.ts +12 -0
  113. package/dist/types/core/footer.d.ts +8 -0
  114. package/dist/types/core/heading-markers.d.ts +5 -7
  115. package/dist/types/core/i18n-ui.d.ts +86 -0
  116. package/dist/types/core/i18n.d.ts +2 -0
  117. package/dist/types/core/includes.d.ts +8 -2
  118. package/dist/types/core/last-modified.d.ts +10 -0
  119. package/dist/types/core/localizable.d.ts +7 -0
  120. package/dist/types/core/meta.d.ts +8 -0
  121. package/dist/types/core/page-modes.d.ts +29 -0
  122. package/dist/types/core/redirect-patterns.d.ts +144 -0
  123. package/dist/types/core/related.d.ts +40 -0
  124. package/dist/types/core/schema.d.ts +558 -56
  125. package/dist/types/core/sources/lower.d.ts +29 -16
  126. package/dist/types/core/sources/normalize.d.ts +13 -1
  127. package/dist/types/core/sources/read.d.ts +5 -0
  128. package/dist/types/core/sources/watch.d.ts +12 -5
  129. package/dist/types/core/standard-schema.d.ts +5 -0
  130. package/dist/types/core/types.d.ts +1 -1
  131. package/dist/types/core/variables.d.ts +42 -0
  132. package/dist/types/deploy/artifacts.d.ts +6 -4
  133. package/dist/types/deploy/headers.d.ts +5 -0
  134. package/dist/types/deploy/node-headers.d.ts +13 -2
  135. package/dist/types/deploy/platforms/cloudflare.d.ts +5 -0
  136. package/dist/types/deploy/platforms/netlify.d.ts +7 -0
  137. package/dist/types/deploy/platforms/types.d.ts +8 -0
  138. package/dist/types/deploy/platforms/vercel.d.ts +6 -0
  139. package/dist/types/deploy/redirects.d.ts +61 -22
  140. package/dist/types/deploy/vercel-negotiation.d.ts +9 -0
  141. package/dist/types/markdown/code-title.d.ts +17 -0
  142. package/dist/types/markdown/directives.d.ts +62 -0
  143. package/dist/types/markdown/features.d.ts +24 -2
  144. package/dist/types/markdown/mdast.d.ts +63 -0
  145. package/dist/types/narration/build.d.ts +70 -0
  146. package/dist/types/narration/provider.d.ts +26 -0
  147. package/dist/types/narration/script.d.ts +154 -0
  148. package/dist/types/openapi/asyncapi.d.ts +4 -2
  149. package/dist/types/openapi/model.d.ts +12 -6
  150. package/dist/types/openapi/overlay.d.ts +54 -0
  151. package/dist/types/openapi/parse.d.ts +13 -0
  152. package/dist/types/openapi/references.d.ts +14 -4
  153. package/dist/types/ratelimit/cloudflare.d.ts +39 -0
  154. package/dist/types/ratelimit/index.d.ts +27 -0
  155. package/dist/types/ratelimit/memory.d.ts +36 -0
  156. package/dist/types/ratelimit/schema.d.ts +111 -0
  157. package/dist/types/ratelimit/upstash.d.ts +21 -0
  158. package/dist/types/ratelimit/wrangler.d.ts +23 -0
  159. package/dist/types/reference/asyncapi.d.ts +10 -10
  160. package/dist/types/reference/graphql.d.ts +13 -10
  161. package/dist/types/reference/openapi.d.ts +33 -12
  162. package/dist/types/reference/options.d.ts +45 -5
  163. package/dist/types/reference/scalar.d.ts +13 -0
  164. package/dist/types/reference/schema.d.ts +60 -36
  165. package/dist/types/search/documents.d.ts +12 -1
  166. package/dist/types/search/orama-index.d.ts +4 -0
  167. package/dist/types/seo/metatags.d.ts +20 -0
  168. package/docs/02-deployment.mdx +44 -6
  169. package/docs/03-upgrading.mdx +4 -4
  170. package/docs/04-migrating.mdx +1 -1
  171. package/docs/08-faq.mdx +1 -1
  172. package/docs/advanced/changelog.mdx +2 -2
  173. package/docs/advanced/custom-pages.mdx +3 -3
  174. package/docs/advanced/skills.mdx +14 -0
  175. package/docs/cli/audit.mdx +38 -2
  176. package/docs/cli/evals.mdx +3 -3
  177. package/docs/cli/index.mdx +1 -0
  178. package/docs/cli/translate.mdx +3 -3
  179. package/docs/cli/version.mdx +1 -1
  180. package/docs/configuration/analytics.mdx +22 -1
  181. package/docs/configuration/assistant.mdx +188 -36
  182. package/docs/configuration/consent.mdx +100 -0
  183. package/docs/configuration/customization.mdx +6 -4
  184. package/docs/configuration/index.mdx +63 -5
  185. package/docs/configuration/meta.ts +3 -0
  186. package/docs/configuration/narration.mdx +119 -0
  187. package/docs/configuration/rate-limiting.mdx +69 -0
  188. package/docs/configuration/search.mdx +30 -2
  189. package/docs/configuration/theming.mdx +3 -1
  190. package/docs/content/components.mdx +107 -1
  191. package/docs/content/frontmatter.mdx +77 -1
  192. package/docs/content/i18n.mdx +40 -3
  193. package/docs/content/includes.mdx +16 -0
  194. package/docs/content/index.mdx +4 -2
  195. package/docs/content/islands.mdx +1 -1
  196. package/docs/content/meta.mdx +4 -1
  197. package/docs/content/meta.ts +1 -0
  198. package/docs/content/navigation.mdx +28 -6
  199. package/docs/content/sources/contentful.mdx +32 -0
  200. package/docs/content/sources/custom.mdx +39 -0
  201. package/docs/content/sources/github-releases.mdx +31 -0
  202. package/docs/content/sources/index.mdx +91 -0
  203. package/docs/content/sources/meta.ts +16 -0
  204. package/docs/content/sources/notion.mdx +32 -0
  205. package/docs/content/sources/obsidian.mdx +37 -0
  206. package/docs/content/sources/payload.mdx +30 -0
  207. package/docs/content/sources/remote-mdx.mdx +40 -0
  208. package/docs/content/sources/sanity.mdx +29 -0
  209. package/docs/content/sources/strapi.mdx +31 -0
  210. package/docs/content/syntax.mdx +81 -2
  211. package/docs/content/variables.mdx +41 -0
  212. package/docs/content/versioning.mdx +1 -1
  213. package/docs/discoverability/agent-discovery.mdx +30 -3
  214. package/docs/discoverability/index.mdx +3 -2
  215. package/docs/discoverability/llms-txt.mdx +1 -1
  216. package/docs/discoverability/markdown.mdx +2 -2
  217. package/docs/discoverability/metadata.mdx +17 -0
  218. package/docs/discoverability/open-graph.mdx +5 -3
  219. package/docs/discoverability/rss.mdx +1 -1
  220. package/docs/references/api-pages.mdx +74 -0
  221. package/docs/references/asyncapi.mdx +2 -2
  222. package/docs/references/graphql.mdx +3 -3
  223. package/docs/references/meta.ts +1 -1
  224. package/docs/references/openapi.mdx +106 -5
  225. package/docs/references/scalar.mdx +3 -1
  226. package/package.json +36 -1
  227. package/skills/blume/SKILL.md +3 -0
  228. package/skills/blume-migrate/SKILL.md +5 -5
  229. package/skills/blume-migrate/references/docusaurus.md +3 -3
  230. package/skills/blume-migrate/references/fumadocs.md +1 -1
  231. package/skills/blume-migrate/references/mintlify.md +37 -19
  232. package/skills/blume-migrate/references/nextra.md +3 -3
  233. package/skills/blume-migrate/references/starlight.md +2 -1
  234. package/skills/blume-migrate/scripts/mintlify-codemod.mjs +67 -16
  235. package/skills/blume-write-skill/SKILL.md +82 -0
  236. package/src/ai/agent-readability.ts +4 -3
  237. package/src/ai/agent-surface.ts +9 -7
  238. package/src/ai/ai-catalog.ts +12 -5
  239. package/src/ai/api/handlers.ts +2 -2
  240. package/src/ai/api/spec.ts +2 -2
  241. package/src/ai/api-catalog.ts +6 -2
  242. package/src/ai/ask-context.ts +73 -20
  243. package/src/ai/ask-data.ts +6 -0
  244. package/src/ai/ask-tools.ts +142 -0
  245. package/src/ai/ask.ts +354 -76
  246. package/src/ai/changelog-markdown.ts +2 -2
  247. package/src/ai/component-markdown.ts +75 -0
  248. package/src/ai/cors.ts +2 -0
  249. package/src/ai/index.ts +12 -0
  250. package/src/ai/link-headers.ts +14 -5
  251. package/src/ai/llms.ts +24 -9
  252. package/src/ai/markdown.ts +2 -1
  253. package/src/ai/mcp/data.ts +6 -0
  254. package/src/ai/mcp/discovery.ts +2 -2
  255. package/src/ai/mcp/query.ts +2 -2
  256. package/src/ai/mcp/server.ts +2 -2
  257. package/src/ai/openapi-components.ts +31 -9
  258. package/src/ai/relative-links.ts +42 -26
  259. package/src/ai/serializers.ts +2 -1
  260. package/src/ai/site-skill.ts +274 -0
  261. package/src/ai/skills.ts +49 -3
  262. package/src/ai/tar.ts +139 -12
  263. package/src/ai/write-skill.ts +97 -0
  264. package/src/analytics/head.ts +4 -0
  265. package/src/analytics/index.ts +5 -0
  266. package/src/analytics/one-dollar-stats.ts +86 -0
  267. package/src/analytics/schema.ts +2 -0
  268. package/src/astro/generate.ts +133 -19
  269. package/src/astro/include-hmr.ts +31 -12
  270. package/src/astro/include-refresh.ts +19 -8
  271. package/src/astro/index.ts +1 -0
  272. package/src/astro/integration.ts +143 -14
  273. package/src/astro/module-types.ts +6 -0
  274. package/src/astro/runtime-modules.ts +28 -13
  275. package/src/astro/templates.ts +364 -67
  276. package/src/astro/variables.ts +31 -0
  277. package/src/audit/catalog.ts +19 -0
  278. package/src/audit/checks/contrast.ts +183 -0
  279. package/src/audit/checks/i18n.ts +2 -3
  280. package/src/audit/checks/indexability.ts +2 -3
  281. package/src/audit/checks/links.ts +13 -13
  282. package/src/audit/checks/sitemap.ts +2 -3
  283. package/src/audit/graph.ts +3 -1
  284. package/src/audit/redirects.ts +46 -15
  285. package/src/audit/run.ts +14 -9
  286. package/src/audit/snapshot.ts +5 -0
  287. package/src/audit/types.ts +2 -1
  288. package/src/blume-modules.d.ts +7 -0
  289. package/src/captcha/client.ts +152 -0
  290. package/src/captcha/hcaptcha.ts +36 -0
  291. package/src/captcha/index.ts +28 -0
  292. package/src/captcha/schema.ts +32 -0
  293. package/src/captcha/turnstile.ts +38 -0
  294. package/src/captcha/verify.ts +71 -0
  295. package/src/cli/args.ts +32 -0
  296. package/src/cli/command-meta.ts +5 -0
  297. package/src/cli/commands/audit.ts +4 -1
  298. package/src/cli/commands/doctor.ts +25 -17
  299. package/src/cli/commands/eval.ts +19 -11
  300. package/src/cli/commands/skill.ts +96 -0
  301. package/src/cli/commands/translate.ts +9 -12
  302. package/src/cli/commands/validate.ts +3 -1
  303. package/src/cli/dev-lock.ts +157 -37
  304. package/src/cli/index.ts +5 -0
  305. package/src/cli/report-format.ts +11 -6
  306. package/src/cli/required-secrets.ts +71 -29
  307. package/src/components/BlumePage.astro +2 -0
  308. package/src/components/colors.ts +19 -0
  309. package/src/components/content/ApiEndpoint.astro +18 -0
  310. package/src/components/content/ApiField.astro +79 -0
  311. package/src/components/content/ApiPlayground.astro +62 -0
  312. package/src/components/content/ApiRail.astro +19 -0
  313. package/src/components/content/Badge.astro +5 -3
  314. package/src/components/content/Color.astro +1 -0
  315. package/src/components/content/Component.astro +2 -2
  316. package/src/components/content/FileTree.astro +1 -0
  317. package/src/components/content/GithubInfo.astro +1 -0
  318. package/src/components/content/Panel.astro +1 -0
  319. package/src/components/content/ParamField.astro +25 -0
  320. package/src/components/content/RequestExample.astro +17 -0
  321. package/src/components/content/ResponseExample.astro +17 -0
  322. package/src/components/content/ResponseField.astro +32 -0
  323. package/src/components/content/Step.astro +1 -0
  324. package/src/components/content/Tab.astro +0 -1
  325. package/src/components/content/Tabs.astro +4 -0
  326. package/src/components/content/Tile.astro +1 -0
  327. package/src/components/content/Tooltip.astro +1 -0
  328. package/src/components/content/Tree.astro +1 -0
  329. package/src/components/content/TypeTable.astro +4 -1
  330. package/src/components/content/View.astro +21 -0
  331. package/src/components/content/ViewSwitcher.astro +168 -0
  332. package/src/components/content/api-field.ts +100 -0
  333. package/src/components/content/api-page.ts +224 -0
  334. package/src/components/content/badge-color.ts +5 -2
  335. package/src/components/content/base-href.ts +13 -36
  336. package/src/components/content/view-switcher.ts +71 -0
  337. package/src/components/index.ts +2 -0
  338. package/src/components/islands/Assistant.astro +10 -1
  339. package/src/components/islands/assistant.tsx +125 -18
  340. package/src/components/islands/base-path.ts +47 -10
  341. package/src/components/islands/code-question.ts +62 -0
  342. package/src/components/islands/hooks.ts +145 -40
  343. package/src/components/islands/support-link.ts +68 -0
  344. package/src/components/islands/webmcp.ts +4 -2
  345. package/src/components/layout/Analytics.astro +11 -2
  346. package/src/components/layout/Banner.astro +14 -10
  347. package/src/components/layout/Breadcrumbs.astro +11 -4
  348. package/src/components/layout/ConsentBanner.astro +62 -0
  349. package/src/components/layout/ConsentHead.astro +38 -0
  350. package/src/components/layout/Directory.astro +94 -0
  351. package/src/components/layout/DiscoveryLinks.astro +9 -5
  352. package/src/components/layout/DrawerSwitchers.astro +51 -0
  353. package/src/components/layout/Empty.astro +2 -2
  354. package/src/components/layout/Header.astro +124 -82
  355. package/src/components/layout/LanguageSwitcher.astro +50 -17
  356. package/src/components/layout/NarrationPlayer.astro +1085 -0
  357. package/src/components/layout/NavSelector.astro +35 -9
  358. package/src/components/layout/NavTabMenu.astro +3 -3
  359. package/src/components/layout/NavTree.astro +16 -6
  360. package/src/components/layout/PageActions.astro +4 -3
  361. package/src/components/layout/PageFeedback.astro +54 -49
  362. package/src/components/layout/PageLayout.astro +78 -9
  363. package/src/components/layout/Pagination.astro +3 -3
  364. package/src/components/layout/ReferenceLayout.astro +17 -6
  365. package/src/components/layout/RelatedPages.astro +42 -0
  366. package/src/components/layout/RootLayout.astro +313 -50
  367. package/src/components/layout/Search.astro +64 -30
  368. package/src/components/layout/SiteFooter.astro +124 -0
  369. package/src/components/layout/SiteMetatags.astro +16 -0
  370. package/src/components/layout/VersionBanner.astro +2 -2
  371. package/src/components/layout/analytics-client.ts +12 -0
  372. package/src/components/layout/consent/native.ts +79 -0
  373. package/src/components/layout/consent/types.ts +37 -0
  374. package/src/components/layout/footer-links.ts +53 -0
  375. package/src/components/layout/footer-socials.ts +87 -0
  376. package/src/components/layout/locale-redirect.ts +107 -0
  377. package/src/components/layout/nav-utils.ts +83 -0
  378. package/src/components/layout/page-feedback.ts +96 -0
  379. package/src/components/layout/search/analytics.ts +98 -0
  380. package/src/components/layout/search/flexsearch.ts +17 -5
  381. package/src/components/layout/search/orama-cloud.ts +26 -14
  382. package/src/components/layout/search/types.ts +20 -0
  383. package/src/components/layout/search/typesense.ts +5 -1
  384. package/src/components/layout/toc-active.ts +41 -0
  385. package/src/components/layout/toc-element.ts +8 -14
  386. package/src/components/openapi/ApiTagOperations.astro +2 -2
  387. package/src/components/openapi/AsyncApiOperation.astro +7 -4
  388. package/src/components/openapi/GraphqlChip.astro +2 -2
  389. package/src/components/openapi/Operation.astro +163 -13
  390. package/src/components/openapi/Playground.astro +4 -4
  391. package/src/components/openapi/RequestBody.astro +6 -4
  392. package/src/components/openapi/RequestPanel.astro +63 -8
  393. package/src/components/openapi/Responses.astro +6 -4
  394. package/src/components/openapi/SchemaTable.astro +7 -0
  395. package/src/components/openapi/async-snippets.ts +4 -4
  396. package/src/components/openapi/async.ts +38 -6
  397. package/src/components/openapi/code-samples.ts +97 -0
  398. package/src/components/openapi/helpers.ts +19 -9
  399. package/src/components/openapi/message-composer.ts +6 -1
  400. package/src/components/openapi/message.ts +17 -2
  401. package/src/components/openapi/operation-model.ts +81 -11
  402. package/src/components/openapi/panel.ts +4 -2
  403. package/src/components/openapi/param-style.ts +181 -0
  404. package/src/components/openapi/playground-client.ts +73 -16
  405. package/src/components/openapi/request.ts +190 -26
  406. package/src/components/openapi/schema-tree.ts +30 -24
  407. package/src/components/openapi/security.ts +6 -3
  408. package/src/components/openapi/snippets.ts +953 -13
  409. package/src/components/openapi/ws-client.ts +18 -2
  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/base-path.ts +62 -16
  421. package/src/core/builtin-tags.ts +5 -0
  422. package/src/core/client-address.ts +19 -0
  423. package/src/core/config-input.ts +203 -27
  424. package/src/core/data.ts +44 -4
  425. package/src/core/diagnostics.ts +5 -2
  426. package/src/core/directive-diagnostics.ts +99 -0
  427. package/src/core/footer.ts +26 -0
  428. package/src/core/frontmatter.ts +21 -18
  429. package/src/core/graph.ts +39 -36
  430. package/src/core/heading-markers.ts +5 -18
  431. package/src/core/i18n-ui.ts +82 -2
  432. package/src/core/i18n.ts +31 -3
  433. package/src/core/includes.ts +20 -3
  434. package/src/core/last-modified.ts +25 -3
  435. package/src/core/links.ts +16 -2
  436. package/src/core/locale-links.ts +5 -1
  437. package/src/core/localizable.ts +26 -0
  438. package/src/core/meta.ts +33 -19
  439. package/src/core/navigation.ts +71 -20
  440. package/src/core/page-modes.ts +56 -0
  441. package/src/core/project-graph.ts +138 -10
  442. package/src/core/redirect-patterns.ts +491 -0
  443. package/src/core/related.ts +131 -0
  444. package/src/core/schema.ts +342 -68
  445. package/src/core/sources/contentful-rich-text.ts +27 -20
  446. package/src/core/sources/filesystem.ts +20 -2
  447. package/src/core/sources/github-releases.ts +25 -6
  448. package/src/core/sources/lexical.ts +23 -18
  449. package/src/core/sources/lower.ts +201 -34
  450. package/src/core/sources/mdx-remote.ts +51 -16
  451. package/src/core/sources/normalize.ts +327 -91
  452. package/src/core/sources/notion.ts +52 -25
  453. package/src/core/sources/obsidian.ts +23 -6
  454. package/src/core/sources/portable-text.ts +38 -21
  455. package/src/core/sources/read.ts +8 -3
  456. package/src/core/sources/strapi-blocks.ts +20 -16
  457. package/src/core/sources/watch.ts +20 -7
  458. package/src/core/standard-schema.ts +10 -6
  459. package/src/core/types.ts +1 -1
  460. package/src/core/ui-packs/ar.ts +43 -0
  461. package/src/core/ui-packs/bg.ts +45 -0
  462. package/src/core/ui-packs/bn.ts +43 -0
  463. package/src/core/ui-packs/ca.ts +46 -0
  464. package/src/core/ui-packs/cs.ts +44 -0
  465. package/src/core/ui-packs/da.ts +45 -0
  466. package/src/core/ui-packs/de.ts +46 -0
  467. package/src/core/ui-packs/el.ts +45 -0
  468. package/src/core/ui-packs/es.ts +46 -0
  469. package/src/core/ui-packs/fa.ts +45 -0
  470. package/src/core/ui-packs/fi.ts +45 -0
  471. package/src/core/ui-packs/fr.ts +46 -0
  472. package/src/core/ui-packs/he.ts +43 -0
  473. package/src/core/ui-packs/hi.ts +44 -0
  474. package/src/core/ui-packs/hr.ts +45 -0
  475. package/src/core/ui-packs/hu.ts +44 -0
  476. package/src/core/ui-packs/id.ts +45 -0
  477. package/src/core/ui-packs/it.ts +45 -0
  478. package/src/core/ui-packs/ja.ts +45 -0
  479. package/src/core/ui-packs/ko.ts +44 -0
  480. package/src/core/ui-packs/nl.ts +46 -0
  481. package/src/core/ui-packs/no.ts +44 -0
  482. package/src/core/ui-packs/pl.ts +45 -0
  483. package/src/core/ui-packs/pt-br.ts +46 -0
  484. package/src/core/ui-packs/pt.ts +45 -0
  485. package/src/core/ui-packs/ro.ts +45 -0
  486. package/src/core/ui-packs/ru.ts +45 -0
  487. package/src/core/ui-packs/sk.ts +44 -0
  488. package/src/core/ui-packs/sr.ts +45 -0
  489. package/src/core/ui-packs/sv.ts +44 -0
  490. package/src/core/ui-packs/th.ts +43 -0
  491. package/src/core/ui-packs/tr.ts +45 -0
  492. package/src/core/ui-packs/uk.ts +45 -0
  493. package/src/core/ui-packs/vi.ts +45 -0
  494. package/src/core/ui-packs/zh-tw.ts +43 -0
  495. package/src/core/ui-packs/zh.ts +43 -0
  496. package/src/core/variables.ts +103 -0
  497. package/src/core/version-cut.ts +52 -15
  498. package/src/deploy/artifacts.ts +104 -14
  499. package/src/deploy/cloudflare-negotiation.ts +39 -17
  500. package/src/deploy/headers.ts +10 -5
  501. package/src/deploy/node-headers.ts +64 -10
  502. package/src/deploy/platforms/cloudflare.ts +44 -3
  503. package/src/deploy/platforms/netlify.ts +34 -1
  504. package/src/deploy/platforms/node.ts +1 -0
  505. package/src/deploy/platforms/static.ts +1 -0
  506. package/src/deploy/platforms/types.ts +8 -0
  507. package/src/deploy/platforms/vercel.ts +38 -2
  508. package/src/deploy/redirects.ts +139 -36
  509. package/src/deploy/robots.ts +2 -2
  510. package/src/deploy/rss.ts +4 -3
  511. package/src/deploy/sitemap.ts +7 -5
  512. package/src/deploy/vercel-negotiation.ts +36 -0
  513. package/src/markdown/api-rail.ts +259 -0
  514. package/src/markdown/base-links.ts +34 -36
  515. package/src/markdown/code-title.ts +35 -9
  516. package/src/markdown/directives.ts +242 -36
  517. package/src/markdown/features.ts +20 -2
  518. package/src/markdown/fence-meta.ts +2 -0
  519. package/src/markdown/include.ts +11 -1
  520. package/src/markdown/index.ts +35 -11
  521. package/src/markdown/math.ts +4 -4
  522. package/src/markdown/mdast.ts +5 -2
  523. package/src/markdown/relative-links.ts +12 -4
  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 +136 -11
  531. package/src/og/derive.ts +41 -31
  532. package/src/og/index.ts +1 -0
  533. package/src/openapi/asyncapi.ts +4 -2
  534. package/src/openapi/model.ts +77 -36
  535. package/src/openapi/overlay.ts +256 -0
  536. package/src/openapi/parse.ts +55 -2
  537. package/src/openapi/proxy.ts +63 -10
  538. package/src/openapi/references.ts +21 -5
  539. package/src/openapi/render-mdx.ts +46 -3
  540. package/src/openapi/scalar.ts +40 -6
  541. package/src/openapi/source.ts +1 -1
  542. package/src/ratelimit/cloudflare.ts +54 -0
  543. package/src/ratelimit/index.ts +30 -0
  544. package/src/ratelimit/memory.ts +45 -0
  545. package/src/ratelimit/runtime.ts +229 -0
  546. package/src/ratelimit/schema.ts +28 -0
  547. package/src/ratelimit/upstash.ts +30 -0
  548. package/src/ratelimit/wrangler.ts +70 -0
  549. package/src/reference/asyncapi.ts +2 -2
  550. package/src/reference/graphql.ts +5 -2
  551. package/src/reference/openapi.ts +21 -7
  552. package/src/reference/options.ts +51 -7
  553. package/src/reference/scalar.ts +33 -8
  554. package/src/registry/eject.ts +201 -28
  555. package/src/registry/registry.ts +7 -0
  556. package/src/search/adapters/version-scope.ts +30 -0
  557. package/src/search/documents.ts +93 -28
  558. package/src/search/orama-index.ts +28 -3
  559. package/src/search/pagefind-ranking.ts +39 -0
  560. package/src/search/popular.ts +2 -1
  561. package/src/search/sync/algolia.ts +15 -4
  562. package/src/search/sync/orama-cloud.ts +3 -0
  563. package/src/search/sync/typesense.ts +5 -0
  564. package/src/seo/jsonld.ts +7 -3
  565. package/src/seo/metatags.ts +67 -0
  566. package/src/theme/chrome-icons.ts +1 -0
  567. package/src/theme/color.ts +500 -0
  568. package/src/theme/entry.ts +56 -3
  569. package/src/theme/palette.ts +121 -45
  570. package/src/translate/meta.ts +68 -24
  571. package/src/translate/prompts.ts +1 -0
  572. package/src/translate/run.ts +3 -3
  573. package/src/translate/validate.ts +12 -3
  574. package/src/translate/work-list.ts +56 -17
  575. package/src/upgrade/upgrade.ts +4 -171
  576. package/dist/cli/chunk-3yce002v.js.map +0 -14
  577. package/dist/cli/chunk-5r8g91qn.js.map +0 -34
  578. package/dist/cli/chunk-6dtt0zfn.js.map +0 -10
  579. package/dist/cli/chunk-7mbqtmgb.js.map +0 -10
  580. package/dist/cli/chunk-ddndchfr.js.map +0 -14
  581. package/dist/cli/chunk-esh98wmb.js +0 -23
  582. package/dist/cli/chunk-esh98wmb.js.map +0 -10
  583. package/dist/cli/chunk-hqp2ajnh.js.map +0 -35
  584. package/dist/cli/chunk-hr8ne106.js.map +0 -13
  585. package/dist/cli/chunk-j85scx15.js.map +0 -10
  586. package/dist/cli/chunk-mqc662a6.js +0 -75
  587. package/dist/cli/chunk-mqc662a6.js.map +0 -11
  588. package/dist/cli/chunk-pbg5a4s3.js.map +0 -19
  589. package/dist/cli/chunk-qkb5a8sa.js.map +0 -10
  590. package/dist/cli/chunk-v6ya5kcb.js.map +0 -189
  591. package/dist/cli/chunk-wdrt2k2v.js.map +0 -10
  592. package/dist/cli/chunk-xh43dwgw.js.map +0 -36
  593. package/docs/content/sources.mdx +0 -338
  594. /package/dist/cli/{chunk-zp79m0ts.js.map → chunk-1n228xqb.js.map} +0 -0
  595. /package/dist/cli/{chunk-h2ez8dzb.js.map → chunk-5ffywsmb.js.map} +0 -0
  596. /package/dist/cli/{chunk-g698a744.js.map → chunk-5wj2qwn2.js.map} +0 -0
  597. /package/dist/cli/{chunk-sqw4ekg1.js.map → chunk-amrb6hgh.js.map} +0 -0
  598. /package/dist/cli/{chunk-h7k3nq3v.js.map → chunk-gp84za0h.js.map} +0 -0
  599. /package/dist/cli/{chunk-bfwp9vp6.js.map → chunk-p804f3pe.js.map} +0 -0
  600. /package/dist/cli/{chunk-00gs3wqs.js.map → chunk-ta27feev.js.map} +0 -0
  601. /package/dist/cli/{chunk-bbnwccaz.js.map → chunk-y4ykbark.js.map} +0 -0
@@ -11,8 +11,10 @@ import {
11
11
  markdownRoutePaths,
12
12
  markdownTokenCount,
13
13
  } from "../../ai/markdown.ts";
14
+ import { normalizeBasePath } from "../../core/base-path.ts";
14
15
  import type { BlumeProject } from "../../core/project-graph.ts";
15
16
  import type { ProjectContext } from "../../core/types.ts";
17
+ import { withRateLimitBinding } from "../../ratelimit/wrangler.ts";
16
18
  import { CLOUDFLARE_ADAPTER_PACKAGE } from "../adapters/cloudflare.ts";
17
19
  import {
18
20
  injectWorkerNegotiation,
@@ -90,8 +92,13 @@ export const emitCloudflareNegotiation = async (
90
92
  const home = rawMarkdown["/"];
91
93
  // The 404 twins the wrapper may substitute for the HTML shell; only wired
92
94
  // when the build actually emitted them (a project that owns `/404` gets
93
- // neither), like the Vercel routing config.
94
- const staticDir = clientDir(context);
95
+ // neither), like the Vercel routing config. The adapter writes the
96
+ // prerendered files under the deployment base (`dist/client/<base>/`),
97
+ // where the wrapper fetches them from.
98
+ const staticDir = join(
99
+ clientDir(context),
100
+ normalizeBasePath(config.deployment.options.base)
101
+ );
95
102
  const injected = injectWorkerNegotiation(
96
103
  await readFile(wranglerPath, "utf-8"),
97
104
  {
@@ -119,7 +126,7 @@ export const emitCloudflareNegotiation = async (
119
126
  // based the same way the platform files are — it answers any the
120
127
  // worker-first rules claim, where `_redirects` is never consulted and
121
128
  // Astro would default their status.
122
- redirects: platformRedirects(config),
129
+ redirects: platformRedirects(project),
123
130
  routePaths,
124
131
  }
125
132
  );
@@ -267,6 +274,36 @@ export const nameCloudflareWorker = async (
267
274
  );
268
275
  };
269
276
 
277
+ /**
278
+ * Declare the Workers rate limiting binding `rateLimit: cloudflare()` counts
279
+ * with in the built Worker's config, which `wrangler deploy` reads.
280
+ */
281
+ export const bindCloudflareRateLimit = async (
282
+ project: BlumeProject,
283
+ log: BuildLog
284
+ ): Promise<void> => {
285
+ const { rateLimit } = project.config;
286
+ if (rateLimit?.kind !== "cloudflare") {
287
+ return;
288
+ }
289
+ const wranglerPath = join(
290
+ distDir(project.context),
291
+ "server",
292
+ "wrangler.json"
293
+ );
294
+ const bound = existsSync(wranglerPath)
295
+ ? withRateLimitBinding(await readFile(wranglerPath, "utf-8"), rateLimit)
296
+ : null;
297
+ if (bound === null) {
298
+ log.warn(
299
+ "Could not declare the rate limiting binding in dist/server/wrangler.json — the routes count in memory instead."
300
+ );
301
+ return;
302
+ }
303
+ await writeFile(wranglerPath, bound, "utf-8");
304
+ log.success("Declared the Workers rate limiting binding");
305
+ };
306
+
270
307
  /**
271
308
  * Cloudflare Workers and Pages. A server build emits the Worker into
272
309
  * `dist/server` and serves `dist/client` through the ASSETS binding, which
@@ -292,6 +329,7 @@ export const cloudflarePlatform: DeployPlatform = {
292
329
  if (!isolated) {
293
330
  await nameCloudflareWorker(project, log);
294
331
  await emitCloudflareNegotiation(project, log);
332
+ await bindCloudflareRateLimit(project, log);
295
333
  await emitCloudflareDeployConfig(project.context);
296
334
  }
297
335
  return true;
@@ -308,6 +346,9 @@ export const cloudflarePlatform: DeployPlatform = {
308
346
  previewDeploy: null,
309
347
  readsHeaderFiles: { server: true, static: true },
310
348
  redirectFiles: [REDIRECTS_FILE],
349
+ // The adapter points `build.client` at `dist/client/<base>/` for a server
350
+ // build, and hoists its own `_headers`/`_redirects` back up to `dist/client`.
351
+ serverClientUnderBase: true,
311
352
  serverOutputDir: distDir,
312
353
  serverStaticDir: clientDir,
313
354
  };
@@ -9,7 +9,11 @@ import type { BlumeProject } from "../../core/project-graph.ts";
9
9
  import { NETLIFY_ADAPTER_PACKAGE } from "../adapters/netlify.ts";
10
10
  import { buildNetlifyConfigHeaders } from "../headers.ts";
11
11
  import type { NetlifyConfigHeaders } from "../headers.ts";
12
- import { buildNetlifyRedirects } from "../redirects.ts";
12
+ import {
13
+ buildNetlifyRedirects,
14
+ netlifyPatternRedirects,
15
+ platformRedirects,
16
+ } from "../redirects.ts";
13
17
  import { distDir, toSiteUrl } from "./paths.ts";
14
18
  import type { BuildLog, DeployPlatform, RedirectFile } from "./types.ts";
15
19
 
@@ -76,6 +80,33 @@ export const emitNetlifyHeaders = async (
76
80
  await writeFile(configPath, JSON.stringify(frameworks), "utf-8");
77
81
  };
78
82
 
83
+ /**
84
+ * Carry the pattern redirects (`/beta/:slug*`) into a server build, as
85
+ * `redirects` in the Frameworks API config: Astro's `redirects` carry only
86
+ * the exact ones, since it can't prerender a pattern's pages, so the adapter
87
+ * never writes these. Netlify appends them after any rules of the user's own.
88
+ */
89
+ export const emitNetlifyPatternRedirects = async (
90
+ project: BlumeProject
91
+ ): Promise<void> => {
92
+ const ours = netlifyPatternRedirects(platformRedirects(project));
93
+ const configPath = join(project.context.root, NETLIFY_CONFIG_FILE);
94
+ if (ours.length === 0 || !existsSync(configPath)) {
95
+ return;
96
+ }
97
+ // Only `redirects` is read; every other key rides through as parsed.
98
+ const frameworks: { redirects?: { from: string }[] } = JSON.parse(
99
+ await readFile(configPath, "utf-8")
100
+ );
101
+ // A second pass over the same build replaces its own entries.
102
+ const froms = new Set(ours.map((entry) => entry.from));
103
+ frameworks.redirects = [
104
+ ...(frameworks.redirects ?? []).filter((entry) => !froms.has(entry.from)),
105
+ ...ours,
106
+ ];
107
+ await writeFile(configPath, JSON.stringify(frameworks), "utf-8");
108
+ };
109
+
79
110
  /**
80
111
  * Netlify. A server build runs on Netlify Functions from the Frameworks API
81
112
  * tree the adapter writes to `.netlify/v1`, relative to the Astro root —
@@ -106,6 +137,7 @@ export const netlifyPlatform: DeployPlatform = {
106
137
  // never surfaces it.
107
138
  if (!isolated) {
108
139
  await emitNetlifyHeaders(project, log);
140
+ await emitNetlifyPatternRedirects(project);
109
141
  }
110
142
  return true;
111
143
  },
@@ -124,6 +156,7 @@ export const netlifyPlatform: DeployPlatform = {
124
156
  // same rules into that tree's config.
125
157
  readsHeaderFiles: { server: false, static: true },
126
158
  redirectFiles: [NETLIFY_REDIRECTS_FILE],
159
+ serverClientUnderBase: false,
127
160
  serverOutputDir: distDir,
128
161
  serverStaticDir: distDir,
129
162
  };
@@ -37,6 +37,7 @@ export const nodePlatform: DeployPlatform = {
37
37
  previewDeploy: null,
38
38
  readsHeaderFiles: { server: false, static: false },
39
39
  redirectFiles: [],
40
+ serverClientUnderBase: false,
40
41
  serverOutputDir: distDir,
41
42
  serverStaticDir: clientDir,
42
43
  };
@@ -25,6 +25,7 @@ export const staticPlatform: DeployPlatform = {
25
25
  previewDeploy: null,
26
26
  readsHeaderFiles: { server: false, static: true },
27
27
  redirectFiles: [REDIRECTS_FILE, VERCEL_JSON_FILE],
28
+ serverClientUnderBase: false,
28
29
  serverOutputDir: distDir,
29
30
  serverStaticDir: distDir,
30
31
  };
@@ -112,6 +112,14 @@ export interface DeployPlatform {
112
112
  readsHeaderFiles: Record<DeployOutput, boolean>;
113
113
  /** The redirect files a static build writes, beside the manifest. */
114
114
  redirectFiles: RedirectFile[];
115
+ /**
116
+ * Whether the adapter moves a server build's client output under
117
+ * `deployment.base` (`dist/client/<base>/`, the directory Astro then hands
118
+ * `astro:build:done`) while the platform keeps serving `serverStaticDir`
119
+ * as the assets root. The files the platform reads there, like `_headers`,
120
+ * belong above the directory Astro reports.
121
+ */
122
+ serverClientUnderBase: boolean;
115
123
  /** Where a server build's deploy bundle lands. */
116
124
  serverOutputDir: (context: ProjectContext) => string;
117
125
  /** The directory a server build serves as static files. */
@@ -29,8 +29,15 @@ import {
29
29
  blumeDependencyNames,
30
30
  functionBundleVerdict,
31
31
  } from "../function-bundle.ts";
32
- import { buildVercelConfig } from "../redirects.ts";
33
- import { injectNegotiationRoutes } from "../vercel-negotiation.ts";
32
+ import {
33
+ buildVercelConfig,
34
+ platformRedirects,
35
+ vercelPatternRoutes,
36
+ } from "../redirects.ts";
37
+ import {
38
+ injectNegotiationRoutes,
39
+ injectRedirectRoutes,
40
+ } from "../vercel-negotiation.ts";
34
41
  import { adapterRoot, toSiteUrl } from "./paths.ts";
35
42
  import type { BuildLog, DeployPlatform, RedirectFile } from "./types.ts";
36
43
 
@@ -141,6 +148,33 @@ export const emitVercelNegotiation = async (
141
148
  );
142
149
  };
143
150
 
151
+ /**
152
+ * Route the pattern redirects (`/beta/:slug*`) in the adapter's Build Output
153
+ * config: Astro's `redirects` carry only the exact ones, since it can't
154
+ * prerender a pattern's pages, so the adapter never sees these.
155
+ */
156
+ export const emitVercelPatternRedirects = async (
157
+ project: BlumeProject,
158
+ log: BuildLog
159
+ ): Promise<void> => {
160
+ const routes = vercelPatternRoutes(platformRedirects(project));
161
+ const configPath = join(buildOutputDir(project.context), "config.json");
162
+ if (routes.length === 0 || !existsSync(configPath)) {
163
+ return;
164
+ }
165
+ const injected = injectRedirectRoutes(
166
+ await readFile(configPath, "utf-8"),
167
+ routes
168
+ );
169
+ if (injected === null) {
170
+ log.warn(
171
+ "Could not route the pattern redirects in .vercel/output/config.json, so paths they cover answer 404."
172
+ );
173
+ return;
174
+ }
175
+ await writeFile(configPath, injected, "utf-8");
176
+ };
177
+
144
178
  /**
145
179
  * Vercel. A server build's Build Output tree lands at `.vercel/output` — at
146
180
  * the project root, because the adapter is handed that root up front: its
@@ -180,6 +214,7 @@ export const vercelPlatform: DeployPlatform = {
180
214
  // config is a deploy artifact and stays untouched.
181
215
  if (!isolated) {
182
216
  await emitVercelNegotiation(project, log);
217
+ await emitVercelPatternRedirects(project, log);
183
218
  }
184
219
  return true;
185
220
  },
@@ -194,6 +229,7 @@ export const vercelPlatform: DeployPlatform = {
194
229
  previewDeploy: "vercel deploy",
195
230
  readsHeaderFiles: { server: false, static: false },
196
231
  redirectFiles: [VERCEL_JSON_FILE],
232
+ serverClientUnderBase: false,
197
233
  serverOutputDir: buildOutputDir,
198
234
  serverStaticDir: (context) => join(buildOutputDir(context), "static"),
199
235
  };
@@ -1,21 +1,55 @@
1
1
  import {
2
2
  normalizeBasePath,
3
+ withAuthoredBasePath,
3
4
  withBasePath,
4
5
  withComposedBasePath,
5
6
  } from "../core/base-path.ts";
7
+ import { routeSetFor, servesRoute } from "../core/locale-links.ts";
8
+ import type { RouteSet } from "../core/locale-links.ts";
9
+ import type { BlumeProject } from "../core/project-graph.ts";
10
+ import {
11
+ compileRedirects,
12
+ exactFirst,
13
+ expandRedirect,
14
+ underscoreRedirect,
15
+ vercelRedirect,
16
+ } from "../core/redirect-patterns.ts";
17
+ import type { RedirectRule } from "../core/redirect-patterns.ts";
6
18
  import type { ResolvedConfig } from "../core/schema.ts";
19
+ import { escapeVercelSource } from "./headers.ts";
7
20
  import type { VercelHeader } from "./headers.ts";
21
+ import type { VercelRoute } from "./vercel-negotiation.ts";
8
22
 
9
23
  /**
10
24
  * Platform redirect files for a static build. Astro already emits redirect HTML
11
25
  * (meta-refresh) pages for static output, but that's a soft client redirect.
12
26
  * These give the host a real HTTP 3xx: Netlify/Cloudflare read `_redirects`,
13
27
  * Vercel reads `vercel.json`, and `blume-redirects.json` is a structured
14
- * manifest for anything else (Apache/nginx rules, an edge worker).
28
+ * manifest for anything else (Apache/nginx rules, an edge worker). A pattern
29
+ * redirect (`/beta/:slug*`) gets no redirect page, since Astro can't
30
+ * prerender one for paths it doesn't know; each host file carries it in that
31
+ * host's syntax (see `core/redirect-patterns.ts`).
15
32
  */
16
33
 
17
34
  type Redirect = ResolvedConfig["redirects"][number];
18
35
 
36
+ /**
37
+ * Base a redirect destination authored as if mounted at root by what it
38
+ * names (see `withAuthoredBasePath`): a page gains the full
39
+ * `{deployment.base}{basePath}` stack, a public file (`/files/spec.pdf`, with
40
+ * no page in `routes`) only the deployment base — `public/` is served there
41
+ * whatever `basePath` is. External URLs pass through.
42
+ */
43
+ const baseRedirectTarget = (
44
+ to: string,
45
+ basePath: string,
46
+ base: string,
47
+ routes: RouteSet
48
+ ): string =>
49
+ withAuthoredBasePath(base, basePath, to, (route) =>
50
+ servesRoute(routes, route)
51
+ );
52
+
19
53
  /**
20
54
  * Base a redirect for Astro's `redirects` config, where the two sides are not
21
55
  * symmetric:
@@ -23,19 +57,23 @@ type Redirect = ResolvedConfig["redirects"][number];
23
57
  * - `from` gains only `basePath`. Astro builds the match pattern with
24
58
  * `deployment.base` already applied (`getPattern(segments, config.base)`), so
25
59
  * adding it here would serve the redirect at `{base}{base}/from`.
26
- * - `to` gains the full `{deployment.base}{basePath}` stack. Astro resolves a
27
- * destination that matches a known route by regenerating it from that route's
28
- * segments, which carry no base — and passes an unmatched destination through
29
- * verbatim. Neither path prepends `base`, so a root-relative `to` escapes the
30
- * base entirely (withastro/astro#7774, still the behavior in Astro 7).
60
+ * - `to` gains the full `{deployment.base}{basePath}` stack, or the deployment
61
+ * base alone for a public file (see {@link baseRedirectTarget}). Astro
62
+ * resolves a destination that matches a known route by regenerating it from
63
+ * that route's segments, which carry no base — and passes an unmatched
64
+ * destination through verbatim. Neither path prepends `base`, so a
65
+ * root-relative `to` escapes the base entirely (withastro/astro#7774, still
66
+ * the behavior in Astro 7).
31
67
  *
32
- * Both sides are authored as if mounted at root; external `to` URLs pass
33
- * through. Idempotent, so a hand-written base isn't doubled.
68
+ * Both sides are authored as if mounted at root; `routes` (the served page
69
+ * routes, carrying `basePath`) tell a dotted page route like `/releases/v1.2`
70
+ * from a file. Idempotent, so a hand-written base isn't doubled.
34
71
  */
35
72
  export const applyBaseToAstroRedirects = (
36
73
  redirects: Redirect[],
37
74
  basePath: string,
38
- deployBase: string
75
+ deployBase: string,
76
+ routes: RouteSet
39
77
  ): Redirect[] => {
40
78
  // `deployment.base` arrives as the user wrote it (Astro accepts `/base/`,
41
79
  // even `base`); normalizing here keeps the composed paths well-formed.
@@ -44,58 +82,86 @@ export const applyBaseToAstroRedirects = (
44
82
  ? redirects.map((redirect) => ({
45
83
  ...redirect,
46
84
  from: withBasePath(basePath, redirect.from),
47
- to: withComposedBasePath(base, basePath, redirect.to),
85
+ to: baseRedirectTarget(redirect.to, basePath, base, routes),
48
86
  }))
49
87
  : redirects;
50
88
  };
51
89
 
52
90
  /**
53
91
  * Base a redirect for the host platform files below. Unlike Astro's config,
54
- * these are matched against the real served URL, so both sides carry the full
55
- * `{deployment.base}{basePath}` stack.
92
+ * these are matched against the real served URL, so `from` carries the full
93
+ * `{deployment.base}{basePath}` stack, and so does `to` unless it names a
94
+ * public file (see {@link baseRedirectTarget}).
56
95
  */
57
96
  export const applyBaseToPlatformRedirects = (
58
97
  redirects: Redirect[],
59
98
  basePath: string,
60
- deployBase: string
99
+ deployBase: string,
100
+ routes: RouteSet
61
101
  ): Redirect[] => {
62
102
  const base = normalizeBasePath(deployBase);
63
103
  return basePath || base
64
104
  ? redirects.map((redirect) => ({
65
105
  ...redirect,
66
106
  from: withComposedBasePath(base, basePath, redirect.from),
67
- to: withComposedBasePath(base, basePath, redirect.to),
107
+ to: baseRedirectTarget(redirect.to, basePath, base, routes),
68
108
  }))
69
109
  : redirects;
70
110
  };
71
111
 
72
112
  /**
73
113
  * The configured redirects as the host platform matches them, via
74
- * {@link applyBaseToPlatformRedirects}. The one basing every consumer must
75
- * share: the emitted redirect files and the Cloudflare worker-first redirect
76
- * exemptions both compare these paths against real served URLs.
114
+ * {@link applyBaseToPlatformRedirects}, exact paths ahead of patterns: hosts
115
+ * try rules in order, and an exact redirect wins over a pattern that also
116
+ * covers its path. The one basing every consumer must share: the emitted
117
+ * redirect files and the server wrappers all compare these paths against
118
+ * real served URLs.
77
119
  */
78
- export const platformRedirects = (config: ResolvedConfig): Redirect[] =>
79
- applyBaseToPlatformRedirects(
80
- config.redirects,
81
- config.basePath,
82
- config.deployment.options.base ?? ""
120
+ export const platformRedirects = (project: {
121
+ config: ResolvedConfig;
122
+ manifest: Pick<BlumeProject["manifest"], "routes">;
123
+ }): Redirect[] => {
124
+ const { config } = project;
125
+ return exactFirst(
126
+ applyBaseToPlatformRedirects(
127
+ config.redirects,
128
+ config.basePath,
129
+ config.deployment.options.base ?? "",
130
+ routeSetFor(project.manifest.routes)
131
+ )
83
132
  );
133
+ };
134
+
135
+ /**
136
+ * A redirect as the entries a host file lists: itself when its path is
137
+ * exact, else the rules its pattern expands to, in the host's syntax.
138
+ */
139
+ const hostEntries = <Entry>(
140
+ redirect: Redirect,
141
+ exact: (redirect: Redirect) => Entry,
142
+ pattern: (rule: RedirectRule) => Entry
143
+ ): Entry[] => {
144
+ const rules = expandRedirect(redirect);
145
+ return rules.length > 0 ? rules.map(pattern) : [exact(redirect)];
146
+ };
84
147
 
85
148
  /**
86
- * `_redirects` text (Netlify + Cloudflare Pages): `from to status` per line.
87
- * `force` appends Netlify's `!` to each status (`301!`), so the rule wins over
88
- * the redirect page Astro writes at `from`; Cloudflare, which always applies
89
- * its rules first, rejects a line carrying it.
149
+ * `_redirects` text (Netlify + Cloudflare Pages): `from to status` per line,
150
+ * a pattern as `:placeholder`s and a `*` splat the destination reads as
151
+ * `:splat`. `force` appends Netlify's `!` to each status (`301!`), so the rule
152
+ * wins over the redirect page Astro writes at `from`; Cloudflare, which always
153
+ * applies its rules first, rejects a line carrying it.
90
154
  */
91
155
  export const buildNetlifyRedirects = (
92
156
  redirects: Redirect[],
93
157
  force = false
94
158
  ): string =>
95
159
  `${redirects
96
- .map(
97
- (redirect) =>
98
- `${redirect.from} ${redirect.to} ${redirect.status}${force ? "!" : ""}`
160
+ .flatMap((redirect) =>
161
+ hostEntries(redirect, (exact) => exact, underscoreRedirect).map(
162
+ (entry) =>
163
+ `${entry.from} ${entry.to} ${redirect.status}${force ? "!" : ""}`
164
+ )
99
165
  )
100
166
  .join("\n")}\n`;
101
167
 
@@ -110,18 +176,26 @@ interface VercelConfig {
110
176
  * header rules are given (see `buildVercelHeaders`). Uses `statusCode` (Vercel's
111
177
  * alternative to the boolean `permanent`) so the configured code ships exactly:
112
178
  * `permanent` would silently coerce a 301 to 308 and a 302 to 307, diverging
113
- * from the `_redirects` file, which preserves exact codes.
179
+ * from the `_redirects` file, which preserves exact codes. A `source` is a
180
+ * `path-to-regexp` pattern, so each literal run of a `from` path is escaped:
181
+ * unescaped, `/c++-guide` fails the whole config and `/faq(old)` never
182
+ * matches. A pattern's captures are `path-to-regexp` params.
114
183
  */
115
184
  export const buildVercelConfig = (
116
185
  redirects: Redirect[],
117
186
  headers: readonly VercelHeader[] = []
118
187
  ): string => {
119
188
  const config: VercelConfig = {
120
- redirects: redirects.map((redirect) => ({
121
- destination: redirect.to,
122
- source: redirect.from,
123
- statusCode: redirect.status,
124
- })),
189
+ redirects: redirects.flatMap((redirect) =>
190
+ hostEntries(
191
+ redirect,
192
+ (exact) => ({
193
+ destination: exact.to,
194
+ source: escapeVercelSource(exact.from),
195
+ }),
196
+ (rule) => vercelRedirect(rule, escapeVercelSource)
197
+ ).map((entry) => ({ ...entry, statusCode: redirect.status }))
198
+ ),
125
199
  };
126
200
  if (headers.length > 0) {
127
201
  config.headers = headers;
@@ -129,7 +203,36 @@ export const buildVercelConfig = (
129
203
  return `${JSON.stringify(config, null, 2)}\n`;
130
204
  };
131
205
 
132
- /** Structured manifest for hosts that need manual wiring. */
206
+ /**
207
+ * The pattern redirects as `_redirects` entries, for the `redirects` of a
208
+ * Netlify server build's Frameworks API config (see
209
+ * {@link buildNetlifyRedirects} for the syntax).
210
+ */
211
+ export const netlifyPatternRedirects = (
212
+ redirects: Redirect[]
213
+ ): { from: string; status: number; to: string }[] =>
214
+ redirects.flatMap(expandRedirect).map((rule) => ({
215
+ ...underscoreRedirect(rule),
216
+ status: rule.status,
217
+ }));
218
+
219
+ /**
220
+ * The pattern redirects as Build Output API routes, for a Vercel server
221
+ * build: the adapter routes only the exact redirects Astro's config carries.
222
+ * Each matches the served path, a trailing slash optional, and fills its
223
+ * `Location` from numbered captures, as the adapter's own redirect routes do.
224
+ */
225
+ export const vercelPatternRoutes = (redirects: Redirect[]): VercelRoute[] =>
226
+ compileRedirects(redirects).map(([src, location, status]) => ({
227
+ headers: { Location: location },
228
+ src,
229
+ status,
230
+ }));
231
+
232
+ /**
233
+ * Structured manifest for hosts that need manual wiring. A pattern stays as
234
+ * configured (`/beta/:slug*`), for the rule it becomes to be written by hand.
235
+ */
133
236
  export const buildRedirectManifest = (redirects: Redirect[]): string =>
134
237
  `${JSON.stringify(
135
238
  redirects.map((redirect) => ({
@@ -1,4 +1,4 @@
1
- import { normalizeBasePath, withBasePath } from "../core/base-path.ts";
1
+ import { mountBasePath, normalizeBasePath } from "../core/base-path.ts";
2
2
  import type { BlumeProject } from "../core/project-graph.ts";
3
3
  import type { ContentSignalPolicy, ContentSignals } from "../core/schema.ts";
4
4
  import { absoluteUrl } from "../core/site-url.ts";
@@ -49,7 +49,7 @@ export const buildRobots = (project: BlumeProject): string | null => {
49
49
 
50
50
  const { site } = config.deployment.options;
51
51
  if (site && config.seo.sitemap) {
52
- const sitemapPath = withBasePath(
52
+ const sitemapPath = mountBasePath(
53
53
  normalizeBasePath(config.deployment.options.base),
54
54
  "/sitemap.xml"
55
55
  );
package/src/deploy/rss.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  // `&#39;` reference rather than `&apos;`, which XML accepts equally.
3
3
  import { escape as escapeXml } from "html-escaper";
4
4
 
5
- import { normalizeBasePath, withBasePath } from "../core/base-path.ts";
5
+ import { mountBasePath, normalizeBasePath } from "../core/base-path.ts";
6
6
  import type { BlumeProject } from "../core/project-graph.ts";
7
7
  import { siteRoot } from "../core/site-url.ts";
8
8
  import type { PageRecord } from "../core/types.ts";
@@ -57,7 +57,8 @@ export const buildRssFeeds = (project: BlumeProject): RssFeed[] => {
57
57
  return [];
58
58
  }
59
59
  const base = siteRoot(site);
60
- // Routes carry `basePath`; a `deployment.base` subdirectory is layered on top.
60
+ // Routes carry `basePath`; a `deployment.base` subdirectory is mounted on top
61
+ // of every one, even a route whose first segment matches it (see sitemap.ts).
61
62
  // The feed's own `link`/self URL points at the docs root under that base, while
62
63
  // `path` stays base-less (it's also the on-disk output location).
63
64
  const deployBase = normalizeBasePath(config.deployment.options.base);
@@ -80,7 +81,7 @@ export const buildRssFeeds = (project: BlumeProject): RssFeed[] => {
80
81
  description: page.description,
81
82
  // Encode like the sitemap does: a route with spaces or non-ASCII
82
83
  // must still yield a valid <link>/<guid> URL after XML decoding.
83
- link: encodeURI(`${base}${withBasePath(deployBase, page.route)}`),
84
+ link: encodeURI(`${base}${mountBasePath(deployBase, page.route)}`),
84
85
  title: page.title,
85
86
  }))
86
87
  .toSorted((a, b) => (b.date?.getTime() ?? 0) - (a.date?.getTime() ?? 0))
@@ -6,7 +6,7 @@ import {
6
6
  discoverPagesSync,
7
7
  hasGeneratedChangelog,
8
8
  } from "../astro/pages.ts";
9
- import { normalizeBasePath, withBasePath } from "../core/base-path.ts";
9
+ import { mountBasePath, normalizeBasePath } from "../core/base-path.ts";
10
10
  import type { BlumeProject } from "../core/project-graph.ts";
11
11
  import { siteRoot } from "../core/site-url.ts";
12
12
 
@@ -78,7 +78,9 @@ export const buildSitemapFiles = (
78
78
  }
79
79
 
80
80
  const base = siteRoot(site);
81
- // Routes carry `basePath`; a `deployment.base` subdirectory is layered on top.
81
+ // Routes carry `basePath`; a `deployment.base` subdirectory is mounted on
82
+ // top of every one, even a route whose first segment matches it: under base
83
+ // `/guides`, `guides/setup.md` is served at `/guides/guides/setup`.
82
84
  const deployBase = normalizeBasePath(project.config.deployment.options.base);
83
85
 
84
86
  // Archived-version pages leave the sitemap when the version is noindexed,
@@ -135,7 +137,7 @@ export const buildSitemapFiles = (
135
137
  ) {
136
138
  continue;
137
139
  }
138
- pushUrl(withBasePath(deployBase, page.route), page.lastModified);
140
+ pushUrl(mountBasePath(deployBase, page.route), page.lastModified);
139
141
  }
140
142
  // Custom `.astro` pages and the generated changelog index mount outside
141
143
  // `basePath` (they're injected at their pattern — see `blumeIntegration`), so
@@ -150,7 +152,7 @@ export const buildSitemapFiles = (
150
152
  extraRoutes.push("/changelog");
151
153
  }
152
154
  for (const route of extraRoutes) {
153
- pushUrl(withBasePath(deployBase, route));
155
+ pushUrl(mountBasePath(deployBase, route));
154
156
  }
155
157
  urls.sort();
156
158
 
@@ -169,7 +171,7 @@ export const buildSitemapFiles = (
169
171
  // Chunks sit next to sitemap.xml, so their URLs layer the same deployment
170
172
  // base robots.txt uses for the index.
171
173
  const loc = escapeXml(
172
- encodeURI(`${base}${withBasePath(deployBase, `/${name}`)}`)
174
+ encodeURI(`${base}${mountBasePath(deployBase, `/${name}`)}`)
173
175
  );
174
176
  references.push(` <sitemap><loc>${loc}</loc></sitemap>`);
175
177
  }
@@ -399,3 +399,39 @@ export const injectNegotiationRoutes = (
399
399
  config.routes = routes;
400
400
  return `${JSON.stringify(config, null, "\t")}\n`;
401
401
  };
402
+
403
+ /**
404
+ * Splice routes for the pattern redirects (see `vercelPatternRoutes`) into a
405
+ * Build Output `config.json`, in the main phase just ahead of
406
+ * `handle: "filesystem"`: after the exact redirects the adapter puts first,
407
+ * so an exact path still wins. A second pass replaces its own routes. Returns
408
+ * the updated JSON text, or `null` when there is nowhere safe to splice: an
409
+ * unparsable config, no `routes` array, or no `handle: "filesystem"` marker.
410
+ */
411
+ export const injectRedirectRoutes = (
412
+ configText: string,
413
+ redirectRoutes: readonly VercelRoute[]
414
+ ): string | null => {
415
+ let config: { routes?: VercelRoute[] };
416
+ try {
417
+ config = JSON.parse(configText);
418
+ } catch {
419
+ return null;
420
+ }
421
+ if (!Array.isArray(config.routes)) {
422
+ return null;
423
+ }
424
+ const ours = new Set(redirectRoutes.map((route) => route.src));
425
+ const routes = config.routes.filter(
426
+ (route) => !(ours.has(route.src) && isString(route.headers?.Location))
427
+ );
428
+ const filesystemIndex = routes.findIndex(
429
+ (route) => route.handle === "filesystem"
430
+ );
431
+ if (filesystemIndex === -1) {
432
+ return null;
433
+ }
434
+ routes.splice(filesystemIndex, 0, ...redirectRoutes);
435
+ config.routes = routes;
436
+ return `${JSON.stringify(config, null, "\t")}\n`;
437
+ };