blume 2.1.0 → 2.1.2

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 (450) hide show
  1. package/CHANGELOG.md +110 -0
  2. package/dist/cli/{chunk-ga0pf4aj.js → chunk-0n9xkp3d.js} +12 -3
  3. package/dist/cli/{chunk-ga0pf4aj.js.map → chunk-0n9xkp3d.js.map} +3 -3
  4. package/dist/cli/{chunk-43zcf4gz.js → chunk-0pmb4tr4.js} +541 -368
  5. package/dist/cli/chunk-0pmb4tr4.js.map +35 -0
  6. package/dist/cli/{chunk-9geymqns.js → chunk-0w6hs479.js} +15 -15
  7. package/dist/cli/{chunk-xkz5xbtv.js → chunk-124fvmzq.js} +2802 -1422
  8. package/dist/cli/chunk-124fvmzq.js.map +220 -0
  9. package/dist/cli/chunk-150jk7km.js +52 -0
  10. package/dist/cli/chunk-150jk7km.js.map +10 -0
  11. package/dist/cli/{chunk-y4ykbark.js → chunk-1f882900.js} +2 -2
  12. package/dist/cli/{chunk-5ffywsmb.js → chunk-297ajdcv.js} +8 -6
  13. package/dist/cli/{chunk-5ffywsmb.js.map → chunk-297ajdcv.js.map} +3 -3
  14. package/dist/cli/{chunk-h2a48yhd.js → chunk-32khwcb6.js} +120 -16
  15. package/dist/cli/{chunk-h2a48yhd.js.map → chunk-32khwcb6.js.map} +10 -10
  16. package/dist/cli/{chunk-5cg2rzgf.js → chunk-36bxs9c7.js} +20 -49
  17. package/dist/cli/{chunk-5cg2rzgf.js.map → chunk-36bxs9c7.js.map} +4 -5
  18. package/dist/cli/{chunk-py62fewh.js → chunk-53jqw8z0.js} +3 -3
  19. package/dist/cli/{chunk-5wj2qwn2.js → chunk-58bt3hag.js} +4 -4
  20. package/dist/cli/{chunk-e7vvk5ya.js → chunk-5cmts6dv.js} +3 -3
  21. package/dist/cli/{chunk-y5h4r2y0.js → chunk-60hz265r.js} +15 -5
  22. package/dist/cli/chunk-60hz265r.js.map +10 -0
  23. package/dist/cli/chunk-74wfhv79.js +65 -0
  24. package/dist/cli/chunk-74wfhv79.js.map +10 -0
  25. package/dist/cli/{chunk-g2z9x01v.js → chunk-9qzgg6mj.js} +3 -3
  26. package/dist/cli/{chunk-q3wxxq39.js → chunk-a57yh0h9.js} +5 -5
  27. package/dist/cli/{chunk-713mngdq.js → chunk-ave1wcbr.js} +76 -10
  28. package/dist/cli/chunk-ave1wcbr.js.map +14 -0
  29. package/dist/cli/{chunk-2n4ps1b3.js → chunk-bqxaqn5p.js} +40 -22
  30. package/dist/cli/chunk-bqxaqn5p.js.map +15 -0
  31. package/dist/cli/{chunk-jnwj5kjj.js → chunk-bxwhtvw8.js} +20 -7
  32. package/dist/cli/{chunk-jnwj5kjj.js.map → chunk-bxwhtvw8.js.map} +3 -3
  33. package/dist/cli/chunk-ckrcm08h.js +324 -0
  34. package/dist/cli/chunk-ckrcm08h.js.map +14 -0
  35. package/dist/cli/{chunk-2f3ndkpw.js → chunk-desvm72z.js} +3 -3
  36. package/dist/cli/{chunk-gp84za0h.js → chunk-dhwtqnnd.js} +2 -2
  37. package/dist/cli/{chunk-4h8z9vv5.js → chunk-dpy8qdjf.js} +93 -47
  38. package/dist/cli/chunk-dpy8qdjf.js.map +17 -0
  39. package/dist/cli/{chunk-2f7n4qzt.js → chunk-dqhg54mr.js} +1 -1
  40. package/dist/cli/{chunk-0gz36akr.js → chunk-dv2z0rzf.js} +21 -5
  41. package/dist/cli/chunk-dv2z0rzf.js.map +12 -0
  42. package/dist/cli/{chunk-cm89scdv.js → chunk-es8h0v3j.js} +9 -11
  43. package/dist/cli/{chunk-cm89scdv.js.map → chunk-es8h0v3j.js.map} +3 -3
  44. package/dist/cli/{chunk-zhvd6adm.js → chunk-gfc5fqvf.js} +14 -14
  45. package/dist/cli/{chunk-nd1821sk.js → chunk-gxtg6ed2.js} +33 -19
  46. package/dist/cli/chunk-gxtg6ed2.js.map +10 -0
  47. package/dist/cli/{chunk-p804f3pe.js → chunk-hvcxng5f.js} +6 -6
  48. package/dist/cli/{chunk-ta27feev.js → chunk-j1x92mx9.js} +21 -2
  49. package/dist/cli/chunk-j1x92mx9.js.map +10 -0
  50. package/dist/cli/{chunk-rekqz4qg.js → chunk-k3gr1nm9.js} +19 -16
  51. package/dist/cli/chunk-k3gr1nm9.js.map +10 -0
  52. package/dist/cli/{chunk-1n228xqb.js → chunk-kdpzw38a.js} +2 -2
  53. package/dist/cli/{chunk-cy5bm0j0.js → chunk-ms9z66nz.js} +2 -2
  54. package/dist/cli/{chunk-cy5bm0j0.js.map → chunk-ms9z66nz.js.map} +2 -2
  55. package/dist/cli/{chunk-zf748avv.js → chunk-nbyvgnnm.js} +193 -286
  56. package/dist/cli/chunk-nbyvgnnm.js.map +36 -0
  57. package/dist/cli/{chunk-6bm0xpgd.js → chunk-s0bdnc7v.js} +2 -2
  58. package/dist/cli/{chunk-yxmk4pec.js → chunk-sc93apfs.js} +17 -16
  59. package/dist/cli/{chunk-yxmk4pec.js.map → chunk-sc93apfs.js.map} +3 -3
  60. package/dist/cli/{chunk-cmtcnh9f.js → chunk-tfycddbt.js} +2 -2
  61. package/dist/cli/{chunk-9xr4dgee.js → chunk-twy7psdz.js} +29 -16
  62. package/dist/cli/chunk-twy7psdz.js.map +10 -0
  63. package/dist/cli/{chunk-ntv4qrt7.js → chunk-y4msrwm9.js} +15 -15
  64. package/dist/cli/{chunk-yj17vs39.js → chunk-ydp3065p.js} +5 -5
  65. package/dist/cli/index.js +24 -19
  66. package/dist/cli/index.js.map +3 -3
  67. package/dist/types/ai/ask-context.d.ts +6 -2
  68. package/dist/types/ai/ask.d.ts +24 -5
  69. package/dist/types/ai/component-markdown.d.ts +2 -0
  70. package/dist/types/ai/openapi-components.d.ts +4 -1
  71. package/dist/types/analytics/clics.d.ts +37 -0
  72. package/dist/types/analytics/index.d.ts +2 -0
  73. package/dist/types/analytics/schema.d.ts +18 -0
  74. package/dist/types/astro/integration.d.ts +11 -4
  75. package/dist/types/astro/markdown-negotiation.d.ts +3 -0
  76. package/dist/types/astro/pages.d.ts +10 -0
  77. package/dist/types/cli/init/scaffold.d.ts +1 -1
  78. package/dist/types/components/content/api-field.d.ts +4 -1
  79. package/dist/types/components/content/api-page.d.ts +6 -1
  80. package/dist/types/components/openapi/code-samples.d.ts +29 -0
  81. package/dist/types/components/openapi/helpers.d.ts +22 -1
  82. package/dist/types/components/openapi/security.d.ts +10 -0
  83. package/dist/types/components/openapi/snippets.d.ts +60 -0
  84. package/dist/types/core/changelog-index.d.ts +8 -0
  85. package/dist/types/core/config-input.d.ts +37 -6
  86. package/dist/types/core/data.d.ts +4 -0
  87. package/dist/types/core/i18n-ui.d.ts +2 -0
  88. package/dist/types/core/links.d.ts +8 -0
  89. package/dist/types/core/powered-by.d.ts +12 -0
  90. package/dist/types/core/probe.d.ts +2 -0
  91. package/dist/types/core/project-graph.d.ts +6 -0
  92. package/dist/types/core/redirect-patterns.d.ts +11 -3
  93. package/dist/types/core/schema.d.ts +228 -3
  94. package/dist/types/core/sources/cache.d.ts +3 -0
  95. package/dist/types/core/sources/contentful-rich-text.d.ts +11 -2
  96. package/dist/types/core/sources/contentful.d.ts +17 -1
  97. package/dist/types/core/sources/lexical.d.ts +5 -0
  98. package/dist/types/core/sources/lower.d.ts +10 -2
  99. package/dist/types/core/sources/normalize.d.ts +20 -1
  100. package/dist/types/core/sources/notion.d.ts +8 -0
  101. package/dist/types/core/sources/remote.d.ts +37 -10
  102. package/dist/types/core/sources/sanity.d.ts +1 -1
  103. package/dist/types/core/types.d.ts +17 -0
  104. package/dist/types/deploy/adapter-output.d.ts +2 -1
  105. package/dist/types/deploy/adapters/cloudflare.d.ts +7 -5
  106. package/dist/types/deploy/adapters/node.d.ts +58 -23
  107. package/dist/types/deploy/adapters/registry.d.ts +10 -0
  108. package/dist/types/deploy/adapters/types.d.ts +3 -2
  109. package/dist/types/deploy/cloudflare-negotiation.d.ts +5 -2
  110. package/dist/types/deploy/node-headers.d.ts +12 -4
  111. package/dist/types/deploy/platforms/cloudflare.d.ts +10 -7
  112. package/dist/types/deploy/platforms/node.d.ts +3 -1
  113. package/dist/types/deploy/platforms/types.d.ts +18 -7
  114. package/dist/types/deploy/platforms/vercel.d.ts +21 -2
  115. package/dist/types/deploy/redirects.d.ts +17 -1
  116. package/dist/types/deploy/sitemap.d.ts +2 -1
  117. package/dist/types/deploy/vercel-negotiation.d.ts +44 -21
  118. package/dist/types/narration/build.d.ts +34 -12
  119. package/dist/types/narration/provider.d.ts +96 -8
  120. package/dist/types/openapi/checks.d.ts +27 -0
  121. package/dist/types/openapi/graphql.d.ts +6 -0
  122. package/dist/types/openapi/model.d.ts +15 -4
  123. package/dist/types/openapi/parse.d.ts +3 -0
  124. package/dist/types/openapi/references.d.ts +16 -0
  125. package/dist/types/openapi/sentence.d.ts +11 -1
  126. package/dist/types/ratelimit/index.d.ts +1 -1
  127. package/dist/types/ratelimit/schema.d.ts +8 -0
  128. package/dist/types/ratelimit/upstash.d.ts +39 -14
  129. package/dist/types/reference/graphql.d.ts +76 -1
  130. package/dist/types/reference/index.d.ts +1 -1
  131. package/dist/types/reference/openapi.d.ts +2 -2
  132. package/dist/types/reference/options.d.ts +37 -1
  133. package/dist/types/reference/schema.d.ts +84 -0
  134. package/dist/types/search/adapters/registry.d.ts +3 -0
  135. package/dist/types/search/adapters/typesense.d.ts +14 -4
  136. package/dist/types/search/build.d.ts +3 -1
  137. package/dist/types/search/orama-index.d.ts +9 -9
  138. package/dist/types/search/sync/algolia.d.ts +5 -2
  139. package/dist/types/search/sync/index.d.ts +3 -1
  140. package/dist/types/search/sync/typesense.d.ts +11 -7
  141. package/dist/types/seo/jsonld.d.ts +11 -6
  142. package/dist/types/sources/contentful.d.ts +16 -0
  143. package/dist/types/sources/github-releases.d.ts +8 -1
  144. package/dist/types/sources/notion.d.ts +8 -0
  145. package/dist/types/sources/payload.d.ts +2 -0
  146. package/dist/types/sources/registry.d.ts +63 -0
  147. package/dist/types/sources/shared.d.ts +3 -0
  148. package/dist/types/sources/strapi.d.ts +2 -0
  149. package/docs/01-quickstart.mdx +4 -2
  150. package/docs/02-deployment.mdx +59 -13
  151. package/docs/03-upgrading.mdx +2 -1
  152. package/docs/04-migrating.mdx +9 -9
  153. package/docs/08-faq.mdx +12 -12
  154. package/docs/advanced/changelog.mdx +30 -2
  155. package/docs/advanced/custom-pages.mdx +1 -1
  156. package/docs/advanced/skills.mdx +1 -1
  157. package/docs/cli/audit.mdx +30 -7
  158. package/docs/cli/doctor.mdx +6 -2
  159. package/docs/cli/evals.mdx +4 -4
  160. package/docs/cli/translate.mdx +1 -0
  161. package/docs/cli/validate.mdx +4 -3
  162. package/docs/configuration/analytics.mdx +19 -1
  163. package/docs/configuration/assistant.mdx +6 -2
  164. package/docs/configuration/customization.mdx +4 -2
  165. package/docs/configuration/export.mdx +5 -3
  166. package/docs/configuration/index.mdx +4 -2
  167. package/docs/configuration/narration.mdx +43 -6
  168. package/docs/configuration/rate-limiting.mdx +23 -2
  169. package/docs/configuration/search.mdx +37 -14
  170. package/docs/content/components.mdx +2 -2
  171. package/docs/content/frontmatter.mdx +5 -3
  172. package/docs/content/i18n.mdx +2 -2
  173. package/docs/content/includes.mdx +1 -1
  174. package/docs/content/navigation.mdx +8 -6
  175. package/docs/content/sources/contentful.mdx +9 -4
  176. package/docs/content/sources/custom.mdx +7 -3
  177. package/docs/content/sources/github-releases.mdx +12 -2
  178. package/docs/content/sources/index.mdx +3 -3
  179. package/docs/content/sources/notion.mdx +17 -2
  180. package/docs/content/sources/obsidian.mdx +3 -1
  181. package/docs/content/sources/payload.mdx +5 -2
  182. package/docs/content/sources/remote-mdx.mdx +6 -0
  183. package/docs/content/sources/sanity.mdx +4 -2
  184. package/docs/content/sources/strapi.mdx +5 -2
  185. package/docs/content/syntax.mdx +2 -2
  186. package/docs/discoverability/agent-discovery.mdx +1 -1
  187. package/docs/discoverability/index.mdx +1 -1
  188. package/docs/discoverability/json-api.mdx +2 -0
  189. package/docs/discoverability/llms-txt.mdx +1 -1
  190. package/docs/discoverability/markdown.mdx +3 -1
  191. package/docs/discoverability/mcp.mdx +6 -0
  192. package/docs/discoverability/sitemap-and-robots.mdx +1 -1
  193. package/docs/discoverability/structured-data.mdx +8 -6
  194. package/docs/references/api-pages.mdx +1 -1
  195. package/docs/references/asyncapi.mdx +5 -1
  196. package/docs/references/graphql.mdx +15 -1
  197. package/docs/references/openapi.mdx +64 -8
  198. package/package.json +9 -9
  199. package/skills/blume/SKILL.md +1 -1
  200. package/skills/blume-migrate/SKILL.md +2 -2
  201. package/skills/blume-migrate/assets/{oxfmt@0.67.0.patch → oxfmt@0.71.0.patch} +7 -7
  202. package/skills/blume-migrate/references/docusaurus.md +4 -4
  203. package/skills/blume-migrate/references/fumadocs.md +17 -13
  204. package/skills/blume-migrate/references/monorepo.md +5 -5
  205. package/skills/blume-migrate/references/nextra.md +8 -5
  206. package/skills/blume-migrate/references/starlight.md +9 -7
  207. package/src/ai/ask-context.ts +33 -3
  208. package/src/ai/ask-limits.ts +13 -0
  209. package/src/ai/ask.ts +24 -6
  210. package/src/ai/changelog-markdown.ts +11 -6
  211. package/src/ai/component-markdown.ts +1 -1
  212. package/src/ai/cors.ts +12 -3
  213. package/src/ai/mcp/query.ts +14 -6
  214. package/src/ai/openapi-components.ts +28 -4
  215. package/src/ai/relative-links.ts +4 -38
  216. package/src/analytics/clics.ts +58 -0
  217. package/src/analytics/head.ts +5 -0
  218. package/src/analytics/index.ts +2 -0
  219. package/src/analytics/schema.ts +2 -0
  220. package/src/astro/generate.ts +91 -56
  221. package/src/astro/integration.ts +72 -22
  222. package/src/astro/markdown-negotiation.ts +3 -0
  223. package/src/astro/module-types.ts +3 -1
  224. package/src/astro/pages.ts +23 -0
  225. package/src/astro/render-deps.ts +10 -5
  226. package/src/astro/runtime-deps.ts +23 -2
  227. package/src/astro/templates.ts +129 -81
  228. package/src/audit/catalog.ts +19 -3
  229. package/src/audit/checks/links.ts +14 -1
  230. package/src/audit/checks/network.ts +110 -6
  231. package/src/audit/checks/redirects.ts +6 -4
  232. package/src/audit/checks/robots.ts +58 -2
  233. package/src/audit/run.ts +3 -0
  234. package/src/audit/types.ts +2 -0
  235. package/src/audit/url.ts +14 -5
  236. package/src/blume-modules.d.ts +5 -1
  237. package/src/cli/args.ts +51 -0
  238. package/src/cli/build-failure.ts +23 -0
  239. package/src/cli/commands/audit.ts +10 -1
  240. package/src/cli/commands/dev.ts +18 -2
  241. package/src/cli/commands/doctor.ts +45 -12
  242. package/src/cli/commands/eval.ts +36 -4
  243. package/src/cli/commands/init.ts +6 -4
  244. package/src/cli/commands/validate.ts +10 -1
  245. package/src/cli/init/scaffold.ts +9 -6
  246. package/src/cli/prepare.ts +6 -3
  247. package/src/cli/required-secrets.ts +3 -2
  248. package/src/components/content/ApiPlayground.astro +6 -2
  249. package/src/components/content/api-field.ts +4 -1
  250. package/src/components/content/api-page.ts +31 -7
  251. package/src/components/content/mermaid-element.ts +15 -1
  252. package/src/components/content/view-switcher.ts +7 -4
  253. package/src/components/islands/hooks.ts +27 -1
  254. package/src/components/layout/NavTree.astro +49 -5
  255. package/src/components/layout/NavTreeScript.astro +18 -0
  256. package/src/components/layout/PageActions.astro +139 -24
  257. package/src/components/layout/PageLayout.astro +13 -3
  258. package/src/components/layout/RootLayout.astro +27 -14
  259. package/src/components/layout/Search.astro +17 -3
  260. package/src/components/layout/TableOfContents.astro +24 -17
  261. package/src/components/layout/analytics-client.ts +8 -8
  262. package/src/components/layout/head-scripts.ts +6 -1
  263. package/src/components/layout/search/algolia.ts +10 -1
  264. package/src/components/layout/search/analytics.ts +42 -14
  265. package/src/components/layout/search/endpoint.ts +48 -5
  266. package/src/components/layout/search/pagefind.ts +114 -5
  267. package/src/components/layout/search/types.ts +12 -0
  268. package/src/components/layout/search/typesense.ts +18 -6
  269. package/src/components/openapi/AsyncApiOperation.astro +14 -7
  270. package/src/components/openapi/Authorization.astro +10 -0
  271. package/src/components/openapi/GraphqlOperation.astro +5 -0
  272. package/src/components/openapi/Operation.astro +24 -1
  273. package/src/components/openapi/ParametersTable.astro +8 -4
  274. package/src/components/openapi/Playground.astro +7 -1
  275. package/src/components/openapi/RequestBody.astro +19 -2
  276. package/src/components/openapi/Responses.astro +1 -0
  277. package/src/components/openapi/SchemaTable.astro +15 -3
  278. package/src/components/openapi/async-snippets.ts +61 -5
  279. package/src/components/openapi/async.ts +123 -29
  280. package/src/components/openapi/graphql-helpers.ts +35 -12
  281. package/src/components/openapi/helpers.ts +36 -2
  282. package/src/components/openapi/message-model.ts +54 -11
  283. package/src/components/openapi/message.ts +14 -4
  284. package/src/components/openapi/operation-model.ts +16 -7
  285. package/src/components/openapi/playground-client.ts +77 -58
  286. package/src/components/openapi/playground-schema.ts +1 -1
  287. package/src/components/openapi/schema-tree.ts +44 -14
  288. package/src/components/openapi/security.ts +10 -0
  289. package/src/components/openapi/snippets.ts +5 -3
  290. package/src/components/openapi/validate-json.ts +4 -3
  291. package/src/components/powered-by-middleware.ts +33 -0
  292. package/src/core/changelog-index.ts +31 -1
  293. package/src/core/component-diagnostics.ts +53 -0
  294. package/src/core/component-overrides.ts +32 -0
  295. package/src/core/config-input.ts +42 -6
  296. package/src/core/data.ts +2 -0
  297. package/src/core/diagnostics.ts +5 -0
  298. package/src/core/i18n-ui.ts +2 -0
  299. package/src/core/includes.ts +1 -1
  300. package/src/core/links.ts +47 -17
  301. package/src/core/powered-by.ts +12 -0
  302. package/src/core/probe.ts +26 -6
  303. package/src/core/project-graph.ts +59 -18
  304. package/src/core/redirect-patterns.ts +36 -7
  305. package/src/core/schema.ts +26 -6
  306. package/src/core/sources/cache.ts +6 -0
  307. package/src/core/sources/contentful-rich-text.ts +29 -6
  308. package/src/core/sources/contentful.ts +135 -25
  309. package/src/core/sources/filesystem.ts +26 -4
  310. package/src/core/sources/github-releases.ts +12 -1
  311. package/src/core/sources/lexical.ts +15 -10
  312. package/src/core/sources/lower.ts +19 -3
  313. package/src/core/sources/mdx-remote.ts +62 -10
  314. package/src/core/sources/normalize.ts +227 -41
  315. package/src/core/sources/notion.ts +34 -10
  316. package/src/core/sources/obsidian.ts +16 -3
  317. package/src/core/sources/payload.ts +38 -15
  318. package/src/core/sources/remote.ts +72 -15
  319. package/src/core/sources/resolve.ts +11 -1
  320. package/src/core/sources/sanity.ts +43 -25
  321. package/src/core/sources/strapi.ts +2 -2
  322. package/src/core/types.ts +18 -0
  323. package/src/core/ui-packs/ar.ts +1 -0
  324. package/src/core/ui-packs/bg.ts +1 -0
  325. package/src/core/ui-packs/bn.ts +1 -0
  326. package/src/core/ui-packs/ca.ts +1 -0
  327. package/src/core/ui-packs/cs.ts +1 -0
  328. package/src/core/ui-packs/da.ts +1 -0
  329. package/src/core/ui-packs/de.ts +1 -0
  330. package/src/core/ui-packs/el.ts +1 -0
  331. package/src/core/ui-packs/es.ts +1 -0
  332. package/src/core/ui-packs/fa.ts +1 -0
  333. package/src/core/ui-packs/fi.ts +1 -0
  334. package/src/core/ui-packs/fr.ts +1 -0
  335. package/src/core/ui-packs/he.ts +1 -0
  336. package/src/core/ui-packs/hi.ts +1 -0
  337. package/src/core/ui-packs/hr.ts +1 -0
  338. package/src/core/ui-packs/hu.ts +1 -0
  339. package/src/core/ui-packs/id.ts +1 -0
  340. package/src/core/ui-packs/it.ts +1 -0
  341. package/src/core/ui-packs/ja.ts +1 -0
  342. package/src/core/ui-packs/ko.ts +1 -0
  343. package/src/core/ui-packs/nl.ts +1 -0
  344. package/src/core/ui-packs/no.ts +1 -0
  345. package/src/core/ui-packs/pl.ts +1 -0
  346. package/src/core/ui-packs/pt-br.ts +1 -0
  347. package/src/core/ui-packs/pt.ts +1 -0
  348. package/src/core/ui-packs/ro.ts +1 -0
  349. package/src/core/ui-packs/ru.ts +1 -0
  350. package/src/core/ui-packs/sk.ts +1 -0
  351. package/src/core/ui-packs/sr.ts +1 -0
  352. package/src/core/ui-packs/sv.ts +1 -0
  353. package/src/core/ui-packs/th.ts +1 -0
  354. package/src/core/ui-packs/tr.ts +1 -0
  355. package/src/core/ui-packs/uk.ts +1 -0
  356. package/src/core/ui-packs/vi.ts +1 -0
  357. package/src/core/ui-packs/zh-tw.ts +1 -0
  358. package/src/core/ui-packs/zh.ts +1 -0
  359. package/src/deploy/adapter-output.ts +7 -2
  360. package/src/deploy/adapters/cloudflare.ts +7 -5
  361. package/src/deploy/adapters/node.ts +47 -9
  362. package/src/deploy/adapters/types.ts +7 -3
  363. package/src/deploy/artifacts.ts +2 -1
  364. package/src/deploy/cloudflare-negotiation.ts +36 -7
  365. package/src/deploy/headers.ts +15 -1
  366. package/src/deploy/node-headers.ts +32 -20
  367. package/src/deploy/platforms/cloudflare.ts +14 -9
  368. package/src/deploy/platforms/netlify.ts +2 -1
  369. package/src/deploy/platforms/node.ts +12 -2
  370. package/src/deploy/platforms/types.ts +21 -7
  371. package/src/deploy/platforms/vercel.ts +115 -17
  372. package/src/deploy/redirects.ts +66 -6
  373. package/src/deploy/rss.ts +3 -1
  374. package/src/deploy/sitemap.ts +39 -5
  375. package/src/deploy/vercel-negotiation.ts +325 -103
  376. package/src/eval/agents.ts +18 -1
  377. package/src/eval/prompts.ts +6 -3
  378. package/src/eval/report.ts +34 -13
  379. package/src/eval/run.ts +5 -0
  380. package/src/markdown/base-links.ts +15 -14
  381. package/src/markdown/package-commands.ts +9 -8
  382. package/src/markdown/relative-links.ts +3 -2
  383. package/src/narration/build.ts +218 -40
  384. package/src/narration/provider.ts +89 -9
  385. package/src/openapi/checks.ts +273 -0
  386. package/src/openapi/graphql-build.ts +21 -0
  387. package/src/openapi/graphql.ts +11 -0
  388. package/src/openapi/model.ts +15 -3
  389. package/src/openapi/parse.ts +127 -2
  390. package/src/openapi/references.ts +43 -3
  391. package/src/openapi/render-mdx.ts +68 -8
  392. package/src/openapi/sentence.ts +29 -2
  393. package/src/openapi/source.ts +39 -14
  394. package/src/ratelimit/index.ts +1 -1
  395. package/src/ratelimit/runtime.ts +37 -19
  396. package/src/ratelimit/upstash.ts +40 -10
  397. package/src/reference/graphql.ts +15 -1
  398. package/src/reference/index.ts +1 -0
  399. package/src/reference/openapi.ts +2 -2
  400. package/src/reference/options.ts +29 -1
  401. package/src/registry/eject.ts +8 -2
  402. package/src/search/adapters/locale-scope.ts +30 -0
  403. package/src/search/adapters/typesense.ts +14 -4
  404. package/src/search/build.ts +21 -3
  405. package/src/search/orama-index.ts +163 -31
  406. package/src/search/source-pages.ts +29 -0
  407. package/src/search/sync/algolia.ts +99 -9
  408. package/src/search/sync/index.ts +52 -13
  409. package/src/search/sync/typesense.ts +72 -27
  410. package/src/seo/jsonld.ts +38 -20
  411. package/src/sources/contentful.ts +12 -0
  412. package/src/sources/github-releases.ts +7 -1
  413. package/src/sources/notion.ts +7 -0
  414. package/src/sources/shared.ts +3 -0
  415. package/src/theme/color.ts +9 -10
  416. package/src/theme/entry.ts +43 -6
  417. package/src/translate/validate.ts +23 -1
  418. package/dist/cli/chunk-0gz36akr.js.map +0 -12
  419. package/dist/cli/chunk-2n4ps1b3.js.map +0 -15
  420. package/dist/cli/chunk-43zcf4gz.js.map +0 -36
  421. package/dist/cli/chunk-4ejxbzee.js +0 -39
  422. package/dist/cli/chunk-4ejxbzee.js.map +0 -10
  423. package/dist/cli/chunk-4h8z9vv5.js.map +0 -16
  424. package/dist/cli/chunk-713mngdq.js.map +0 -14
  425. package/dist/cli/chunk-9xr4dgee.js.map +0 -10
  426. package/dist/cli/chunk-amrb6hgh.js +0 -145
  427. package/dist/cli/chunk-amrb6hgh.js.map +0 -11
  428. package/dist/cli/chunk-nd1821sk.js.map +0 -10
  429. package/dist/cli/chunk-rekqz4qg.js.map +0 -10
  430. package/dist/cli/chunk-ta27feev.js.map +0 -10
  431. package/dist/cli/chunk-xkz5xbtv.js.map +0 -213
  432. package/dist/cli/chunk-y5h4r2y0.js.map +0 -10
  433. package/dist/cli/chunk-zf748avv.js.map +0 -37
  434. /package/dist/cli/{chunk-9geymqns.js.map → chunk-0w6hs479.js.map} +0 -0
  435. /package/dist/cli/{chunk-y4ykbark.js.map → chunk-1f882900.js.map} +0 -0
  436. /package/dist/cli/{chunk-py62fewh.js.map → chunk-53jqw8z0.js.map} +0 -0
  437. /package/dist/cli/{chunk-5wj2qwn2.js.map → chunk-58bt3hag.js.map} +0 -0
  438. /package/dist/cli/{chunk-e7vvk5ya.js.map → chunk-5cmts6dv.js.map} +0 -0
  439. /package/dist/cli/{chunk-g2z9x01v.js.map → chunk-9qzgg6mj.js.map} +0 -0
  440. /package/dist/cli/{chunk-q3wxxq39.js.map → chunk-a57yh0h9.js.map} +0 -0
  441. /package/dist/cli/{chunk-2f3ndkpw.js.map → chunk-desvm72z.js.map} +0 -0
  442. /package/dist/cli/{chunk-gp84za0h.js.map → chunk-dhwtqnnd.js.map} +0 -0
  443. /package/dist/cli/{chunk-2f7n4qzt.js.map → chunk-dqhg54mr.js.map} +0 -0
  444. /package/dist/cli/{chunk-zhvd6adm.js.map → chunk-gfc5fqvf.js.map} +0 -0
  445. /package/dist/cli/{chunk-p804f3pe.js.map → chunk-hvcxng5f.js.map} +0 -0
  446. /package/dist/cli/{chunk-1n228xqb.js.map → chunk-kdpzw38a.js.map} +0 -0
  447. /package/dist/cli/{chunk-6bm0xpgd.js.map → chunk-s0bdnc7v.js.map} +0 -0
  448. /package/dist/cli/{chunk-cmtcnh9f.js.map → chunk-tfycddbt.js.map} +0 -0
  449. /package/dist/cli/{chunk-ntv4qrt7.js.map → chunk-y4msrwm9.js.map} +0 -0
  450. /package/dist/cli/{chunk-yj17vs39.js.map → chunk-ydp3065p.js.map} +0 -0
@@ -50,6 +50,7 @@ export const uk: UIStringsOverride = {
50
50
  banner: { dismiss: "Закрити оголошення" },
51
51
  changelog: {
52
52
  description: "Оновлення продукту та примітки до випусків.",
53
+ empty: "У журналі змін поки немає записів.",
53
54
  title: "Журнал змін",
54
55
  },
55
56
  consent: {
@@ -49,6 +49,7 @@ export const vi: UIStringsOverride = {
49
49
  banner: { dismiss: "Đóng thông báo" },
50
50
  changelog: {
51
51
  description: "Cập nhật sản phẩm và ghi chú phát hành.",
52
+ empty: "Chưa có mục nào trong nhật ký thay đổi.",
52
53
  title: "Nhật ký thay đổi",
53
54
  },
54
55
  consent: {
@@ -48,6 +48,7 @@ export const zhTW: UIStringsOverride = {
48
48
  banner: { dismiss: "關閉公告" },
49
49
  changelog: {
50
50
  description: "產品更新與版本說明。",
51
+ empty: "尚無更新日誌項目。",
51
52
  title: "更新日誌",
52
53
  },
53
54
  consent: {
@@ -48,6 +48,7 @@ export const zh: UIStringsOverride = {
48
48
  banner: { dismiss: "关闭公告" },
49
49
  changelog: {
50
50
  description: "产品更新与版本说明。",
51
+ empty: "暂无更新日志条目。",
51
52
  title: "更新日志",
52
53
  },
53
54
  consent: {
@@ -3,6 +3,7 @@ import { cp, mkdir, rm } from "node:fs/promises";
3
3
 
4
4
  import { dirname, join } from "pathe";
5
5
 
6
+ import { normalizeBasePath } from "../core/base-path.ts";
6
7
  import type { ResolvedConfig } from "../core/schema.ts";
7
8
  import type { ProjectContext } from "../core/types.ts";
8
9
  import type { ResolvedDeployment } from "./adapters/registry.ts";
@@ -30,7 +31,8 @@ export const deployOutputDir = (
30
31
  * served. A static build serves `dist/` wherever it lands; a server build
31
32
  * serves what its platform declares — the Build Output tree's `static/` half
32
33
  * on Vercel, Astro's `build.client` dir (`dist/client/`) on Node and
33
- * Cloudflare, `dist/` on Netlify.
34
+ * Cloudflare, `dist/` on Netlify. On Vercel and Cloudflare that's its
35
+ * `<base>/` directory under `deployment.base`, where the files land.
34
36
  */
35
37
  export const deployStaticDir = (
36
38
  config: ResolvedConfig,
@@ -38,7 +40,10 @@ export const deployStaticDir = (
38
40
  ): string => {
39
41
  const { deployment } = config;
40
42
  return deployment.options.output === "server"
41
- ? deployPlatform(deployment).serverStaticDir(context)
43
+ ? deployPlatform(deployment).serverStaticDir(
44
+ context,
45
+ normalizeBasePath(deployment.options.base)
46
+ )
42
47
  : distDir(context);
43
48
  };
44
49
 
@@ -23,11 +23,13 @@ export const cloudflareAdapterSchema = adapterDescriptorSchema(
23
23
  );
24
24
 
25
25
  /**
26
- * Cloudflare Workers and Pages. A server build emits a Worker into
27
- * `dist/server` (with a generated wrapper that answers `Accept:
28
- * text/markdown`) and serves `dist/client` through the ASSETS binding.
29
- * `@astrojs/cloudflare` is an optional peer: install it in the project
30
- * (`npm install @astrojs/cloudflare`).
26
+ * Cloudflare. A server build targets Workers only (`@astrojs/cloudflare`
27
+ * dropped Pages in v13): it emits a Worker into `dist/server` (with a
28
+ * generated wrapper that answers `Accept: text/markdown`) and serves
29
+ * `dist/client` through the ASSETS binding. A static build
30
+ * (`output: "static"`) never loads the adapter, and its `dist/` deploys to
31
+ * Pages or to Workers static assets. `@astrojs/cloudflare` is an optional
32
+ * peer: install it in the project (`npm install @astrojs/cloudflare`).
31
33
  */
32
34
  export const cloudflare = (
33
35
  options: CloudflareOptions = {}
@@ -1,3 +1,5 @@
1
+ import { z } from "zod";
2
+
1
3
  import { adapterDescriptorSchema } from "../../core/adapter.ts";
2
4
  import type { DeployAdapter, DeployOptions } from "./types.ts";
3
5
  import { deployOptionsSchema, serverRuntimeDeps } from "./types.ts";
@@ -6,18 +8,54 @@ import { deployOptionsSchema, serverRuntimeDeps } from "./types.ts";
6
8
  export const NODE_ADAPTER_PACKAGE = "@astrojs/node";
7
9
 
8
10
  /**
9
- * Options for {@link node}: `output`, `site`, and `base`, plus any option of
10
- * `@astrojs/node` forwarded verbatim (`mode`, `staticHeaders`, …). Blume
11
- * defaults `mode` to `"standalone"`; an option you pass wins. `host` and
12
- * `port` are forwarded too, but `@astrojs/node` replaces both with Astro's
13
- * own server settings, so the standalone server listens on `localhost:4321`
14
- * unless the `HOST` and `PORT` environment variables are set when it starts.
11
+ * A host the server trusts forwarded headers for, in Astro's
12
+ * `security.allowedDomains` shape: each field set must match.
15
13
  */
16
- export type NodeOptions = DeployOptions;
14
+ // oxlint-disable-next-line typescript/consistent-type-definitions -- interface loses the implicit index signature a deploy option's JsonValue needs
15
+ export type NodeAllowedDomain = {
16
+ /** The host, like `docs.example.com`; `*.example.com` matches subdomains. */
17
+ hostname?: string;
18
+ /** The port, as a string. */
19
+ port?: string;
20
+ /** The scheme, like `https`. */
21
+ protocol?: string;
22
+ };
17
23
 
18
- export type NodeAdapter = DeployAdapter<"node">;
24
+ /**
25
+ * Options for {@link node}: `output`, `site`, `base`, and `allowedDomains`,
26
+ * plus any option of `@astrojs/node` forwarded verbatim (`mode`,
27
+ * `staticHeaders`, …). Blume defaults `mode` to `"standalone"`; an option you
28
+ * pass wins. `host` and `port` are forwarded too, but `@astrojs/node`
29
+ * replaces both with Astro's own server settings, so the standalone server
30
+ * listens on `localhost:4321` unless the `HOST` and `PORT` environment
31
+ * variables are set when it starts.
32
+ */
33
+ export type NodeOptions = DeployOptions & {
34
+ /**
35
+ * The hosts a reverse proxy in front of the server answers for, set as
36
+ * Astro's `security.allowedDomains`. A request whose `Host` (or
37
+ * `X-Forwarded-Host`) matches one is trusted to name the reader in
38
+ * `X-Forwarded-For`, which the rate limit counts by; otherwise every
39
+ * reader counts as the proxy's address. Only list them when a proxy sets
40
+ * that header on every request, since a reader could otherwise send their
41
+ * own.
42
+ */
43
+ allowedDomains?: NodeAllowedDomain[];
44
+ };
19
45
 
20
- export const nodeOptionsSchema = deployOptionsSchema;
46
+ export type NodeAdapter = DeployAdapter<"node", NodeOptions>;
47
+
48
+ export const nodeOptionsSchema = deployOptionsSchema.extend({
49
+ allowedDomains: z
50
+ .array(
51
+ z.strictObject({
52
+ hostname: z.string().min(1).optional(),
53
+ port: z.string().min(1).optional(),
54
+ protocol: z.string().min(1).optional(),
55
+ })
56
+ )
57
+ .optional(),
58
+ });
21
59
 
22
60
  export const nodeAdapterSchema = adapterDescriptorSchema(
23
61
  "node",
@@ -84,13 +84,17 @@ const NAMED_OPTIONS = new Set(["base", "output", "site"]);
84
84
 
85
85
  /**
86
86
  * The options Blume does not name — what reaches the `@astrojs/*` adapter's
87
- * constructor verbatim.
87
+ * constructor verbatim. `configOptions` are the ones a host adapter names
88
+ * itself, for the Astro config rather than the constructor.
88
89
  */
89
90
  export const deployPassthrough = (
90
- options: DeployOptions
91
+ options: DeployOptions,
92
+ configOptions: readonly string[] = []
91
93
  ): Record<string, JsonValue> =>
92
94
  Object.fromEntries(
93
- Object.entries(options).filter(([key]) => !NAMED_OPTIONS.has(key))
95
+ Object.entries(options).filter(
96
+ ([key]) => !(NAMED_OPTIONS.has(key) || configOptions.includes(key))
97
+ )
94
98
  );
95
99
 
96
100
  /**
@@ -445,7 +445,8 @@ export const publishBuildArtifacts = async (
445
445
  await buildNarration(project, distDir, logger);
446
446
 
447
447
  // Upload the index to a hosted provider (Algolia, Orama Cloud, Typesense).
448
- // Skipped with a warning when its admin key isn't configured.
448
+ // Skipped with a warning when its admin key isn't configured; with the key
449
+ // set, a failed sync fails the build.
449
450
  await syncSearchProvider(project, {
450
451
  start: logger.info.bind(logger),
451
452
  success: logger.info.bind(logger),
@@ -40,8 +40,9 @@
40
40
  * Cloudflare does not apply `_headers` to worker-first routes, so the wrapper
41
41
  * also re-stamps what the static layer would otherwise add on the routes it
42
42
  * takes over: the homepage agent-discovery `Link` header, the Markdown
43
- * `charset=utf-8`, and the sandbox on the SVGs a content source downloaded
44
- * (see `deploy/headers.ts`). The raw `.md`/`.mdx` URLs are
43
+ * `charset=utf-8`, the sandbox on the SVGs a content source downloaded (see
44
+ * `deploy/headers.ts`), and `X-Powered-By` unless `poweredBy` is `false`. The
45
+ * raw `.md`/`.mdx` URLs are
45
46
  * exempted from worker-first routing with negative rules, keeping their
46
47
  * `_headers` treatment and their zero-Worker serving path.
47
48
  *
@@ -85,6 +86,7 @@ import {
85
86
  OPENAPI_PATH,
86
87
  } from "../ai/api/paths.ts";
87
88
  import { normalizeBasePath, normalizePath } from "../core/base-path.ts";
89
+ import { POWERED_BY_HEADERS } from "../core/powered-by.ts";
88
90
  import { compileRedirects, isPatternPath } from "../core/redirect-patterns.ts";
89
91
  import { CONTENT_ASSETS_ROOT, SVG_ASSET_HEADERS } from "./headers.ts";
90
92
  import type { NotFoundVariants } from "./vercel-negotiation.ts";
@@ -244,6 +246,8 @@ export interface NegotiationWorkerOptions {
244
246
  base?: string;
245
247
  /** Homepage agent-discovery `Link` header (see `ai/link-headers.ts`). */
246
248
  homeLinkHeader?: string | null;
249
+ /** Whether responses identify Blume with `X-Powered-By`. Defaults to `true`. */
250
+ poweredBy?: boolean;
247
251
  /** Estimated token count of the homepage Markdown mirror. */
248
252
  homeTokens?: number;
249
253
  /** Configured redirects the wrapper answers with their exact status. */
@@ -277,6 +281,9 @@ export const buildNegotiationWorker = (
277
281
  const binding = JSON.stringify(options.assetsBinding);
278
282
  const prefix = JSON.stringify(encodeURI(normalizeBasePath(options.base)));
279
283
  const homeLinkHeader = JSON.stringify(options.homeLinkHeader ?? null);
284
+ const poweredBy = JSON.stringify(
285
+ options.poweredBy === false ? {} : POWERED_BY_HEADERS
286
+ );
280
287
  const homeTokens = JSON.stringify(
281
288
  options.homeTokens === undefined ? null : String(options.homeTokens)
282
289
  );
@@ -331,6 +338,7 @@ const PATTERN_REDIRECTS = ${patternRedirects}.map(
331
338
  );
332
339
  const NOT_FOUND = ${notFound};
333
340
  const SVG_ASSET_HEADERS = ${JSON.stringify(SVG_ASSET_HEADERS)};
341
+ const POWERED_BY = ${poweredBy};
334
342
 
335
343
  // An SVG a content source downloaded: a document that could run script as the
336
344
  // docs site if opened directly, so it goes out sandboxed. Matched on the
@@ -542,12 +550,30 @@ const isHomePath = (pathname) => {
542
550
  return rest === "" || rest === "/";
543
551
  };
544
552
 
553
+ // Blume's X-Powered-By, on every answer the wrapper gives unless the
554
+ // response already names its own (the project's middleware, say).
555
+ const identify = (headers) => {
556
+ for (const [name, value] of Object.entries(POWERED_BY)) {
557
+ if (!headers.has(name)) {
558
+ headers.set(name, value);
559
+ }
560
+ }
561
+ };
562
+
545
563
  const withHeaders = (response, apply) => {
546
564
  const patched = new Response(response.body, response);
547
565
  apply(patched.headers);
566
+ identify(patched.headers);
548
567
  return patched;
549
568
  };
550
569
 
570
+ // A response that already carries the header (Blume's runtime middleware sets
571
+ // it on every page Astro renders) goes out as it is.
572
+ const withPoweredBy = (response) =>
573
+ Object.keys(POWERED_BY).every((name) => response.headers.has(name))
574
+ ? response
575
+ : withHeaders(response, () => {});
576
+
551
577
  export default {
552
578
  async fetch(request, env, context) {
553
579
  const url = new URL(request.url);
@@ -556,7 +582,10 @@ export default {
556
582
  const redirect = redirectFor(url.pathname);
557
583
  if (redirect !== null) {
558
584
  return new Response(null, {
559
- headers: { location: redirectLocation(redirect[0], url.search) },
585
+ headers: {
586
+ ...POWERED_BY,
587
+ location: redirectLocation(redirect[0], url.search),
588
+ },
560
589
  status: redirect[1],
561
590
  });
562
591
  }
@@ -568,7 +597,7 @@ export default {
568
597
  });
569
598
  }
570
599
  if (request.method !== "GET" && request.method !== "HEAD") {
571
- return server.fetch(request, env, context);
600
+ return withPoweredBy(await server.fetch(request, env, context));
572
601
  }
573
602
  const assets = env[ASSETS_BINDING];
574
603
  // The request goes through untouched, so a conditional revalidation
@@ -577,7 +606,7 @@ export default {
577
606
  if (assets !== undefined && isPageJson(url.pathname)) {
578
607
  const asset = await assets.fetch(request);
579
608
  if (asset.status !== 404) {
580
- return asset;
609
+ return withPoweredBy(asset);
581
610
  }
582
611
  }
583
612
  const variant = markdownVariantUrl(url.pathname + url.search);
@@ -608,10 +637,10 @@ export default {
608
637
  const response = await server.fetch(request, env, context);
609
638
  const twin = await notFoundTwin(request, url, response, assets);
610
639
  if (twin !== null) {
611
- return twin;
640
+ return withPoweredBy(twin);
612
641
  }
613
642
  if (variant === null && !(home && HOME_LINK_HEADER !== null)) {
614
- return response;
643
+ return withPoweredBy(response);
615
644
  }
616
645
  return withHeaders(response, (headers) => {
617
646
  if (variant !== null) {
@@ -10,6 +10,7 @@ import {
10
10
  SIGNATURES_DIRECTORY_TYPE,
11
11
  } from "../ai/web-bot-auth.ts";
12
12
  import { normalizeBasePath } from "../core/base-path.ts";
13
+ import { POWERED_BY_HEADERS } from "../core/powered-by.ts";
13
14
  import type { ResolvedConfig } from "../core/schema.ts";
14
15
 
15
16
  /**
@@ -98,6 +99,13 @@ export const headerRules = (
98
99
  path: `${deployBase}/*.${rule.ext}`,
99
100
  value: rule.contentType,
100
101
  }));
102
+ // One rule for the whole site: `/*` matches the homepage too, and a host
103
+ // that joins the values of overlapping rules would send a second one twice.
104
+ if (config.poweredBy) {
105
+ for (const [name, value] of Object.entries(POWERED_BY_HEADERS)) {
106
+ rules.push({ name, path: `${deployBase}/*`, value });
107
+ }
108
+ }
101
109
  if (homeLinkHeader) {
102
110
  rules.push({ name: "Link", path: `${deployBase}/`, value: homeLinkHeader });
103
111
  }
@@ -212,9 +220,15 @@ export const escapeVercelSource = (path: string): string =>
212
220
  * A `_headers` path as a `vercel.json` `source`, which `path-to-regexp` reads:
213
221
  * the literal parts are escaped, and a `*` glob becomes the `(.*)` group that
214
222
  * spans path segments as the glob does. An exact directory path (the homepage
215
- * rule's `/docs/`) drops its trailing slash, the URL Vercel serves it at.
223
+ * rule's `/docs/`) drops its trailing slash, the URL Vercel serves it at. A
224
+ * whole-directory glob (`/docs/*`) becomes an optional group,
225
+ * `/docs/:path(.*)?`, so it matches that bare URL as well as every path
226
+ * beneath it.
216
227
  */
217
228
  const vercelSource = (path: string): string => {
229
+ if (path.endsWith("/*")) {
230
+ return `${escapeVercelSource(path.slice(0, -2))}/:path(.*)?`;
231
+ }
218
232
  const trimmed =
219
233
  path.length > 1 && path.endsWith("/") ? path.slice(0, -1) : path;
220
234
  return trimmed.split("*").map(escapeVercelSource).join("(.*)");
@@ -14,6 +14,7 @@ import {
14
14
  SIGNATURES_DIRECTORY_TYPE,
15
15
  } from "../ai/web-bot-auth.ts";
16
16
  import { normalizeBasePath, normalizePath } from "../core/base-path.ts";
17
+ import { POWERED_BY_HEADERS } from "../core/powered-by.ts";
17
18
  import type { BlumeProject } from "../core/project-graph.ts";
18
19
  import { compileRedirects, isPatternPath } from "../core/redirect-patterns.ts";
19
20
  import type { CompiledRedirect } from "../core/redirect-patterns.ts";
@@ -159,6 +160,8 @@ export const nodePatternRedirects = (
159
160
  export interface NodeEntryOptions {
160
161
  /** `deployment.base`, normalized (`""` at the root). */
161
162
  base?: string;
163
+ /** Whether responses identify Blume with `X-Powered-By`. Defaults to `true`. */
164
+ poweredBy?: boolean;
162
165
  /** The configured redirects, from {@link nodeRedirects}. */
163
166
  redirects?: NodeRedirects;
164
167
  /** The configured pattern redirects, from {@link nodePatternRedirects}. */
@@ -169,7 +172,12 @@ export interface NodeEntryOptions {
169
172
  * The entry that replaces Astro's: it answers a configured redirect with its
170
173
  * exact status, and otherwise stamps the rules' headers on the response and
171
174
  * hands the request to Astro's handler. `send` leaves a Content-Type that is
172
- * already set alone, so the registered media type wins.
175
+ * already set alone, so the registered media type wins. Unless `poweredBy` is
176
+ * `false`, every answer also names Blume in `X-Powered-By` — the redirects,
177
+ * and the prerendered pages and files the static handler serves, which
178
+ * Blume's runtime middleware never sees. A middleware-mode entry leaves that
179
+ * header to the middleware: Astro hands a request it has no route for back to
180
+ * the host app through `next()`, and that response is the host's own.
173
181
  *
174
182
  * A rule matches the file the static handler will serve, not the raw URL:
175
183
  * Astro strips the base when it leads the path and serves the rest from the
@@ -194,10 +202,11 @@ export const nodeEntryWrapper = (
194
202
  // This wrapper answers the configured redirects with their exact status and
195
203
  // sets the headers the standalone server's static handler can't (the media
196
204
  // types and CORS headers of the .well-known discovery files, the sandbox on
197
- // downloaded SVGs), then hands every other request to Astro.
205
+ // downloaded SVGs, X-Powered-By), then hands every other request to Astro.
198
206
  import { posix } from "node:path";
199
207
 
200
208
  const BASE = ${JSON.stringify(options.base ?? "")};
209
+ const POWERED_BY = ${JSON.stringify(options.poweredBy === false ? {} : POWERED_BY_HEADERS)};
201
210
  const RULES = new Map(${JSON.stringify(exactRules(rules))});
202
211
  const PATTERNS = ${JSON.stringify(patternRules(rules))};
203
212
  const REDIRECTS = ${JSON.stringify(options.redirects ?? {})};
@@ -221,20 +230,20 @@ const servedPath = (req) => {
221
230
  : path;
222
231
  return BASE + (rest || "/");
223
232
  };
224
- const applyHeaders = (req, res) => {
233
+ const applyHeaders = (req, res, identify) => {
225
234
  const path = servedPath(req);
226
235
  const lower = path.toLowerCase();
227
- const headers =
228
- RULES.get(path) ??
229
- PATTERNS.find(
230
- ([prefix, suffix]) =>
231
- lower.startsWith(prefix.toLowerCase()) &&
232
- lower.endsWith(suffix.toLowerCase())
233
- )?.[2];
234
- if (headers) {
235
- for (const [name, value] of Object.entries(headers)) {
236
- res.setHeader(name, value);
237
- }
236
+ const headers = {
237
+ ...(identify ? POWERED_BY : {}),
238
+ ...(RULES.get(path) ??
239
+ PATTERNS.find(
240
+ ([prefix, suffix]) =>
241
+ lower.startsWith(prefix.toLowerCase()) &&
242
+ lower.endsWith(suffix.toLowerCase())
243
+ )?.[2]),
244
+ };
245
+ for (const [name, value] of Object.entries(headers)) {
246
+ res.setHeader(name, value);
238
247
  }
239
248
  };
240
249
  // A pattern redirect's Location: its template, each $n filled with that
@@ -268,7 +277,7 @@ const answerRedirect = (req, res) => {
268
277
  return false;
269
278
  }
270
279
  const [location, status] = redirect;
271
- res.writeHead(status, { location });
280
+ res.writeHead(status, { ...POWERED_BY, location });
272
281
  res.end();
273
282
  return true;
274
283
  };
@@ -285,7 +294,7 @@ export const handler = (req, res, ...rest) => {
285
294
  if (answerRedirect(req, res)) {
286
295
  return;
287
296
  }
288
- applyHeaders(req, res);
297
+ applyHeaders(req, res, options.mode !== "middleware");
289
298
  return astro.handler(req, res, ...rest);
290
299
  };
291
300
  export const startServer = () => {
@@ -301,7 +310,7 @@ export const startServer = () => {
301
310
  if (answerRedirect(req, res)) {
302
311
  return;
303
312
  }
304
- applyHeaders(req, res);
313
+ applyHeaders(req, res, true);
305
314
  for (const listener of astroListeners) {
306
315
  listener.call(httpServer, req, res);
307
316
  }
@@ -315,9 +324,10 @@ if (options.mode === "standalone" && previous !== "disabled") {
315
324
  `;
316
325
 
317
326
  /**
318
- * Put the wrapper in front of a Node server build's entry. A build with no
319
- * rule (no API catalog, AI catalog, MCP server, signatures directory, or
320
- * downloaded content assets) and no redirect leaves Astro's entry alone.
327
+ * Put the wrapper in front of a Node server build's entry. A build with
328
+ * `poweredBy` off, no rule (no API catalog, AI catalog, MCP server,
329
+ * signatures directory, or downloaded content assets), and no redirect leaves
330
+ * Astro's entry alone.
321
331
  */
322
332
  export const wrapNodeEntry = async (
323
333
  project: BlumeProject,
@@ -341,6 +351,7 @@ export const wrapNodeEntry = async (
341
351
  const redirects = nodeRedirects(project);
342
352
  const patternRedirects = nodePatternRedirects(project);
343
353
  if (
354
+ !project.config.poweredBy &&
344
355
  rules.length === 0 &&
345
356
  Object.keys(redirects).length === 0 &&
346
357
  patternRedirects.length === 0
@@ -365,6 +376,7 @@ export const wrapNodeEntry = async (
365
376
  nodeEntryWrapper(rules, {
366
377
  base: normalizeBasePath(project.config.deployment.options.base),
367
378
  patternRedirects,
379
+ poweredBy: project.config.poweredBy,
368
380
  redirects,
369
381
  }),
370
382
  "utf-8"
@@ -122,6 +122,7 @@ export const emitCloudflareNegotiation = async (
122
122
  )
123
123
  .map((route) => pageJsonPath(route.path))
124
124
  : [],
125
+ poweredBy: config.poweredBy,
125
126
  // The wrapper Worker matches full served URLs, so the redirects are
126
127
  // based the same way the platform files are — it answers any the
127
128
  // worker-first rules claim, where `_redirects` is never consulted and
@@ -305,17 +306,21 @@ export const bindCloudflareRateLimit = async (
305
306
  };
306
307
 
307
308
  /**
308
- * Cloudflare Workers and Pages. A server build emits the Worker into
309
- * `dist/server` and serves `dist/client` through the ASSETS binding, which
310
- * honors `_headers` from that directory exactly as Pages does — so the file
311
- * applies to both output modes here. Without a configured driver the adapter
312
- * force-enables KV-backed sessions and declares a `SESSION` binding that
313
- * `wrangler deploy` then demands a namespace for, even though Blume never
314
- * reads `Astro.session`; `session: false` opts the project out.
309
+ * Cloudflare. A server build targets Workers only (`@astrojs/cloudflare`
310
+ * dropped Pages in v13): it emits the Worker into `dist/server` and serves
311
+ * `dist/client` through the ASSETS binding, which honors `_headers` from that
312
+ * directory exactly as Pages does — so the file applies to both output modes
313
+ * here. A static build never loads the adapter; its `dist/` deploys to Pages,
314
+ * whose build environment `env` detects, or to Workers static assets (Workers
315
+ * Builds exposes no site URL to detect). Without a configured driver the
316
+ * adapter force-enables KV-backed sessions and declares a `SESSION` binding
317
+ * that `wrangler deploy` then demands a namespace for, even though Blume
318
+ * never reads `Astro.session`; `session: false` opts the project out.
315
319
  */
316
320
  export const cloudflarePlatform: DeployPlatform = {
317
321
  astro: {
318
- config: { session: false },
322
+ config: () => ({ session: false }),
323
+ configOptions: [],
319
324
  options: adapterOptions,
320
325
  package: CLOUDFLARE_ADAPTER_PACKAGE,
321
326
  },
@@ -350,5 +355,5 @@ export const cloudflarePlatform: DeployPlatform = {
350
355
  // build, and hoists its own `_headers`/`_redirects` back up to `dist/client`.
351
356
  serverClientUnderBase: true,
352
357
  serverOutputDir: distDir,
353
- serverStaticDir: clientDir,
358
+ serverStaticDir: (context, base) => join(clientDir(context), base),
354
359
  };
@@ -118,7 +118,8 @@ export const emitNetlifyPatternRedirects = async (
118
118
  */
119
119
  export const netlifyPlatform: DeployPlatform = {
120
120
  astro: {
121
- config: {},
121
+ config: () => ({}),
122
+ configOptions: [],
122
123
  options: () => ({}),
123
124
  package: NETLIFY_ADAPTER_PACKAGE,
124
125
  },
@@ -1,3 +1,4 @@
1
+ import type { JsonValue } from "../../core/adapter.ts";
1
2
  import { NODE_ADAPTER_PACKAGE } from "../adapters/node.ts";
2
3
  import { wrapNodeEntry } from "../node-headers.ts";
3
4
  import { clientDir, distDir } from "./paths.ts";
@@ -11,11 +12,20 @@ import type { DeployPlatform } from "./types.ts";
11
12
  * discovery files and downloaded SVGs need instead, and answers the
12
13
  * configured redirects with their exact status, which Astro's own handler
13
14
  * would default to 301 or 308. No platform env to detect: the site URL has to
14
- * be configured.
15
+ * be configured. `allowedDomains` becomes Astro's `security.allowedDomains`,
16
+ * which a server behind a reverse proxy needs before Astro reads the reader's
17
+ * address from `X-Forwarded-For`.
15
18
  */
16
19
  export const nodePlatform: DeployPlatform = {
17
20
  astro: {
18
- config: {},
21
+ config: ({ allowedDomains }) => {
22
+ const config: Record<string, JsonValue> = {};
23
+ if (allowedDomains) {
24
+ config.security = { allowedDomains };
25
+ }
26
+ return config;
27
+ },
28
+ configOptions: ["allowedDomains"],
19
29
  options: () => ({ mode: "standalone" }),
20
30
  package: NODE_ADAPTER_PACKAGE,
21
31
  },
@@ -2,7 +2,10 @@ import type { JsonValue } from "../../core/adapter.ts";
2
2
  import type { BlumeProject } from "../../core/project-graph.ts";
3
3
  import type { ResolvedConfig } from "../../core/schema.ts";
4
4
  import type { ProjectContext } from "../../core/types.ts";
5
- import type { DeployAdapterKind } from "../adapters/registry.ts";
5
+ import type {
6
+ DeployAdapterKind,
7
+ ResolvedDeployOptions,
8
+ } from "../adapters/registry.ts";
6
9
  import type { DeployOutput } from "../adapters/types.ts";
7
10
 
8
11
  /**
@@ -15,8 +18,16 @@ import type { DeployOutput } from "../adapters/types.ts";
15
18
 
16
19
  /** How the generated `astro.config.mjs` constructs the `@astrojs/*` adapter. */
17
20
  export interface AstroAdapterSpec {
18
- /** Top-level `defineConfig` entries the adapter needs beside itself. */
19
- config: Record<string, JsonValue>;
21
+ /**
22
+ * Top-level `defineConfig` entries the adapter needs beside itself, some
23
+ * of them set from the deployment's own options.
24
+ */
25
+ config: (options: ResolvedDeployOptions) => Record<string, JsonValue>;
26
+ /**
27
+ * The deployment options `config` reads, which therefore stay out of the
28
+ * passthrough to the `@astrojs/*` constructor.
29
+ */
30
+ configOptions: string[];
20
31
  /** Blume's own constructor options; the user's passthrough is spread over them. */
21
32
  options: (context: ProjectContext) => Record<string, JsonValue>;
22
33
  /** The package the config imports the adapter from. */
@@ -115,13 +126,16 @@ export interface DeployPlatform {
115
126
  /**
116
127
  * Whether the adapter moves a server build's client output under
117
128
  * `deployment.base` (`dist/client/<base>/`, the directory Astro then hands
118
- * `astro:build:done`) while the platform keeps serving `serverStaticDir`
119
- * as the assets root. The files the platform reads there, like `_headers`,
129
+ * `astro:build:done`) while the platform keeps serving `dist/client` as
130
+ * the assets root. The files the platform reads there, like `_headers`,
120
131
  * belong above the directory Astro reports.
121
132
  */
122
133
  serverClientUnderBase: boolean;
123
134
  /** Where a server build's deploy bundle lands. */
124
135
  serverOutputDir: (context: ProjectContext) => string;
125
- /** The directory a server build serves as static files. */
126
- serverStaticDir: (context: ProjectContext) => string;
136
+ /**
137
+ * The directory a server build serves as static files, given the normalized
138
+ * `deployment.base` for a platform that serves them from `<dir>/<base>/`.
139
+ */
140
+ serverStaticDir: (context: ProjectContext, base: string) => string;
127
141
  }