blume 2.0.3 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (607) hide show
  1. package/AGENTS.md +1 -0
  2. package/CHANGELOG.md +142 -0
  3. package/README.md +1 -1
  4. package/dist/cli/{chunk-5f86nr5m.js → chunk-073rdvgb.js} +21 -48
  5. package/dist/cli/{chunk-5f86nr5m.js.map → chunk-073rdvgb.js.map} +4 -5
  6. package/dist/cli/{chunk-ga0pf4aj.js → chunk-0n9xkp3d.js} +12 -3
  7. package/dist/cli/{chunk-ga0pf4aj.js.map → chunk-0n9xkp3d.js.map} +3 -3
  8. package/dist/cli/chunk-150jk7km.js +52 -0
  9. package/dist/cli/chunk-150jk7km.js.map +10 -0
  10. package/dist/cli/{chunk-9t7a85s3.js → chunk-313vnn40.js} +30 -19
  11. package/dist/cli/chunk-313vnn40.js.map +10 -0
  12. package/dist/cli/{chunk-tkacnehg.js → chunk-3v3ffbrj.js} +2 -2
  13. package/dist/cli/{chunk-q58y5e6a.js → chunk-3x6x3pph.js} +46 -24
  14. package/dist/cli/chunk-3x6x3pph.js.map +10 -0
  15. package/dist/cli/{chunk-1d7ve1dm.js → chunk-42t4jxdx.js} +37 -5
  16. package/dist/cli/chunk-42t4jxdx.js.map +12 -0
  17. package/dist/cli/{chunk-8ktnccpt.js → chunk-54ztw1nj.js} +21 -2
  18. package/dist/cli/chunk-54ztw1nj.js.map +10 -0
  19. package/dist/cli/{chunk-hm3vjy5s.js → chunk-5vjtgs6e.js} +28 -10
  20. package/dist/cli/{chunk-hm3vjy5s.js.map → chunk-5vjtgs6e.js.map} +4 -4
  21. package/dist/cli/chunk-66w9j7p6.js +322 -0
  22. package/dist/cli/chunk-66w9j7p6.js.map +14 -0
  23. package/dist/cli/{chunk-2hsdwb9n.js → chunk-7ppf1rva.js} +34 -18
  24. package/dist/cli/chunk-7ppf1rva.js.map +10 -0
  25. package/dist/cli/{chunk-r9rcc4w7.js → chunk-8y2kmjv8.js} +7 -7
  26. package/dist/cli/{chunk-r20tn01b.js → chunk-c1y538kd.js} +8 -2
  27. package/dist/cli/{chunk-r20tn01b.js.map → chunk-c1y538kd.js.map} +3 -3
  28. package/dist/cli/{chunk-2eytanqx.js → chunk-c887fj0s.js} +2 -2
  29. package/dist/cli/{chunk-p3v96n38.js → chunk-cfyg47zg.js} +16 -6
  30. package/dist/cli/chunk-cfyg47zg.js.map +10 -0
  31. package/dist/cli/chunk-cm89scdv.js +443 -0
  32. package/dist/cli/chunk-cm89scdv.js.map +11 -0
  33. package/dist/cli/{chunk-akbpwfxc.js → chunk-dzwehpy7.js} +2 -2
  34. package/dist/cli/{chunk-wrr3j9w9.js → chunk-e6atmn4h.js} +299 -72
  35. package/dist/cli/chunk-e6atmn4h.js.map +36 -0
  36. package/dist/cli/{chunk-tzmab476.js → chunk-e8xw9qkh.js} +8 -6
  37. package/dist/cli/{chunk-tzmab476.js.map → chunk-e8xw9qkh.js.map} +3 -3
  38. package/dist/cli/{chunk-6dsbexzp.js → chunk-e9q7a2ak.js} +17 -15
  39. package/dist/cli/{chunk-6dsbexzp.js.map → chunk-e9q7a2ak.js.map} +2 -2
  40. package/dist/cli/{chunk-pv29h0wf.js → chunk-ehweg4fr.js} +5155 -1249
  41. package/dist/cli/chunk-ehweg4fr.js.map +218 -0
  42. package/dist/cli/{chunk-c8chx29p.js → chunk-ep6nd0rn.js} +390 -318
  43. package/dist/cli/chunk-ep6nd0rn.js.map +36 -0
  44. package/dist/cli/{chunk-p73c0m7w.js → chunk-fdrkhpjy.js} +18 -15
  45. package/dist/cli/{chunk-p73c0m7w.js.map → chunk-fdrkhpjy.js.map} +3 -3
  46. package/dist/cli/{chunk-2z928egk.js → chunk-ft9vfys6.js} +5 -5
  47. package/dist/cli/{chunk-5m5nmvyq.js → chunk-gb420vvz.js} +316 -79
  48. package/dist/cli/chunk-gb420vvz.js.map +17 -0
  49. package/dist/cli/{chunk-vg9r4eb9.js → chunk-hebn0tpx.js} +86 -13
  50. package/dist/cli/chunk-hebn0tpx.js.map +14 -0
  51. package/dist/cli/{chunk-35d4wj9f.js → chunk-htrwgabr.js} +41 -23
  52. package/dist/cli/chunk-htrwgabr.js.map +15 -0
  53. package/dist/cli/{chunk-g4hq16wv.js → chunk-hx3xqnk9.js} +16 -14
  54. package/dist/cli/{chunk-g4hq16wv.js.map → chunk-hx3xqnk9.js.map} +2 -2
  55. package/dist/cli/chunk-j134vrp2.js +65 -0
  56. package/dist/cli/chunk-j134vrp2.js.map +10 -0
  57. package/dist/cli/{chunk-a58773jm.js → chunk-kma9f6sf.js} +2 -2
  58. package/dist/cli/chunk-kwrg63tm.js +31 -0
  59. package/dist/cli/chunk-kwrg63tm.js.map +10 -0
  60. package/dist/cli/{chunk-ey84smr6.js → chunk-mrcccgnh.js} +6 -6
  61. package/dist/cli/{chunk-ey84smr6.js.map → chunk-mrcccgnh.js.map} +1 -1
  62. package/dist/cli/{chunk-yfyb25rh.js → chunk-n0v7m8ry.js} +24 -8
  63. package/dist/cli/chunk-n0v7m8ry.js.map +14 -0
  64. package/dist/cli/{chunk-j85vccga.js → chunk-ncj7w596.js} +576 -367
  65. package/dist/cli/chunk-ncj7w596.js.map +35 -0
  66. package/dist/cli/{chunk-5xvm6tfj.js → chunk-nq1d20w2.js} +17 -15
  67. package/dist/cli/{chunk-5xvm6tfj.js.map → chunk-nq1d20w2.js.map} +2 -2
  68. package/dist/cli/{chunk-pehfxfta.js → chunk-nrns10mj.js} +6 -98
  69. package/dist/cli/chunk-nrns10mj.js.map +10 -0
  70. package/dist/cli/{chunk-crgn1q09.js → chunk-sds923fa.js} +4 -4
  71. package/dist/cli/chunk-t2m5j4pr.js +104 -0
  72. package/dist/cli/chunk-t2m5j4pr.js.map +11 -0
  73. package/dist/cli/{chunk-acanzt5p.js → chunk-tdqhyxfz.js} +6 -6
  74. package/dist/cli/chunk-tphac74f.js +126 -0
  75. package/dist/cli/chunk-tphac74f.js.map +11 -0
  76. package/dist/cli/chunk-w1r4vase.js +18 -0
  77. package/dist/cli/chunk-w1r4vase.js.map +10 -0
  78. package/dist/cli/{chunk-3em5wd2y.js → chunk-zccbjcj0.js} +4 -3
  79. package/dist/cli/{chunk-3em5wd2y.js.map → chunk-zccbjcj0.js.map} +2 -2
  80. package/dist/cli/index.js +31 -20
  81. package/dist/cli/index.js.map +5 -5
  82. package/dist/types/ai/agent-surface.d.ts +8 -7
  83. package/dist/types/ai/ai-catalog.d.ts +7 -0
  84. package/dist/types/ai/ask-context.d.ts +26 -0
  85. package/dist/types/ai/ask.d.ts +271 -58
  86. package/dist/types/ai/component-markdown.d.ts +2 -0
  87. package/dist/types/ai/index.d.ts +2 -2
  88. package/dist/types/ai/openapi-components.d.ts +4 -1
  89. package/dist/types/ai/site-skill.d.ts +26 -0
  90. package/dist/types/ai/skills.d.ts +16 -0
  91. package/dist/types/astro/integration.d.ts +9 -0
  92. package/dist/types/astro/markdown-negotiation.d.ts +3 -0
  93. package/dist/types/astro/pages.d.ts +10 -0
  94. package/dist/types/captcha/hcaptcha.d.ts +27 -0
  95. package/dist/types/captcha/index.d.ts +28 -0
  96. package/dist/types/captcha/schema.d.ts +29 -0
  97. package/dist/types/captcha/turnstile.d.ts +28 -0
  98. package/dist/types/cli/init/scaffold.d.ts +1 -1
  99. package/dist/types/components/content/api-field.d.ts +62 -0
  100. package/dist/types/components/content/api-page.d.ts +100 -0
  101. package/dist/types/components/layout/nav-utils.d.ts +26 -0
  102. package/dist/types/components/openapi/code-samples.d.ts +29 -0
  103. package/dist/types/components/openapi/helpers.d.ts +158 -0
  104. package/dist/types/components/openapi/security.d.ts +95 -0
  105. package/dist/types/components/openapi/snippets.d.ts +60 -0
  106. package/dist/types/consent/ethyca.d.ts +48 -0
  107. package/dist/types/consent/index.d.ts +37 -0
  108. package/dist/types/consent/native.d.ts +28 -0
  109. package/dist/types/consent/osano.d.ts +42 -0
  110. package/dist/types/consent/schema.d.ts +110 -0
  111. package/dist/types/core/changelog-index.d.ts +8 -0
  112. package/dist/types/core/config-input.d.ts +217 -23
  113. package/dist/types/core/data.d.ts +53 -4
  114. package/dist/types/core/footer.d.ts +8 -0
  115. package/dist/types/core/i18n-ui.d.ts +88 -0
  116. package/dist/types/core/includes.d.ts +8 -2
  117. package/dist/types/core/links.d.ts +8 -0
  118. package/dist/types/core/localizable.d.ts +7 -0
  119. package/dist/types/core/page-modes.d.ts +29 -0
  120. package/dist/types/core/probe.d.ts +2 -0
  121. package/dist/types/core/project-graph.d.ts +6 -0
  122. package/dist/types/core/redirect-patterns.d.ts +152 -0
  123. package/dist/types/core/related.d.ts +40 -0
  124. package/dist/types/core/schema.d.ts +670 -56
  125. package/dist/types/core/sources/cache.d.ts +3 -0
  126. package/dist/types/core/sources/contentful-rich-text.d.ts +11 -2
  127. package/dist/types/core/sources/contentful.d.ts +17 -1
  128. package/dist/types/core/sources/lexical.d.ts +5 -0
  129. package/dist/types/core/sources/normalize.d.ts +20 -1
  130. package/dist/types/core/sources/read.d.ts +5 -0
  131. package/dist/types/core/sources/remote.d.ts +37 -10
  132. package/dist/types/core/sources/sanity.d.ts +1 -1
  133. package/dist/types/core/types.d.ts +18 -1
  134. package/dist/types/core/variables.d.ts +42 -0
  135. package/dist/types/deploy/adapter-output.d.ts +2 -1
  136. package/dist/types/deploy/adapters/cloudflare.d.ts +7 -5
  137. package/dist/types/deploy/adapters/node.d.ts +58 -23
  138. package/dist/types/deploy/adapters/registry.d.ts +10 -0
  139. package/dist/types/deploy/adapters/types.d.ts +3 -2
  140. package/dist/types/deploy/node-headers.d.ts +13 -2
  141. package/dist/types/deploy/platforms/cloudflare.d.ts +15 -7
  142. package/dist/types/deploy/platforms/netlify.d.ts +7 -0
  143. package/dist/types/deploy/platforms/node.d.ts +3 -1
  144. package/dist/types/deploy/platforms/types.d.ts +18 -7
  145. package/dist/types/deploy/platforms/vercel.d.ts +26 -1
  146. package/dist/types/deploy/redirects.d.ts +55 -11
  147. package/dist/types/deploy/sitemap.d.ts +2 -1
  148. package/dist/types/deploy/vercel-negotiation.d.ts +51 -21
  149. package/dist/types/markdown/code-title.d.ts +17 -0
  150. package/dist/types/markdown/features.d.ts +3 -2
  151. package/dist/types/narration/build.d.ts +70 -0
  152. package/dist/types/narration/provider.d.ts +26 -0
  153. package/dist/types/narration/script.d.ts +154 -0
  154. package/dist/types/openapi/checks.d.ts +27 -0
  155. package/dist/types/openapi/graphql.d.ts +6 -0
  156. package/dist/types/openapi/model.d.ts +27 -10
  157. package/dist/types/openapi/overlay.d.ts +54 -0
  158. package/dist/types/openapi/parse.d.ts +16 -0
  159. package/dist/types/openapi/references.d.ts +30 -4
  160. package/dist/types/ratelimit/cloudflare.d.ts +39 -0
  161. package/dist/types/ratelimit/index.d.ts +27 -0
  162. package/dist/types/ratelimit/memory.d.ts +36 -0
  163. package/dist/types/ratelimit/schema.d.ts +119 -0
  164. package/dist/types/ratelimit/upstash.d.ts +46 -0
  165. package/dist/types/ratelimit/wrangler.d.ts +23 -0
  166. package/dist/types/reference/asyncapi.d.ts +10 -10
  167. package/dist/types/reference/graphql.d.ts +89 -11
  168. package/dist/types/reference/index.d.ts +1 -1
  169. package/dist/types/reference/openapi.d.ts +33 -12
  170. package/dist/types/reference/options.d.ts +82 -6
  171. package/dist/types/reference/scalar.d.ts +13 -0
  172. package/dist/types/reference/schema.d.ts +144 -36
  173. package/dist/types/search/adapters/registry.d.ts +3 -0
  174. package/dist/types/search/adapters/typesense.d.ts +14 -4
  175. package/dist/types/search/build.d.ts +3 -1
  176. package/dist/types/search/documents.d.ts +12 -1
  177. package/dist/types/search/orama-index.d.ts +13 -9
  178. package/dist/types/search/sync/algolia.d.ts +5 -2
  179. package/dist/types/search/sync/index.d.ts +3 -1
  180. package/dist/types/search/sync/typesense.d.ts +11 -7
  181. package/dist/types/seo/metatags.d.ts +20 -0
  182. package/dist/types/sources/contentful.d.ts +16 -0
  183. package/dist/types/sources/github-releases.d.ts +8 -1
  184. package/dist/types/sources/payload.d.ts +2 -0
  185. package/dist/types/sources/registry.d.ts +54 -0
  186. package/dist/types/sources/shared.d.ts +3 -0
  187. package/dist/types/sources/strapi.d.ts +2 -0
  188. package/docs/01-quickstart.mdx +4 -2
  189. package/docs/02-deployment.mdx +71 -17
  190. package/docs/03-upgrading.mdx +4 -4
  191. package/docs/04-migrating.mdx +1 -1
  192. package/docs/advanced/changelog.mdx +13 -2
  193. package/docs/advanced/skills.mdx +15 -1
  194. package/docs/cli/audit.mdx +51 -8
  195. package/docs/cli/doctor.mdx +5 -1
  196. package/docs/cli/evals.mdx +4 -4
  197. package/docs/cli/index.mdx +1 -0
  198. package/docs/cli/translate.mdx +1 -0
  199. package/docs/cli/validate.mdx +4 -3
  200. package/docs/configuration/analytics.mdx +3 -1
  201. package/docs/configuration/assistant.mdx +194 -38
  202. package/docs/configuration/consent.mdx +100 -0
  203. package/docs/configuration/customization.mdx +8 -6
  204. package/docs/configuration/export.mdx +5 -3
  205. package/docs/configuration/index.mdx +64 -5
  206. package/docs/configuration/meta.ts +3 -0
  207. package/docs/configuration/narration.mdx +119 -0
  208. package/docs/configuration/rate-limiting.mdx +90 -0
  209. package/docs/configuration/search.mdx +63 -12
  210. package/docs/configuration/theming.mdx +3 -1
  211. package/docs/content/components.mdx +108 -2
  212. package/docs/content/frontmatter.mdx +80 -2
  213. package/docs/content/i18n.mdx +39 -4
  214. package/docs/content/includes.mdx +16 -0
  215. package/docs/content/meta.mdx +1 -0
  216. package/docs/content/meta.ts +1 -0
  217. package/docs/content/navigation.mdx +32 -10
  218. package/docs/content/sources/contentful.mdx +35 -0
  219. package/docs/content/sources/custom.mdx +43 -0
  220. package/docs/content/sources/github-releases.mdx +39 -0
  221. package/docs/content/sources/index.mdx +91 -0
  222. package/docs/content/sources/meta.ts +16 -0
  223. package/docs/content/sources/notion.mdx +32 -0
  224. package/docs/content/sources/obsidian.mdx +37 -0
  225. package/docs/content/sources/payload.mdx +31 -0
  226. package/docs/content/sources/remote-mdx.mdx +44 -0
  227. package/docs/content/sources/sanity.mdx +29 -0
  228. package/docs/content/sources/strapi.mdx +32 -0
  229. package/docs/content/syntax.mdx +69 -4
  230. package/docs/content/variables.mdx +41 -0
  231. package/docs/discoverability/agent-discovery.mdx +29 -2
  232. package/docs/discoverability/index.mdx +2 -2
  233. package/docs/discoverability/json-api.mdx +2 -0
  234. package/docs/discoverability/llms-txt.mdx +2 -2
  235. package/docs/discoverability/markdown.mdx +3 -1
  236. package/docs/discoverability/mcp.mdx +4 -0
  237. package/docs/discoverability/metadata.mdx +17 -0
  238. package/docs/discoverability/sitemap-and-robots.mdx +1 -1
  239. package/docs/references/api-pages.mdx +74 -0
  240. package/docs/references/asyncapi.mdx +4 -2
  241. package/docs/references/graphql.mdx +14 -2
  242. package/docs/references/meta.ts +1 -1
  243. package/docs/references/openapi.mdx +148 -7
  244. package/docs/references/scalar.mdx +3 -1
  245. package/package.json +43 -8
  246. package/skills/blume/SKILL.md +3 -0
  247. package/skills/blume-migrate/SKILL.md +6 -6
  248. package/skills/blume-migrate/references/docusaurus.md +7 -7
  249. package/skills/blume-migrate/references/fumadocs.md +17 -13
  250. package/skills/blume-migrate/references/mintlify.md +37 -19
  251. package/skills/blume-migrate/references/nextra.md +10 -7
  252. package/skills/blume-migrate/references/starlight.md +11 -8
  253. package/skills/blume-migrate/scripts/mintlify-codemod.mjs +60 -14
  254. package/skills/blume-write-skill/SKILL.md +82 -0
  255. package/src/ai/agent-readability.ts +2 -1
  256. package/src/ai/agent-surface.ts +9 -7
  257. package/src/ai/ai-catalog.ts +6 -3
  258. package/src/ai/ask-context.ts +103 -20
  259. package/src/ai/ask-data.ts +6 -0
  260. package/src/ai/ask-limits.ts +13 -0
  261. package/src/ai/ask-tools.ts +142 -0
  262. package/src/ai/ask.ts +354 -76
  263. package/src/ai/changelog-markdown.ts +11 -6
  264. package/src/ai/component-markdown.ts +76 -1
  265. package/src/ai/cors.ts +14 -3
  266. package/src/ai/index.ts +12 -0
  267. package/src/ai/llms.ts +6 -2
  268. package/src/ai/markdown.ts +2 -1
  269. package/src/ai/mcp/data.ts +6 -0
  270. package/src/ai/mcp/query.ts +14 -6
  271. package/src/ai/openapi-components.ts +36 -6
  272. package/src/ai/relative-links.ts +4 -38
  273. package/src/ai/site-skill.ts +274 -0
  274. package/src/ai/skills.ts +35 -2
  275. package/src/ai/write-skill.ts +97 -0
  276. package/src/astro/generate.ts +210 -66
  277. package/src/astro/index.ts +1 -0
  278. package/src/astro/integration.ts +42 -4
  279. package/src/astro/markdown-negotiation.ts +3 -0
  280. package/src/astro/module-types.ts +9 -1
  281. package/src/astro/pages.ts +23 -0
  282. package/src/astro/templates.ts +406 -108
  283. package/src/astro/variables.ts +31 -0
  284. package/src/audit/catalog.ts +38 -3
  285. package/src/audit/checks/contrast.ts +183 -0
  286. package/src/audit/checks/i18n.ts +2 -3
  287. package/src/audit/checks/indexability.ts +2 -3
  288. package/src/audit/checks/links.ts +19 -13
  289. package/src/audit/checks/network.ts +110 -6
  290. package/src/audit/checks/redirects.ts +6 -4
  291. package/src/audit/checks/robots.ts +58 -2
  292. package/src/audit/checks/sitemap.ts +2 -3
  293. package/src/audit/redirects.ts +46 -15
  294. package/src/audit/run.ts +5 -0
  295. package/src/audit/types.ts +4 -1
  296. package/src/audit/url.ts +14 -5
  297. package/src/blume-modules.d.ts +12 -1
  298. package/src/captcha/client.ts +152 -0
  299. package/src/captcha/hcaptcha.ts +36 -0
  300. package/src/captcha/index.ts +28 -0
  301. package/src/captcha/schema.ts +32 -0
  302. package/src/captcha/turnstile.ts +38 -0
  303. package/src/captcha/verify.ts +71 -0
  304. package/src/cli/args.ts +51 -0
  305. package/src/cli/build-failure.ts +23 -0
  306. package/src/cli/command-meta.ts +5 -0
  307. package/src/cli/commands/audit.ts +10 -1
  308. package/src/cli/commands/dev.ts +18 -2
  309. package/src/cli/commands/doctor.ts +67 -28
  310. package/src/cli/commands/eval.ts +36 -4
  311. package/src/cli/commands/init.ts +6 -4
  312. package/src/cli/commands/skill.ts +96 -0
  313. package/src/cli/commands/validate.ts +10 -1
  314. package/src/cli/index.ts +5 -0
  315. package/src/cli/init/scaffold.ts +3 -3
  316. package/src/cli/prepare.ts +6 -3
  317. package/src/cli/required-secrets.ts +72 -30
  318. package/src/components/BlumePage.astro +2 -0
  319. package/src/components/content/ApiEndpoint.astro +18 -0
  320. package/src/components/content/ApiField.astro +79 -0
  321. package/src/components/content/ApiPlayground.astro +66 -0
  322. package/src/components/content/ApiRail.astro +19 -0
  323. package/src/components/content/Color.astro +1 -0
  324. package/src/components/content/FileTree.astro +1 -0
  325. package/src/components/content/GithubInfo.astro +1 -0
  326. package/src/components/content/Panel.astro +1 -0
  327. package/src/components/content/ParamField.astro +25 -0
  328. package/src/components/content/RequestExample.astro +17 -0
  329. package/src/components/content/ResponseExample.astro +17 -0
  330. package/src/components/content/ResponseField.astro +32 -0
  331. package/src/components/content/Step.astro +1 -0
  332. package/src/components/content/Tile.astro +1 -0
  333. package/src/components/content/Tooltip.astro +1 -0
  334. package/src/components/content/Tree.astro +1 -0
  335. package/src/components/content/TypeTable.astro +4 -1
  336. package/src/components/content/View.astro +21 -0
  337. package/src/components/content/ViewSwitcher.astro +168 -0
  338. package/src/components/content/api-field.ts +103 -0
  339. package/src/components/content/api-page.ts +248 -0
  340. package/src/components/content/mermaid-element.ts +15 -1
  341. package/src/components/content/view-switcher.ts +71 -0
  342. package/src/components/index.ts +2 -0
  343. package/src/components/islands/Assistant.astro +10 -1
  344. package/src/components/islands/assistant.tsx +104 -14
  345. package/src/components/islands/code-question.ts +62 -0
  346. package/src/components/islands/hooks.ts +136 -21
  347. package/src/components/islands/support-link.ts +68 -0
  348. package/src/components/layout/Analytics.astro +11 -2
  349. package/src/components/layout/Banner.astro +14 -10
  350. package/src/components/layout/Breadcrumbs.astro +6 -2
  351. package/src/components/layout/ConsentBanner.astro +62 -0
  352. package/src/components/layout/ConsentHead.astro +38 -0
  353. package/src/components/layout/Directory.astro +94 -0
  354. package/src/components/layout/DrawerSwitchers.astro +51 -0
  355. package/src/components/layout/Empty.astro +2 -2
  356. package/src/components/layout/Header.astro +122 -80
  357. package/src/components/layout/LanguageSwitcher.astro +48 -15
  358. package/src/components/layout/NarrationPlayer.astro +1085 -0
  359. package/src/components/layout/NavSelector.astro +33 -7
  360. package/src/components/layout/PageActions.astro +139 -24
  361. package/src/components/layout/PageFeedback.astro +54 -49
  362. package/src/components/layout/PageLayout.astro +71 -3
  363. package/src/components/layout/ReferenceLayout.astro +17 -6
  364. package/src/components/layout/RelatedPages.astro +42 -0
  365. package/src/components/layout/RootLayout.astro +318 -47
  366. package/src/components/layout/Search.astro +47 -19
  367. package/src/components/layout/SiteFooter.astro +124 -0
  368. package/src/components/layout/SiteMetatags.astro +16 -0
  369. package/src/components/layout/TableOfContents.astro +24 -17
  370. package/src/components/layout/consent/native.ts +79 -0
  371. package/src/components/layout/consent/types.ts +37 -0
  372. package/src/components/layout/footer-links.ts +53 -0
  373. package/src/components/layout/footer-socials.ts +87 -0
  374. package/src/components/layout/head-scripts.ts +6 -1
  375. package/src/components/layout/locale-redirect.ts +107 -0
  376. package/src/components/layout/nav-utils.ts +83 -0
  377. package/src/components/layout/page-feedback.ts +96 -0
  378. package/src/components/layout/search/algolia.ts +10 -1
  379. package/src/components/layout/search/analytics.ts +126 -0
  380. package/src/components/layout/search/endpoint.ts +48 -5
  381. package/src/components/layout/search/flexsearch.ts +17 -5
  382. package/src/components/layout/search/orama-cloud.ts +26 -14
  383. package/src/components/layout/search/pagefind.ts +114 -5
  384. package/src/components/layout/search/types.ts +32 -0
  385. package/src/components/layout/search/typesense.ts +19 -3
  386. package/src/components/openapi/AsyncApiOperation.astro +14 -7
  387. package/src/components/openapi/Authorization.astro +10 -0
  388. package/src/components/openapi/GraphqlOperation.astro +5 -0
  389. package/src/components/openapi/Operation.astro +184 -13
  390. package/src/components/openapi/ParametersTable.astro +8 -4
  391. package/src/components/openapi/Playground.astro +7 -1
  392. package/src/components/openapi/RequestBody.astro +24 -5
  393. package/src/components/openapi/RequestPanel.astro +59 -7
  394. package/src/components/openapi/Responses.astro +7 -4
  395. package/src/components/openapi/SchemaTable.astro +15 -3
  396. package/src/components/openapi/async-snippets.ts +65 -9
  397. package/src/components/openapi/async.ts +123 -29
  398. package/src/components/openapi/code-samples.ts +97 -0
  399. package/src/components/openapi/graphql-helpers.ts +35 -12
  400. package/src/components/openapi/helpers.ts +36 -2
  401. package/src/components/openapi/message-model.ts +54 -11
  402. package/src/components/openapi/message.ts +14 -4
  403. package/src/components/openapi/operation-model.ts +16 -7
  404. package/src/components/openapi/playground-client.ts +24 -5
  405. package/src/components/openapi/playground-schema.ts +1 -1
  406. package/src/components/openapi/schema-tree.ts +44 -14
  407. package/src/components/openapi/security.ts +16 -3
  408. package/src/components/openapi/snippets.ts +873 -10
  409. package/src/components/openapi/validate-json.ts +4 -3
  410. package/src/components/props.ts +6 -0
  411. package/src/consent/client.ts +95 -0
  412. package/src/consent/clients.ts +19 -0
  413. package/src/consent/ethyca.ts +71 -0
  414. package/src/consent/head.ts +24 -0
  415. package/src/consent/index.ts +37 -0
  416. package/src/consent/init.ts +29 -0
  417. package/src/consent/native.ts +36 -0
  418. package/src/consent/osano.ts +59 -0
  419. package/src/consent/schema.ts +30 -0
  420. package/src/core/builtin-tags.ts +5 -0
  421. package/src/core/changelog-index.ts +31 -1
  422. package/src/core/client-address.ts +19 -0
  423. package/src/core/component-diagnostics.ts +53 -0
  424. package/src/core/component-overrides.ts +32 -0
  425. package/src/core/config-input.ts +225 -23
  426. package/src/core/data.ts +46 -4
  427. package/src/core/diagnostics.ts +8 -2
  428. package/src/core/footer.ts +26 -0
  429. package/src/core/graph.ts +39 -36
  430. package/src/core/i18n-ui.ts +84 -2
  431. package/src/core/includes.ts +21 -4
  432. package/src/core/links.ts +63 -19
  433. package/src/core/localizable.ts +26 -0
  434. package/src/core/navigation.ts +43 -12
  435. package/src/core/page-modes.ts +56 -0
  436. package/src/core/probe.ts +26 -6
  437. package/src/core/project-graph.ts +163 -16
  438. package/src/core/redirect-patterns.ts +520 -0
  439. package/src/core/related.ts +131 -0
  440. package/src/core/schema.ts +339 -66
  441. package/src/core/sources/cache.ts +6 -0
  442. package/src/core/sources/contentful-rich-text.ts +29 -6
  443. package/src/core/sources/contentful.ts +135 -25
  444. package/src/core/sources/filesystem.ts +26 -4
  445. package/src/core/sources/github-releases.ts +12 -1
  446. package/src/core/sources/lexical.ts +15 -10
  447. package/src/core/sources/mdx-remote.ts +62 -10
  448. package/src/core/sources/normalize.ts +228 -41
  449. package/src/core/sources/notion.ts +7 -1
  450. package/src/core/sources/obsidian.ts +16 -3
  451. package/src/core/sources/payload.ts +38 -15
  452. package/src/core/sources/read.ts +8 -3
  453. package/src/core/sources/remote.ts +72 -15
  454. package/src/core/sources/resolve.ts +11 -1
  455. package/src/core/sources/sanity.ts +43 -25
  456. package/src/core/sources/strapi.ts +2 -2
  457. package/src/core/types.ts +19 -1
  458. package/src/core/ui-packs/ar.ts +44 -0
  459. package/src/core/ui-packs/bg.ts +46 -0
  460. package/src/core/ui-packs/bn.ts +44 -0
  461. package/src/core/ui-packs/ca.ts +47 -0
  462. package/src/core/ui-packs/cs.ts +45 -0
  463. package/src/core/ui-packs/da.ts +46 -0
  464. package/src/core/ui-packs/de.ts +47 -0
  465. package/src/core/ui-packs/el.ts +46 -0
  466. package/src/core/ui-packs/es.ts +47 -0
  467. package/src/core/ui-packs/fa.ts +46 -0
  468. package/src/core/ui-packs/fi.ts +46 -0
  469. package/src/core/ui-packs/fr.ts +47 -0
  470. package/src/core/ui-packs/he.ts +44 -0
  471. package/src/core/ui-packs/hi.ts +45 -0
  472. package/src/core/ui-packs/hr.ts +46 -0
  473. package/src/core/ui-packs/hu.ts +45 -0
  474. package/src/core/ui-packs/id.ts +46 -0
  475. package/src/core/ui-packs/it.ts +46 -0
  476. package/src/core/ui-packs/ja.ts +46 -0
  477. package/src/core/ui-packs/ko.ts +45 -0
  478. package/src/core/ui-packs/nl.ts +47 -0
  479. package/src/core/ui-packs/no.ts +45 -0
  480. package/src/core/ui-packs/pl.ts +46 -0
  481. package/src/core/ui-packs/pt-br.ts +47 -0
  482. package/src/core/ui-packs/pt.ts +46 -0
  483. package/src/core/ui-packs/ro.ts +46 -0
  484. package/src/core/ui-packs/ru.ts +46 -0
  485. package/src/core/ui-packs/sk.ts +45 -0
  486. package/src/core/ui-packs/sr.ts +46 -0
  487. package/src/core/ui-packs/sv.ts +45 -0
  488. package/src/core/ui-packs/th.ts +44 -0
  489. package/src/core/ui-packs/tr.ts +46 -0
  490. package/src/core/ui-packs/uk.ts +46 -0
  491. package/src/core/ui-packs/vi.ts +46 -0
  492. package/src/core/ui-packs/zh-tw.ts +44 -0
  493. package/src/core/ui-packs/zh.ts +44 -0
  494. package/src/core/variables.ts +103 -0
  495. package/src/deploy/adapter-output.ts +7 -2
  496. package/src/deploy/adapters/cloudflare.ts +7 -5
  497. package/src/deploy/adapters/node.ts +47 -9
  498. package/src/deploy/adapters/types.ts +7 -3
  499. package/src/deploy/artifacts.ts +79 -9
  500. package/src/deploy/cloudflare-negotiation.ts +35 -5
  501. package/src/deploy/headers.ts +2 -1
  502. package/src/deploy/node-headers.ts +63 -9
  503. package/src/deploy/platforms/cloudflare.ts +45 -9
  504. package/src/deploy/platforms/netlify.ts +35 -2
  505. package/src/deploy/platforms/node.ts +12 -2
  506. package/src/deploy/platforms/types.ts +21 -7
  507. package/src/deploy/platforms/vercel.ts +149 -17
  508. package/src/deploy/redirects.ts +151 -24
  509. package/src/deploy/rss.ts +3 -1
  510. package/src/deploy/sitemap.ts +39 -5
  511. package/src/deploy/vercel-negotiation.ts +326 -102
  512. package/src/eval/agents.ts +18 -1
  513. package/src/eval/prompts.ts +6 -3
  514. package/src/eval/report.ts +34 -13
  515. package/src/eval/run.ts +5 -0
  516. package/src/markdown/api-rail.ts +259 -0
  517. package/src/markdown/code-title.ts +35 -9
  518. package/src/markdown/features.ts +3 -2
  519. package/src/markdown/fence-meta.ts +2 -0
  520. package/src/markdown/include.ts +11 -1
  521. package/src/markdown/index.ts +28 -5
  522. package/src/markdown/math.ts +4 -4
  523. package/src/markdown/relative-links.ts +3 -2
  524. package/src/markdown/variables.ts +56 -0
  525. package/src/markdown/views.ts +82 -0
  526. package/src/narration/build.ts +412 -0
  527. package/src/narration/player.ts +249 -0
  528. package/src/narration/provider.ts +29 -0
  529. package/src/narration/script.ts +592 -0
  530. package/src/og/card.ts +7 -6
  531. package/src/openapi/checks.ts +273 -0
  532. package/src/openapi/graphql-build.ts +21 -0
  533. package/src/openapi/graphql.ts +11 -0
  534. package/src/openapi/model.ts +72 -27
  535. package/src/openapi/overlay.ts +256 -0
  536. package/src/openapi/parse.ts +182 -4
  537. package/src/openapi/references.ts +64 -8
  538. package/src/openapi/render-mdx.ts +5 -1
  539. package/src/openapi/scalar.ts +40 -6
  540. package/src/openapi/source.ts +40 -15
  541. package/src/ratelimit/cloudflare.ts +54 -0
  542. package/src/ratelimit/index.ts +30 -0
  543. package/src/ratelimit/memory.ts +45 -0
  544. package/src/ratelimit/runtime.ts +247 -0
  545. package/src/ratelimit/schema.ts +28 -0
  546. package/src/ratelimit/upstash.ts +60 -0
  547. package/src/ratelimit/wrangler.ts +70 -0
  548. package/src/reference/asyncapi.ts +2 -2
  549. package/src/reference/graphql.ts +20 -3
  550. package/src/reference/index.ts +1 -0
  551. package/src/reference/openapi.ts +21 -7
  552. package/src/reference/options.ts +80 -8
  553. package/src/reference/scalar.ts +33 -8
  554. package/src/registry/eject.ts +23 -5
  555. package/src/registry/registry.ts +7 -0
  556. package/src/search/adapters/locale-scope.ts +30 -0
  557. package/src/search/adapters/typesense.ts +14 -4
  558. package/src/search/build.ts +21 -3
  559. package/src/search/documents.ts +26 -2
  560. package/src/search/orama-index.ts +191 -34
  561. package/src/search/pagefind-ranking.ts +39 -0
  562. package/src/search/source-pages.ts +29 -0
  563. package/src/search/sync/algolia.ts +111 -10
  564. package/src/search/sync/index.ts +52 -13
  565. package/src/search/sync/orama-cloud.ts +3 -0
  566. package/src/search/sync/typesense.ts +76 -26
  567. package/src/seo/metatags.ts +67 -0
  568. package/src/sources/contentful.ts +12 -0
  569. package/src/sources/github-releases.ts +7 -1
  570. package/src/sources/shared.ts +3 -0
  571. package/src/theme/chrome-icons.ts +1 -0
  572. package/src/theme/color.ts +500 -0
  573. package/src/theme/entry.ts +85 -6
  574. package/src/theme/palette.ts +121 -45
  575. package/src/translate/prompts.ts +1 -0
  576. package/src/translate/validate.ts +31 -3
  577. package/src/upgrade/upgrade.ts +4 -171
  578. package/dist/cli/chunk-1d7ve1dm.js.map +0 -12
  579. package/dist/cli/chunk-2hsdwb9n.js.map +0 -10
  580. package/dist/cli/chunk-35d4wj9f.js.map +0 -15
  581. package/dist/cli/chunk-364znk6q.js +0 -75
  582. package/dist/cli/chunk-364znk6q.js.map +0 -11
  583. package/dist/cli/chunk-5m5nmvyq.js.map +0 -13
  584. package/dist/cli/chunk-8ktnccpt.js.map +0 -10
  585. package/dist/cli/chunk-9t7a85s3.js.map +0 -10
  586. package/dist/cli/chunk-b07cmahc.js +0 -145
  587. package/dist/cli/chunk-b07cmahc.js.map +0 -11
  588. package/dist/cli/chunk-c8chx29p.js.map +0 -36
  589. package/dist/cli/chunk-e04dxsz1.js +0 -39
  590. package/dist/cli/chunk-e04dxsz1.js.map +0 -10
  591. package/dist/cli/chunk-j85vccga.js.map +0 -36
  592. package/dist/cli/chunk-p3v96n38.js.map +0 -10
  593. package/dist/cli/chunk-pehfxfta.js.map +0 -10
  594. package/dist/cli/chunk-pv29h0wf.js.map +0 -190
  595. package/dist/cli/chunk-q58y5e6a.js.map +0 -10
  596. package/dist/cli/chunk-vg9r4eb9.js.map +0 -14
  597. package/dist/cli/chunk-wrr3j9w9.js.map +0 -35
  598. package/dist/cli/chunk-yfyb25rh.js.map +0 -14
  599. package/docs/content/sources.mdx +0 -334
  600. /package/dist/cli/{chunk-tkacnehg.js.map → chunk-3v3ffbrj.js.map} +0 -0
  601. /package/dist/cli/{chunk-r9rcc4w7.js.map → chunk-8y2kmjv8.js.map} +0 -0
  602. /package/dist/cli/{chunk-2eytanqx.js.map → chunk-c887fj0s.js.map} +0 -0
  603. /package/dist/cli/{chunk-akbpwfxc.js.map → chunk-dzwehpy7.js.map} +0 -0
  604. /package/dist/cli/{chunk-2z928egk.js.map → chunk-ft9vfys6.js.map} +0 -0
  605. /package/dist/cli/{chunk-a58773jm.js.map → chunk-kma9f6sf.js.map} +0 -0
  606. /package/dist/cli/{chunk-crgn1q09.js.map → chunk-sds923fa.js.map} +0 -0
  607. /package/dist/cli/{chunk-acanzt5p.js.map → chunk-tdqhyxfz.js.map} +0 -0
@@ -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
  /**
@@ -14,6 +14,11 @@ import { API_CATALOG_PATH, buildApiCatalog } from "../ai/api-catalog.ts";
14
14
  import { buildHomeLinkHeader } from "../ai/link-headers.ts";
15
15
  import { buildLlmsFiles } from "../ai/llms.ts";
16
16
  import { markdownRoutePaths } from "../ai/markdown.ts";
17
+ import {
18
+ buildSiteSkill,
19
+ SKILL_MD_PATH,
20
+ siteSkillName,
21
+ } from "../ai/site-skill.ts";
17
22
  import {
18
23
  AGENT_SKILLS_DIR,
19
24
  AGENT_SKILLS_INDEX_PATH,
@@ -29,6 +34,7 @@ import { normalizeBasePath } from "../core/base-path.ts";
29
34
  import { discoverPagesSync } from "../core/custom-pages.ts";
30
35
  import type { BlumeProject } from "../core/project-graph.ts";
31
36
  import type { ResolvedConfig } from "../core/schema.ts";
37
+ import { buildNarration } from "../narration/build.ts";
32
38
  import { buildSearchIndex } from "../search/build.ts";
33
39
  import { syncSearchProvider } from "../search/sync/index.ts";
34
40
  import { readsHeaderFiles } from "./adapter-output.ts";
@@ -212,7 +218,8 @@ export const emitHeaderFiles = async (
212
218
 
213
219
  /**
214
220
  * Collect the Agent Skills `agents.skills` publishes, once per build, so both the
215
- * skills surface and llms.txt (which lists them) read the same set. Empty
221
+ * skills surface and llms.txt (which lists them) read the same set (the
222
+ * generated site skill joins them in {@link publishedSkills}). Empty
216
223
  * when the feature is off, the directory is missing, nothing in it is
217
224
  * publishable (each with a warning), or a user-shipped
218
225
  * `public/.well-known/agent-skills/index.json` already owns the surface.
@@ -248,6 +255,57 @@ const collectConfiguredSkills = async (
248
255
  return skills;
249
256
  };
250
257
 
258
+ /**
259
+ * Every skill the build publishes: the `agents.skills` ones, plus the
260
+ * generated site skill unless one of those shares its name (the hand-written
261
+ * one wins) or a user-shipped index owns the surface.
262
+ */
263
+ const publishedSkills = async (
264
+ project: BlumeProject,
265
+ distDir: string,
266
+ surface: { mcp: boolean; userIndex: boolean },
267
+ logger: ArtifactLogger
268
+ ): Promise<SkillArtifact[]> => {
269
+ const configured = await collectConfiguredSkills(project, distDir, logger);
270
+ // The skill lists the MCP server only when the build serves it.
271
+ const site = surface.userIndex
272
+ ? null
273
+ : buildSiteSkill({
274
+ ...project,
275
+ config: advertisedConfig(project.config, {
276
+ mcp: surface.mcp,
277
+ skills: true,
278
+ }),
279
+ });
280
+ return site && !configured.some((skill) => skill.name === site.name)
281
+ ? [site, ...configured]
282
+ : configured;
283
+ };
284
+
285
+ /**
286
+ * Serve the site skill at `/skill.md` too, the path agents try first: the
287
+ * generated one, or a same-named hand-written skill that is a lone
288
+ * `SKILL.md` (an archive's relative references need its other files). A
289
+ * `public/skill.md` wins.
290
+ */
291
+ const emitSkillMd = async (
292
+ project: BlumeProject,
293
+ distDir: string,
294
+ skills: readonly SkillArtifact[],
295
+ logger: ArtifactLogger
296
+ ): Promise<void> => {
297
+ const target = join(distDir, SKILL_MD_PATH.slice(1));
298
+ const name = siteSkillName(project);
299
+ const skill = skills.find(
300
+ (candidate) => candidate.name === name && candidate.type === "skill-md"
301
+ );
302
+ if (!(project.config.agents.skillMd && skill) || existsSync(target)) {
303
+ return;
304
+ }
305
+ await writeFile(target, skill.content);
306
+ logger.info(`Generated ${SKILL_MD_PATH.slice(1)} (the "${name}" skill)`);
307
+ };
308
+
251
309
  /**
252
310
  * Publish the collected Agent Skills: copy each skill artifact under
253
311
  * `.well-known/agent-skills/` and emit the discovery index. A user-shipped
@@ -382,8 +440,13 @@ export const publishBuildArtifacts = async (
382
440
  logger.info(`Indexed ${indexed} page(s) for search`);
383
441
  }
384
442
 
443
+ // Generated narration reads the built pages, so it runs once they exist.
444
+ // Nothing happens without `narration.provider`.
445
+ await buildNarration(project, distDir, logger);
446
+
385
447
  // Upload the index to a hosted provider (Algolia, Orama Cloud, Typesense).
386
- // 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.
387
450
  await syncSearchProvider(project, {
388
451
  start: logger.info.bind(logger),
389
452
  success: logger.info.bind(logger),
@@ -394,18 +457,24 @@ export const publishBuildArtifacts = async (
394
457
  const userSkillsIndex = existsSync(
395
458
  join(distDir, AGENT_SKILLS_INDEX_PATH.slice(1))
396
459
  );
397
- const skills = await collectConfiguredSkills(project, distDir, logger);
460
+ const mcp = servesMcp(
461
+ project,
462
+ project.context.pagesRoot
463
+ ? discoverPagesSync(project.context.pagesRoot)
464
+ : []
465
+ );
466
+ const skills = await publishedSkills(
467
+ project,
468
+ distDir,
469
+ { mcp, userIndex: userSkillsIndex },
470
+ logger
471
+ );
398
472
  // The discovery documents advertise what this build serves: no MCP server
399
473
  // when a page took its route, no skills index when nothing was published.
400
474
  const advertised: BlumeProject = {
401
475
  ...project,
402
476
  config: advertisedConfig(project.config, {
403
- mcp: servesMcp(
404
- project,
405
- project.context.pagesRoot
406
- ? discoverPagesSync(project.context.pagesRoot)
407
- : []
408
- ),
477
+ mcp,
409
478
  skills: skills.length > 0 || userSkillsIndex,
410
479
  }),
411
480
  };
@@ -444,6 +513,7 @@ export const publishBuildArtifacts = async (
444
513
 
445
514
  await emitWellKnownFiles(advertised.config, distDir, skills, logger);
446
515
  await emitAgentSkills(project, distDir, skills, logger);
516
+ await emitSkillMd(project, distDir, skills, logger);
447
517
 
448
518
  await emitRedirectFiles(advertised, distDir, logger);
449
519
  await emitHeaderFiles(advertised, distDir, logger);
@@ -85,6 +85,7 @@ import {
85
85
  OPENAPI_PATH,
86
86
  } from "../ai/api/paths.ts";
87
87
  import { normalizeBasePath, normalizePath } from "../core/base-path.ts";
88
+ import { compileRedirects, isPatternPath } from "../core/redirect-patterns.ts";
88
89
  import { CONTENT_ASSETS_ROOT, SVG_ASSET_HEADERS } from "./headers.ts";
89
90
  import type { NotFoundVariants } from "./vercel-negotiation.ts";
90
91
 
@@ -289,12 +290,18 @@ export const buildNegotiationWorker = (
289
290
  // header, which cannot carry non-ASCII.
290
291
  const redirects = JSON.stringify(
291
292
  Object.fromEntries(
292
- (options.redirects ?? []).map((redirect) => [
293
- normalizePath(redirect.from),
294
- [encodeURI(redirect.to), redirect.status],
295
- ])
293
+ (options.redirects ?? [])
294
+ .filter((redirect) => !isPatternPath(redirect.from))
295
+ .map((redirect) => [
296
+ normalizePath(redirect.from),
297
+ [encodeURI(redirect.to), redirect.status],
298
+ ])
296
299
  )
297
300
  );
301
+ // Patterns (`/beta/:slug*`) are tried in order after the exact paths.
302
+ const patternRedirects = JSON.stringify(
303
+ compileRedirects(options.redirects ?? [])
304
+ );
298
305
  return `// Generated by Blume. Do not edit; this file is recreated on each build.
299
306
  //
300
307
  // Request-time \`Accept: text/markdown\` negotiation for a Cloudflare server
@@ -319,6 +326,9 @@ const ASSETS_BINDING = ${binding};
319
326
  const HOME_LINK_HEADER = ${homeLinkHeader};
320
327
  const HOME_TOKENS = ${homeTokens};
321
328
  const REDIRECTS = ${redirects};
329
+ const PATTERN_REDIRECTS = ${patternRedirects}.map(
330
+ ([source, location, status]) => [new RegExp(source, "u"), location, status]
331
+ );
322
332
  const NOT_FOUND = ${notFound};
323
333
  const SVG_ASSET_HEADERS = ${JSON.stringify(SVG_ASSET_HEADERS)};
324
334
 
@@ -335,11 +345,31 @@ const isSvgAsset = (pathname) => {
335
345
  // reads — a worker-first route never reaches it. Answering from this baked-in
336
346
  // copy keeps the exact configured status; delegating instead would let Astro's
337
347
  // SSR handler default a GET to a permanent 301.
348
+ // An exact path is looked up first, then each pattern is tried in order, its
349
+ // Location template filled with the captures, encoded so a capture can't grow
350
+ // a query or fragment.
338
351
  const redirectFor = (pathname) => {
339
352
  const trimmed =
340
353
  pathname !== "/" && pathname.endsWith("/") ? pathname.slice(0, -1) : pathname;
341
354
  const path = safeDecode(trimmed);
342
- return Object.hasOwn(REDIRECTS, path) ? REDIRECTS[path] : null;
355
+ if (Object.hasOwn(REDIRECTS, path)) {
356
+ return REDIRECTS[path];
357
+ }
358
+ const decoded = safeDecode(pathname);
359
+ for (const [pattern, location, status] of PATTERN_REDIRECTS) {
360
+ const match = pattern.exec(decoded);
361
+ if (match) {
362
+ return [
363
+ location.replace(/\\$(\\d+)/gu, (_reference, index) =>
364
+ encodeURI(match[Number(index)] ?? "")
365
+ .replaceAll("?", "%3F")
366
+ .replaceAll("#", "%23")
367
+ ),
368
+ status,
369
+ ];
370
+ }
371
+ }
372
+ return null;
343
373
  };
344
374
 
345
375
  // The request path with the deployment base removed: the whole path on a
@@ -4,6 +4,7 @@ import {
4
4
  API_CATALOG_TYPE,
5
5
  hasApiCatalog,
6
6
  } from "../ai/api-catalog.ts";
7
+ import { servesSkillsIndex } from "../ai/skills.ts";
7
8
  import {
8
9
  SIGNATURES_DIRECTORY_PATH,
9
10
  SIGNATURES_DIRECTORY_TYPE,
@@ -128,7 +129,7 @@ export const headerRules = (
128
129
  }
129
130
  // Published skills live at the deployment base, and the RFC wants their
130
131
  // archives served as application/gzip explicitly.
131
- if (config.agents.skills) {
132
+ if (servesSkillsIndex(config)) {
132
133
  rules.push(
133
134
  {
134
135
  name: "Content-Type",
@@ -15,6 +15,8 @@ import {
15
15
  } from "../ai/web-bot-auth.ts";
16
16
  import { normalizeBasePath, normalizePath } from "../core/base-path.ts";
17
17
  import type { BlumeProject } from "../core/project-graph.ts";
18
+ import { compileRedirects, isPatternPath } from "../core/redirect-patterns.ts";
19
+ import type { CompiledRedirect } from "../core/redirect-patterns.ts";
18
20
  import type { ResolvedConfig } from "../core/schema.ts";
19
21
  import { SVG_ASSET_HEADERS, svgAssetPath } from "./headers.ts";
20
22
  import { distDir } from "./platforms/paths.ts";
@@ -106,11 +108,12 @@ const patternRules = (
106
108
  export type NodeRedirects = Record<string, [string, number]>;
107
109
 
108
110
  /**
109
- * The configured redirects, based the way the host matches them (see
111
+ * The configured exact redirects, based the way the host matches them (see
110
112
  * `platformRedirects`) and keyed for the wrapper's lookup, the `Location`
111
113
  * percent-encoded as Astro sends it. A redirect at a content route's own path
112
114
  * is left out: the prerendered page owns that URL, and the static handler
113
- * serves it before Astro's redirect ever could.
115
+ * serves it before Astro's redirect ever could. Patterns are compiled apart
116
+ * ({@link nodePatternRedirects}).
114
117
  */
115
118
  export const nodeRedirects = (project: BlumeProject): NodeRedirects => {
116
119
  const { config } = project;
@@ -125,7 +128,13 @@ export const nodeRedirects = (project: BlumeProject): NodeRedirects => {
125
128
  );
126
129
  return Object.fromEntries(
127
130
  platformRedirects(project)
128
- .filter((redirect) => !pages.has(normalizePath(redirect.from)))
131
+ .filter(
132
+ (redirect) =>
133
+ !(
134
+ isPatternPath(redirect.from) ||
135
+ pages.has(normalizePath(redirect.from))
136
+ )
137
+ )
129
138
  .map((redirect) => [
130
139
  normalizePath(redirect.from),
131
140
  [encodeURI(redirect.to), redirect.status],
@@ -133,12 +142,27 @@ export const nodeRedirects = (project: BlumeProject): NodeRedirects => {
133
142
  );
134
143
  };
135
144
 
145
+ /**
146
+ * The configured pattern redirects (`/beta/:slug*`), based the way the host
147
+ * matches them and compiled for the wrapper, which tries them in order after
148
+ * the exact ones. None can match a page: the scan rejects a pattern that
149
+ * does (`BLUME_REDIRECT_MATCHES_PAGE`).
150
+ */
151
+ export const nodePatternRedirects = (
152
+ project: BlumeProject
153
+ ): CompiledRedirect[] =>
154
+ project.config.redirects.length === 0
155
+ ? []
156
+ : compileRedirects(platformRedirects(project));
157
+
136
158
  /** What the wrapper needs beyond the header rules. */
137
159
  export interface NodeEntryOptions {
138
160
  /** `deployment.base`, normalized (`""` at the root). */
139
161
  base?: string;
140
162
  /** The configured redirects, from {@link nodeRedirects}. */
141
163
  redirects?: NodeRedirects;
164
+ /** The configured pattern redirects, from {@link nodePatternRedirects}. */
165
+ patternRedirects?: CompiledRedirect[];
142
166
  }
143
167
 
144
168
  /**
@@ -177,6 +201,9 @@ const BASE = ${JSON.stringify(options.base ?? "")};
177
201
  const RULES = new Map(${JSON.stringify(exactRules(rules))});
178
202
  const PATTERNS = ${JSON.stringify(patternRules(rules))};
179
203
  const REDIRECTS = ${JSON.stringify(options.redirects ?? {})};
204
+ const PATTERN_REDIRECTS = ${JSON.stringify(options.patternRedirects ?? [])}.map(
205
+ ([source, location, status]) => [new RegExp(source, "u"), location, status]
206
+ );
180
207
  const safeDecode = (path) => {
181
208
  try {
182
209
  return decodeURIComponent(path);
@@ -210,16 +237,37 @@ const applyHeaders = (req, res) => {
210
237
  }
211
238
  }
212
239
  };
240
+ // A pattern redirect's Location: its template, each $n filled with that
241
+ // capture, encoded so it can't grow a query or fragment.
242
+ const fillLocation = (location, match) =>
243
+ location.replace(/\\$(\\d+)/gu, (_reference, index) =>
244
+ encodeURI(match[Number(index)] ?? "")
245
+ .replaceAll("?", "%3F")
246
+ .replaceAll("#", "%23")
247
+ );
248
+ // The configured redirect for a decoded path: an exact one, else the first
249
+ // pattern that matches it.
250
+ const redirectFor = (path) => {
251
+ const trimmed = path !== "/" && path.endsWith("/") ? path.slice(0, -1) : path;
252
+ if (Object.hasOwn(REDIRECTS, trimmed)) {
253
+ return REDIRECTS[trimmed];
254
+ }
255
+ for (const [pattern, location, status] of PATTERN_REDIRECTS) {
256
+ const match = pattern.exec(path);
257
+ if (match) {
258
+ return [fillLocation(location, match), status];
259
+ }
260
+ }
261
+ return null;
262
+ };
213
263
  // Answer a configured redirect with its exact status; Astro's handler would
214
264
  // send a 301 (a 308 for other methods) whatever the configured one.
215
265
  const answerRedirect = (req, res) => {
216
- const path = urlPath(req);
217
- const trimmed = path !== "/" && path.endsWith("/") ? path.slice(0, -1) : path;
218
- const key = safeDecode(trimmed);
219
- if (!Object.hasOwn(REDIRECTS, key)) {
266
+ const redirect = redirectFor(safeDecode(urlPath(req)));
267
+ if (redirect === null) {
220
268
  return false;
221
269
  }
222
- const [location, status] = REDIRECTS[key];
270
+ const [location, status] = redirect;
223
271
  res.writeHead(status, { location });
224
272
  res.end();
225
273
  return true;
@@ -291,7 +339,12 @@ export const wrapNodeEntry = async (
291
339
  : []),
292
340
  ];
293
341
  const redirects = nodeRedirects(project);
294
- if (rules.length === 0 && Object.keys(redirects).length === 0) {
342
+ const patternRedirects = nodePatternRedirects(project);
343
+ if (
344
+ rules.length === 0 &&
345
+ Object.keys(redirects).length === 0 &&
346
+ patternRedirects.length === 0
347
+ ) {
295
348
  return;
296
349
  }
297
350
  const serverDir = join(distDir(project.context), "server");
@@ -311,6 +364,7 @@ export const wrapNodeEntry = async (
311
364
  entry,
312
365
  nodeEntryWrapper(rules, {
313
366
  base: normalizeBasePath(project.config.deployment.options.base),
367
+ patternRedirects,
314
368
  redirects,
315
369
  }),
316
370
  "utf-8"
@@ -14,6 +14,7 @@ import {
14
14
  import { normalizeBasePath } from "../../core/base-path.ts";
15
15
  import type { BlumeProject } from "../../core/project-graph.ts";
16
16
  import type { ProjectContext } from "../../core/types.ts";
17
+ import { withRateLimitBinding } from "../../ratelimit/wrangler.ts";
17
18
  import { CLOUDFLARE_ADAPTER_PACKAGE } from "../adapters/cloudflare.ts";
18
19
  import {
19
20
  injectWorkerNegotiation,
@@ -274,17 +275,51 @@ export const nameCloudflareWorker = async (
274
275
  };
275
276
 
276
277
  /**
277
- * Cloudflare Workers and Pages. A server build emits the Worker into
278
- * `dist/server` and serves `dist/client` through the ASSETS binding, which
279
- * honors `_headers` from that directory exactly as Pages does — so the file
280
- * applies to both output modes here. Without a configured driver the adapter
281
- * force-enables KV-backed sessions and declares a `SESSION` binding that
282
- * `wrangler deploy` then demands a namespace for, even though Blume never
283
- * reads `Astro.session`; `session: false` opts the project out.
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
+
307
+ /**
308
+ * Cloudflare. A server build targets Workers only (`@astrojs/cloudflare`
309
+ * dropped Pages in v13): it emits the Worker into `dist/server` and serves
310
+ * `dist/client` through the ASSETS binding, which honors `_headers` from that
311
+ * directory exactly as Pages does — so the file applies to both output modes
312
+ * here. A static build never loads the adapter; its `dist/` deploys to Pages,
313
+ * whose build environment `env` detects, or to Workers static assets (Workers
314
+ * Builds exposes no site URL to detect). Without a configured driver the
315
+ * adapter force-enables KV-backed sessions and declares a `SESSION` binding
316
+ * that `wrangler deploy` then demands a namespace for, even though Blume
317
+ * never reads `Astro.session`; `session: false` opts the project out.
284
318
  */
285
319
  export const cloudflarePlatform: DeployPlatform = {
286
320
  astro: {
287
- config: { session: false },
321
+ config: () => ({ session: false }),
322
+ configOptions: [],
288
323
  options: adapterOptions,
289
324
  package: CLOUDFLARE_ADAPTER_PACKAGE,
290
325
  },
@@ -298,6 +333,7 @@ export const cloudflarePlatform: DeployPlatform = {
298
333
  if (!isolated) {
299
334
  await nameCloudflareWorker(project, log);
300
335
  await emitCloudflareNegotiation(project, log);
336
+ await bindCloudflareRateLimit(project, log);
301
337
  await emitCloudflareDeployConfig(project.context);
302
338
  }
303
339
  return true;
@@ -318,5 +354,5 @@ export const cloudflarePlatform: DeployPlatform = {
318
354
  // build, and hoists its own `_headers`/`_redirects` back up to `dist/client`.
319
355
  serverClientUnderBase: true,
320
356
  serverOutputDir: distDir,
321
- serverStaticDir: clientDir,
357
+ serverStaticDir: (context, base) => join(clientDir(context), base),
322
358
  };
@@ -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 —
@@ -87,7 +118,8 @@ export const emitNetlifyHeaders = async (
87
118
  */
88
119
  export const netlifyPlatform: DeployPlatform = {
89
120
  astro: {
90
- config: {},
121
+ config: () => ({}),
122
+ configOptions: [],
91
123
  options: () => ({}),
92
124
  package: NETLIFY_ADAPTER_PACKAGE,
93
125
  },
@@ -106,6 +138,7 @@ export const netlifyPlatform: DeployPlatform = {
106
138
  // never surfaces it.
107
139
  if (!isolated) {
108
140
  await emitNetlifyHeaders(project, log);
141
+ await emitNetlifyPatternRedirects(project);
109
142
  }
110
143
  return true;
111
144
  },
@@ -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
  },