blume 2.0.1 → 2.0.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 (410) hide show
  1. package/CHANGELOG.md +82 -0
  2. package/dist/cli/{chunk-vtk4a6dg.js → chunk-00gs3wqs.js} +1 -1
  3. package/dist/cli/{chunk-fa25z98p.js → chunk-273ygyr4.js} +4 -3
  4. package/dist/cli/chunk-273ygyr4.js.map +11 -0
  5. package/dist/cli/{chunk-yw7dm696.js → chunk-3yce002v.js} +21 -8
  6. package/dist/cli/{chunk-yw7dm696.js.map → chunk-3yce002v.js.map} +3 -3
  7. package/dist/cli/{chunk-yt5n7ppj.js → chunk-4e9b9ra6.js} +17 -7
  8. package/dist/cli/chunk-4e9b9ra6.js.map +10 -0
  9. package/dist/cli/{chunk-fs23ddbb.js → chunk-5r8g91qn.js} +352 -676
  10. package/dist/cli/chunk-5r8g91qn.js.map +34 -0
  11. package/dist/cli/{chunk-6vm74dry.js → chunk-6dtt0zfn.js} +8 -8
  12. package/dist/cli/{chunk-qwsrynx5.js → chunk-6k4ftwze.js} +38 -19
  13. package/dist/cli/{chunk-qwsrynx5.js.map → chunk-6k4ftwze.js.map} +4 -4
  14. package/dist/cli/{chunk-qs4q5p4e.js → chunk-7mbqtmgb.js} +16 -7
  15. package/dist/cli/chunk-7mbqtmgb.js.map +10 -0
  16. package/dist/cli/{chunk-q5163e60.js → chunk-7vtckvaw.js} +21 -19
  17. package/dist/cli/chunk-7vtckvaw.js.map +11 -0
  18. package/dist/cli/{chunk-kdp5q7ke.js → chunk-8g8ytmgx.js} +17 -18
  19. package/dist/cli/{chunk-kdp5q7ke.js.map → chunk-8g8ytmgx.js.map} +2 -2
  20. package/dist/cli/{chunk-epjnccmv.js → chunk-91ws1n6j.js} +18 -15
  21. package/dist/cli/chunk-91ws1n6j.js.map +10 -0
  22. package/dist/cli/{chunk-fxypxtvm.js → chunk-bbnwccaz.js} +2 -2
  23. package/dist/cli/{chunk-m3vmjgmq.js → chunk-bfwp9vp6.js} +16 -8
  24. package/dist/cli/chunk-bfwp9vp6.js.map +10 -0
  25. package/dist/cli/{chunk-f2z5v128.js → chunk-d1v5rhy0.js} +14 -15
  26. package/dist/cli/{chunk-f2z5v128.js.map → chunk-d1v5rhy0.js.map} +2 -2
  27. package/dist/cli/{chunk-6crbhc3x.js → chunk-ddndchfr.js} +21 -6
  28. package/dist/cli/chunk-ddndchfr.js.map +14 -0
  29. package/dist/cli/{chunk-ce574jw2.js → chunk-esh98wmb.js} +1 -1
  30. package/dist/cli/{chunk-zxcczpyx.js → chunk-fsmrqk8a.js} +1 -1
  31. package/dist/cli/{chunk-hdpx1tax.js → chunk-g698a744.js} +5 -5
  32. package/dist/cli/{chunk-jts8mvcz.js → chunk-gs7r695n.js} +9 -3
  33. package/dist/cli/{chunk-jts8mvcz.js.map → chunk-gs7r695n.js.map} +3 -3
  34. package/dist/cli/{chunk-zxh4d9vy.js → chunk-h2ez8dzb.js} +4 -4
  35. package/dist/cli/{chunk-5shv93fd.js → chunk-h7k3nq3v.js} +2 -2
  36. package/dist/cli/{chunk-79jhk4py.js → chunk-hqp2ajnh.js} +251 -122
  37. package/dist/cli/chunk-hqp2ajnh.js.map +35 -0
  38. package/dist/cli/{chunk-2hn4b8z7.js → chunk-hr8ne106.js} +109 -40
  39. package/dist/cli/chunk-hr8ne106.js.map +13 -0
  40. package/dist/cli/{chunk-6hsn950k.js → chunk-j85scx15.js} +62 -18
  41. package/dist/cli/chunk-j85scx15.js.map +10 -0
  42. package/dist/cli/{chunk-s1p84fyh.js → chunk-k7pj68a8.js} +63 -22
  43. package/dist/cli/chunk-k7pj68a8.js.map +11 -0
  44. package/dist/cli/{chunk-ah61y8py.js → chunk-mqc662a6.js} +2 -2
  45. package/dist/cli/{chunk-ch6g3ar0.js → chunk-n1yg3tj3.js} +4 -4
  46. package/dist/cli/{chunk-mb2919y2.js → chunk-nfcyttvj.js} +17 -6
  47. package/dist/cli/chunk-nfcyttvj.js.map +10 -0
  48. package/dist/cli/{chunk-kpf8rrjc.js → chunk-pbg5a4s3.js} +56 -24
  49. package/dist/cli/chunk-pbg5a4s3.js.map +19 -0
  50. package/dist/cli/{chunk-jwyddg7y.js → chunk-ppzjqwx2.js} +21 -14
  51. package/dist/cli/{chunk-jwyddg7y.js.map → chunk-ppzjqwx2.js.map} +4 -4
  52. package/dist/cli/{chunk-dh8cwk36.js → chunk-qkb5a8sa.js} +24 -9
  53. package/dist/cli/chunk-qkb5a8sa.js.map +10 -0
  54. package/dist/cli/{chunk-wm7js3j9.js → chunk-sqw4ekg1.js} +2 -2
  55. package/dist/cli/{chunk-qkqwkpte.js → chunk-v6ya5kcb.js} +3083 -953
  56. package/dist/cli/chunk-v6ya5kcb.js.map +189 -0
  57. package/dist/cli/{chunk-fz5wtpmh.js → chunk-w4bxdvsa.js} +18 -15
  58. package/dist/cli/chunk-w4bxdvsa.js.map +10 -0
  59. package/dist/cli/{chunk-27g6wdth.js → chunk-wdrt2k2v.js} +2 -2
  60. package/dist/cli/{chunk-wgm7m9qk.js → chunk-xh43dwgw.js} +158 -43
  61. package/dist/cli/chunk-xh43dwgw.js.map +36 -0
  62. package/dist/cli/{chunk-s6jhgk0q.js → chunk-zp79m0ts.js} +2 -2
  63. package/dist/cli/index.js +160 -35
  64. package/dist/cli/index.js.map +4 -4
  65. package/dist/types/ai/agent-surface.d.ts +32 -0
  66. package/dist/types/ai/api-catalog.d.ts +7 -1
  67. package/dist/types/ai/ask-context.d.ts +7 -0
  68. package/dist/types/ai/component-markdown.d.ts +4 -4
  69. package/dist/types/ai/relative-links.d.ts +9 -4
  70. package/dist/types/ai/static-expression.d.ts +28 -0
  71. package/dist/types/analytics/databuddy.d.ts +43 -0
  72. package/dist/types/analytics/index.d.ts +2 -0
  73. package/dist/types/analytics/schema.d.ts +14 -0
  74. package/dist/types/cli/env.d.ts +5 -0
  75. package/dist/types/cli/init/scaffold.d.ts +19 -3
  76. package/dist/types/cli/init/starter-spec.d.ts +11 -0
  77. package/dist/types/core/base-path.d.ts +9 -0
  78. package/dist/types/core/config-input.d.ts +4 -4
  79. package/dist/types/core/config.d.ts +2 -2
  80. package/dist/types/core/graph.d.ts +2 -0
  81. package/dist/types/core/i18n-ui.d.ts +31 -0
  82. package/dist/types/core/i18n.d.ts +7 -1
  83. package/dist/types/core/links.d.ts +3 -1
  84. package/dist/types/core/load-module.d.ts +10 -0
  85. package/dist/types/core/locale-links.d.ts +12 -2
  86. package/dist/types/core/meta.d.ts +5 -1
  87. package/dist/types/core/nav-diagnostics.d.ts +10 -0
  88. package/dist/types/core/navigation.d.ts +38 -0
  89. package/dist/types/core/ordering-prefix.d.ts +4 -0
  90. package/dist/types/core/safe-links.d.ts +3 -1
  91. package/dist/types/core/schema.d.ts +52 -13
  92. package/dist/types/core/sources/github-releases.d.ts +5 -0
  93. package/dist/types/core/sources/lower.d.ts +14 -2
  94. package/dist/types/core/sources/normalize.d.ts +10 -1
  95. package/dist/types/core/sources/remote.d.ts +11 -1
  96. package/dist/types/core/sources/resolve.d.ts +12 -0
  97. package/dist/types/core/sources/types.d.ts +31 -0
  98. package/dist/types/core/types.d.ts +9 -0
  99. package/dist/types/deploy/adapters/node.d.ts +5 -2
  100. package/dist/types/deploy/adapters/types.d.ts +7 -0
  101. package/dist/types/deploy/cloudflare-negotiation.d.ts +3 -2
  102. package/dist/types/deploy/headers.d.ts +31 -7
  103. package/dist/types/deploy/node-headers.d.ts +43 -8
  104. package/dist/types/deploy/platforms/netlify.d.ts +27 -2
  105. package/dist/types/deploy/platforms/node.d.ts +6 -5
  106. package/dist/types/deploy/platforms/types.d.ts +7 -0
  107. package/dist/types/deploy/platforms/vercel.d.ts +3 -2
  108. package/dist/types/deploy/redirects.d.ts +7 -2
  109. package/dist/types/deploy/vercel-negotiation.d.ts +3 -2
  110. package/dist/types/openapi/model.d.ts +20 -6
  111. package/dist/types/search/sync/algolia.d.ts +3 -1
  112. package/docs/01-quickstart.mdx +3 -2
  113. package/docs/02-deployment.mdx +17 -8
  114. package/docs/08-faq.mdx +9 -2
  115. package/docs/advanced/changelog.mdx +1 -1
  116. package/docs/advanced/custom-pages.mdx +4 -2
  117. package/docs/cli/audit.mdx +2 -2
  118. package/docs/cli/doctor.mdx +2 -2
  119. package/docs/cli/evals.mdx +2 -2
  120. package/docs/cli/index.mdx +4 -1
  121. package/docs/cli/translate.mdx +1 -1
  122. package/docs/configuration/analytics.mdx +23 -2
  123. package/docs/configuration/assistant.mdx +1 -1
  124. package/docs/configuration/customization.mdx +4 -3
  125. package/docs/configuration/index.mdx +5 -3
  126. package/docs/configuration/search.mdx +1 -1
  127. package/docs/content/components.mdx +1 -1
  128. package/docs/content/frontmatter.mdx +5 -1
  129. package/docs/content/i18n.mdx +1 -1
  130. package/docs/content/index.mdx +4 -2
  131. package/docs/content/meta.mdx +5 -3
  132. package/docs/content/navigation.mdx +29 -4
  133. package/docs/content/sources.mdx +18 -12
  134. package/docs/content/versioning.mdx +1 -0
  135. package/docs/discoverability/agent-discovery.mdx +21 -7
  136. package/docs/discoverability/index.mdx +2 -2
  137. package/docs/discoverability/llms-txt.mdx +2 -5
  138. package/docs/discoverability/markdown.mdx +5 -3
  139. package/docs/discoverability/mcp.mdx +4 -0
  140. package/docs/discoverability/metadata.mdx +3 -2
  141. package/docs/discoverability/open-graph.mdx +6 -4
  142. package/docs/discoverability/rss.mdx +3 -1
  143. package/docs/references/asyncapi.mdx +1 -1
  144. package/docs/references/graphql.mdx +2 -2
  145. package/docs/references/openapi.mdx +3 -3
  146. package/package.json +1 -1
  147. package/skills/blume-migrate/SKILL.md +6 -6
  148. package/skills/blume-migrate/references/docusaurus.md +6 -6
  149. package/skills/blume-migrate/references/mintlify.md +3 -3
  150. package/skills/blume-migrate/references/monorepo.md +1 -1
  151. package/skills/blume-migrate/references/nextra.md +1 -1
  152. package/skills/blume-migrate/references/starlight.md +5 -5
  153. package/skills/blume-migrate/scripts/mintlify-codemod.mjs +2 -2
  154. package/skills/blume-update-docs/references/audit-checklist.md +1 -1
  155. package/src/ai/agent-surface.ts +56 -0
  156. package/src/ai/api/spec.ts +15 -2
  157. package/src/ai/api-catalog.ts +7 -1
  158. package/src/ai/ask-context.ts +14 -2
  159. package/src/ai/ask-data.ts +26 -12
  160. package/src/ai/component-markdown.ts +43 -37
  161. package/src/ai/link-headers.ts +8 -3
  162. package/src/ai/llms.ts +4 -3
  163. package/src/ai/markdown.ts +23 -11
  164. package/src/ai/mcp/server.ts +78 -7
  165. package/src/ai/relative-links.ts +78 -10
  166. package/src/ai/static-expression.ts +416 -0
  167. package/src/ai/visibility.ts +45 -14
  168. package/src/analytics/databuddy.ts +67 -0
  169. package/src/analytics/head.ts +4 -0
  170. package/src/analytics/index.ts +2 -0
  171. package/src/analytics/posthog.ts +21 -3
  172. package/src/analytics/schema.ts +2 -0
  173. package/src/astro/generate.ts +71 -27
  174. package/src/astro/templates.ts +61 -23
  175. package/src/audit/catalog.ts +2 -2
  176. package/src/audit/checks/assets.ts +25 -5
  177. package/src/audit/checks/content.ts +20 -2
  178. package/src/audit/checks/i18n.ts +46 -8
  179. package/src/audit/checks/indexability.ts +39 -19
  180. package/src/audit/checks/links.ts +14 -0
  181. package/src/audit/checks/llms.ts +6 -3
  182. package/src/audit/checks/network.ts +3 -1
  183. package/src/audit/checks/og-image.ts +10 -0
  184. package/src/audit/checks/robots.ts +6 -1
  185. package/src/audit/checks/sitemap.ts +56 -31
  186. package/src/audit/checks/social.ts +21 -2
  187. package/src/audit/crawl.ts +88 -15
  188. package/src/audit/report.ts +54 -17
  189. package/src/audit/run.ts +1 -0
  190. package/src/audit/types.ts +18 -0
  191. package/src/audit/url.ts +37 -6
  192. package/src/cli/build-failure.ts +50 -0
  193. package/src/cli/commands/audit.ts +7 -0
  194. package/src/cli/commands/build.ts +19 -5
  195. package/src/cli/commands/check.ts +2 -0
  196. package/src/cli/commands/eval.ts +1 -7
  197. package/src/cli/commands/init.ts +15 -5
  198. package/src/cli/commands/preview.ts +15 -0
  199. package/src/cli/commands/sync.ts +2 -0
  200. package/src/cli/commands/translate.ts +9 -6
  201. package/src/cli/commands/upgrade.ts +11 -0
  202. package/src/cli/eject-scripts.ts +32 -7
  203. package/src/cli/env.ts +12 -1
  204. package/src/cli/init/scaffold.ts +117 -11
  205. package/src/cli/init/starter-spec.ts +235 -0
  206. package/src/components/content/AccordionItem.astro +26 -22
  207. package/src/components/content/Badge.astro +2 -9
  208. package/src/components/content/Card.astro +2 -2
  209. package/src/components/content/Component.astro +9 -3
  210. package/src/components/content/Expandable.astro +5 -1
  211. package/src/components/content/Frame.astro +2 -7
  212. package/src/components/content/GithubInfo.astro +12 -2
  213. package/src/components/content/Prompt.astro +2 -7
  214. package/src/components/content/Tabs.astro +54 -5
  215. package/src/components/content/Tile.astro +1 -1
  216. package/src/components/content/Tooltip.astro +69 -7
  217. package/src/components/content/Tree.astro +7 -2
  218. package/src/components/content/TypeTable.astro +10 -5
  219. package/src/components/content/Update.astro +8 -2
  220. package/src/components/content/auto-type-table.ts +4 -1
  221. package/src/components/content/badge-color.ts +17 -0
  222. package/src/components/content/base-href.ts +18 -3
  223. package/src/components/content/inline-markdown.ts +27 -7
  224. package/src/components/copy-feedback.ts +35 -8
  225. package/src/components/islands/assistant.tsx +16 -2
  226. package/src/components/islands/hooks.ts +7 -2
  227. package/src/components/islands/webmcp.ts +12 -8
  228. package/src/components/layout/Banner.astro +23 -4
  229. package/src/components/layout/Header.astro +22 -9
  230. package/src/components/layout/Logo.astro +5 -0
  231. package/src/components/layout/NavSelector.astro +6 -1
  232. package/src/components/layout/NavTabMenu.astro +133 -0
  233. package/src/components/layout/NavTree.astro +15 -6
  234. package/src/components/layout/NavTreeCache.astro +5 -2
  235. package/src/components/layout/NavTreeScript.astro +45 -5
  236. package/src/components/layout/PageLayout.astro +22 -8
  237. package/src/components/layout/ReferenceLayout.astro +4 -1
  238. package/src/components/layout/RootLayout.astro +21 -8
  239. package/src/components/layout/Search.astro +5 -1
  240. package/src/components/layout/analytics-client.ts +2 -0
  241. package/src/components/openapi/GraphqlType.astro +11 -3
  242. package/src/components/openapi/MessageComposer.astro +1 -1
  243. package/src/components/openapi/Operation.astro +21 -5
  244. package/src/components/openapi/PanelTabs.astro +4 -1
  245. package/src/components/openapi/Playground.astro +8 -2
  246. package/src/components/openapi/RequestPanel.astro +9 -5
  247. package/src/components/openapi/SchemaProperty.astro +11 -33
  248. package/src/components/openapi/SchemaTable.astro +27 -52
  249. package/src/components/openapi/helpers.ts +90 -12
  250. package/src/components/openapi/message-composer.ts +8 -0
  251. package/src/components/openapi/message-model.ts +12 -2
  252. package/src/components/openapi/message.ts +4 -1
  253. package/src/components/openapi/operation-model.ts +40 -8
  254. package/src/components/openapi/panel.ts +29 -5
  255. package/src/components/openapi/playground-client.ts +37 -7
  256. package/src/components/openapi/playground-schema.ts +25 -6
  257. package/src/components/openapi/request.ts +2 -0
  258. package/src/components/openapi/schema-tree.ts +203 -0
  259. package/src/components/openapi/snippets.ts +30 -15
  260. package/src/components/openapi/validate-json.ts +1 -1
  261. package/src/core/base-path.ts +15 -0
  262. package/src/core/config-input.ts +4 -4
  263. package/src/core/config.ts +18 -4
  264. package/src/core/diagnostics.ts +217 -30
  265. package/src/core/graph.ts +120 -11
  266. package/src/core/i18n-ui.ts +39 -2
  267. package/src/core/i18n.ts +13 -2
  268. package/src/core/links.ts +7 -4
  269. package/src/core/load-module.ts +20 -0
  270. package/src/core/locale-links.ts +17 -17
  271. package/src/core/manifest.ts +3 -2
  272. package/src/core/meta.ts +69 -6
  273. package/src/core/nav-diagnostics.ts +56 -1
  274. package/src/core/navigation.ts +245 -76
  275. package/src/core/ordering-prefix.ts +27 -0
  276. package/src/core/project-graph.ts +23 -1
  277. package/src/core/safe-href.ts +53 -1
  278. package/src/core/safe-links.ts +11 -2
  279. package/src/core/schema.ts +70 -14
  280. package/src/core/sources/assets.ts +83 -41
  281. package/src/core/sources/contentful-rich-text.ts +3 -2
  282. package/src/core/sources/contentful.ts +25 -12
  283. package/src/core/sources/filesystem.ts +5 -1
  284. package/src/core/sources/github-releases.ts +106 -5
  285. package/src/core/sources/lexical.ts +9 -4
  286. package/src/core/sources/lower.ts +79 -26
  287. package/src/core/sources/mdx-remote.ts +1 -0
  288. package/src/core/sources/normalize.ts +132 -30
  289. package/src/core/sources/notion.ts +26 -10
  290. package/src/core/sources/obsidian.ts +17 -3
  291. package/src/core/sources/payload.ts +1 -0
  292. package/src/core/sources/portable-text.ts +63 -44
  293. package/src/core/sources/remote.ts +18 -2
  294. package/src/core/sources/resolve.ts +52 -33
  295. package/src/core/sources/sanity.ts +1 -0
  296. package/src/core/sources/strapi-blocks.ts +9 -4
  297. package/src/core/sources/strapi.ts +1 -0
  298. package/src/core/sources/types.ts +31 -0
  299. package/src/core/types.ts +9 -0
  300. package/src/core/ui-packs/ar.ts +13 -0
  301. package/src/core/ui-packs/bg.ts +13 -0
  302. package/src/core/ui-packs/bn.ts +13 -0
  303. package/src/core/ui-packs/ca.ts +13 -0
  304. package/src/core/ui-packs/cs.ts +13 -0
  305. package/src/core/ui-packs/da.ts +13 -0
  306. package/src/core/ui-packs/de.ts +13 -0
  307. package/src/core/ui-packs/el.ts +13 -0
  308. package/src/core/ui-packs/es.ts +13 -0
  309. package/src/core/ui-packs/fa.ts +13 -0
  310. package/src/core/ui-packs/fi.ts +13 -0
  311. package/src/core/ui-packs/fr.ts +13 -0
  312. package/src/core/ui-packs/he.ts +13 -0
  313. package/src/core/ui-packs/hi.ts +13 -0
  314. package/src/core/ui-packs/hr.ts +13 -0
  315. package/src/core/ui-packs/hu.ts +13 -0
  316. package/src/core/ui-packs/id.ts +13 -0
  317. package/src/core/ui-packs/it.ts +13 -0
  318. package/src/core/ui-packs/ja.ts +13 -0
  319. package/src/core/ui-packs/ko.ts +13 -0
  320. package/src/core/ui-packs/nl.ts +13 -0
  321. package/src/core/ui-packs/no.ts +13 -0
  322. package/src/core/ui-packs/pl.ts +13 -0
  323. package/src/core/ui-packs/pt-br.ts +13 -0
  324. package/src/core/ui-packs/pt.ts +13 -0
  325. package/src/core/ui-packs/ro.ts +13 -0
  326. package/src/core/ui-packs/ru.ts +13 -0
  327. package/src/core/ui-packs/sk.ts +13 -0
  328. package/src/core/ui-packs/sr.ts +13 -0
  329. package/src/core/ui-packs/sv.ts +13 -0
  330. package/src/core/ui-packs/th.ts +13 -0
  331. package/src/core/ui-packs/tr.ts +13 -0
  332. package/src/core/ui-packs/uk.ts +13 -0
  333. package/src/core/ui-packs/vi.ts +13 -0
  334. package/src/core/ui-packs/zh-tw.ts +13 -0
  335. package/src/core/ui-packs/zh.ts +13 -0
  336. package/src/core/version-cut.ts +17 -2
  337. package/src/core/versions.ts +4 -1
  338. package/src/deploy/adapters/node.ts +5 -2
  339. package/src/deploy/adapters/registry.ts +2 -1
  340. package/src/deploy/adapters/types.ts +13 -1
  341. package/src/deploy/artifacts.ts +25 -5
  342. package/src/deploy/cloudflare-negotiation.ts +23 -4
  343. package/src/deploy/headers.ts +67 -51
  344. package/src/deploy/node-headers.ts +148 -27
  345. package/src/deploy/platforms/cloudflare.ts +1 -0
  346. package/src/deploy/platforms/netlify.ts +82 -5
  347. package/src/deploy/platforms/node.ts +7 -5
  348. package/src/deploy/platforms/static.ts +1 -0
  349. package/src/deploy/platforms/types.ts +7 -0
  350. package/src/deploy/platforms/vercel.ts +9 -3
  351. package/src/deploy/redirects.ts +14 -3
  352. package/src/deploy/vercel-negotiation.ts +35 -3
  353. package/src/eval/agents.ts +10 -2
  354. package/src/eval/run.ts +25 -0
  355. package/src/markdown/base-links.ts +55 -8
  356. package/src/markdown/index.ts +6 -3
  357. package/src/markdown/relative-links.ts +3 -23
  358. package/src/markdown/route-snapshot.ts +37 -0
  359. package/src/og/card.ts +20 -4
  360. package/src/og/derive.ts +145 -4
  361. package/src/openapi/graphql-build.ts +28 -2
  362. package/src/openapi/model.ts +77 -13
  363. package/src/openapi/render-mdx.ts +10 -1
  364. package/src/registry/eject.ts +119 -22
  365. package/src/search/documents.ts +22 -1
  366. package/src/search/sync/algolia.ts +36 -2
  367. package/src/sources/registry.ts +5 -0
  368. package/src/theme/entry.ts +11 -4
  369. package/src/translate/agents.ts +6 -1
  370. package/src/translate/ledger.ts +26 -3
  371. package/src/translate/meta.ts +11 -3
  372. package/src/translate/run.ts +11 -5
  373. package/src/translate/validate.ts +10 -1
  374. package/src/translate/work-list.ts +36 -3
  375. package/src/upgrade/upgrade.ts +36 -4
  376. package/dist/cli/chunk-2hn4b8z7.js.map +0 -12
  377. package/dist/cli/chunk-6crbhc3x.js.map +0 -14
  378. package/dist/cli/chunk-6hsn950k.js.map +0 -10
  379. package/dist/cli/chunk-79jhk4py.js.map +0 -35
  380. package/dist/cli/chunk-82bbrxdn.js +0 -51
  381. package/dist/cli/chunk-82bbrxdn.js.map +0 -10
  382. package/dist/cli/chunk-abh8yjkn.js +0 -31
  383. package/dist/cli/chunk-abh8yjkn.js.map +0 -10
  384. package/dist/cli/chunk-dh8cwk36.js.map +0 -10
  385. package/dist/cli/chunk-epjnccmv.js.map +0 -10
  386. package/dist/cli/chunk-fa25z98p.js.map +0 -11
  387. package/dist/cli/chunk-fs23ddbb.js.map +0 -35
  388. package/dist/cli/chunk-fz5wtpmh.js.map +0 -10
  389. package/dist/cli/chunk-kpf8rrjc.js.map +0 -19
  390. package/dist/cli/chunk-m3vmjgmq.js.map +0 -10
  391. package/dist/cli/chunk-mb2919y2.js.map +0 -10
  392. package/dist/cli/chunk-q5163e60.js.map +0 -11
  393. package/dist/cli/chunk-qkqwkpte.js.map +0 -182
  394. package/dist/cli/chunk-qs4q5p4e.js.map +0 -10
  395. package/dist/cli/chunk-s1p84fyh.js.map +0 -10
  396. package/dist/cli/chunk-wgm7m9qk.js.map +0 -36
  397. package/dist/cli/chunk-yt5n7ppj.js.map +0 -10
  398. /package/dist/cli/{chunk-vtk4a6dg.js.map → chunk-00gs3wqs.js.map} +0 -0
  399. /package/dist/cli/{chunk-6vm74dry.js.map → chunk-6dtt0zfn.js.map} +0 -0
  400. /package/dist/cli/{chunk-fxypxtvm.js.map → chunk-bbnwccaz.js.map} +0 -0
  401. /package/dist/cli/{chunk-ce574jw2.js.map → chunk-esh98wmb.js.map} +0 -0
  402. /package/dist/cli/{chunk-zxcczpyx.js.map → chunk-fsmrqk8a.js.map} +0 -0
  403. /package/dist/cli/{chunk-hdpx1tax.js.map → chunk-g698a744.js.map} +0 -0
  404. /package/dist/cli/{chunk-zxh4d9vy.js.map → chunk-h2ez8dzb.js.map} +0 -0
  405. /package/dist/cli/{chunk-5shv93fd.js.map → chunk-h7k3nq3v.js.map} +0 -0
  406. /package/dist/cli/{chunk-ah61y8py.js.map → chunk-mqc662a6.js.map} +0 -0
  407. /package/dist/cli/{chunk-ch6g3ar0.js.map → chunk-n1yg3tj3.js.map} +0 -0
  408. /package/dist/cli/{chunk-wm7js3j9.js.map → chunk-sqw4ekg1.js.map} +0 -0
  409. /package/dist/cli/{chunk-27g6wdth.js.map → chunk-wdrt2k2v.js.map} +0 -0
  410. /package/dist/cli/{chunk-s6jhgk0q.js.map → chunk-zp79m0ts.js.map} +0 -0
@@ -44,9 +44,20 @@ export const sv: UIStringsOverride = {
44
44
  title: "Ändringslogg",
45
45
  },
46
46
  content: {
47
+ code: "Kod",
47
48
  copyColor: "Kopiera färgen {name} {value}",
48
49
  copyPrompt: "Kopiera prompt",
50
+ default: "Standard",
49
51
  diagramError: "Det här diagrammet kunde inte visas.",
52
+ forks: "Forkar",
53
+ preview: "Förhandsgranskning",
54
+ prop: "Egenskap",
55
+ selectTab: "Välj flik",
56
+ showMore: "Visa mer",
57
+ stars: "Stjärnor",
58
+ tab: "Flik {n}",
59
+ type: "Typ",
60
+ update: "Uppdatering",
50
61
  },
51
62
  feedback: {
52
63
  no: "Nej",
@@ -88,8 +99,10 @@ export const sv: UIStringsOverride = {
88
99
  assistantHint: "Få ett omedelbart svar från AI",
89
100
  button: "Sök",
90
101
  devOnly: "Sökning är tillgänglig i produktionsbygget.",
102
+ docs: "Dokumentation",
91
103
  error: "Något gick fel. Försök igen.",
92
104
  label: "Sök i dokumentationen",
105
+ latest: "senaste",
93
106
  navigate: "navigera",
94
107
  noResults: "Inga resultat hittades.",
95
108
  open: "öppna",
@@ -43,9 +43,20 @@ export const th: UIStringsOverride = {
43
43
  title: "บันทึกการเปลี่ยนแปลง",
44
44
  },
45
45
  content: {
46
+ code: "โค้ด",
46
47
  copyColor: "คัดลอกสี {name} {value}",
47
48
  copyPrompt: "คัดลอกพรอมต์",
49
+ default: "ค่าเริ่มต้น",
48
50
  diagramError: "ไม่สามารถแสดงไดอะแกรมนี้ได้",
51
+ forks: "ฟอร์ก",
52
+ preview: "ตัวอย่าง",
53
+ prop: "คุณสมบัติ",
54
+ selectTab: "เลือกแท็บ",
55
+ showMore: "แสดงเพิ่มเติม",
56
+ stars: "ดาว",
57
+ tab: "แท็บ {n}",
58
+ type: "ชนิด",
59
+ update: "อัปเดต",
49
60
  },
50
61
  feedback: {
51
62
  no: "ไม่",
@@ -87,8 +98,10 @@ export const th: UIStringsOverride = {
87
98
  assistantHint: "รับคำตอบทันทีจาก AI",
88
99
  button: "ค้นหา",
89
100
  devOnly: "การค้นหาพร้อมใช้งานในบิลด์โปรดักชัน",
101
+ docs: "เอกสาร",
90
102
  error: "เกิดข้อผิดพลาดบางอย่าง โปรดลองอีกครั้ง",
91
103
  label: "ค้นหาเอกสาร",
104
+ latest: "ล่าสุด",
92
105
  navigate: "นำทาง",
93
106
  noResults: "ไม่พบผลลัพธ์",
94
107
  open: "เปิด",
@@ -44,9 +44,20 @@ export const tr: UIStringsOverride = {
44
44
  title: "Değişiklik günlüğü",
45
45
  },
46
46
  content: {
47
+ code: "Kod",
47
48
  copyColor: "Rengi kopyala: {name} {value}",
48
49
  copyPrompt: "İstemi kopyala",
50
+ default: "Varsayılan",
49
51
  diagramError: "Bu diyagram görüntülenemedi.",
52
+ forks: "Çatallar",
53
+ preview: "Önizleme",
54
+ prop: "Özellik",
55
+ selectTab: "Sekme seç",
56
+ showMore: "Daha fazla göster",
57
+ stars: "Yıldızlar",
58
+ tab: "Sekme {n}",
59
+ type: "Tür",
60
+ update: "Güncelleme",
50
61
  },
51
62
  feedback: {
52
63
  no: "Hayır",
@@ -88,8 +99,10 @@ export const tr: UIStringsOverride = {
88
99
  assistantHint: "Yapay zekâdan anında yanıt alın",
89
100
  button: "Ara",
90
101
  devOnly: "Arama, üretim derlemesinde kullanılabilir.",
102
+ docs: "Belgeler",
91
103
  error: "Bir şeyler ters gitti. Lütfen tekrar deneyin.",
92
104
  label: "Belgelerde ara",
105
+ latest: "en son",
93
106
  navigate: "gezin",
94
107
  noResults: "Sonuç bulunamadı.",
95
108
  open: "aç",
@@ -44,9 +44,20 @@ export const uk: UIStringsOverride = {
44
44
  title: "Журнал змін",
45
45
  },
46
46
  content: {
47
+ code: "Код",
47
48
  copyColor: "Скопіювати колір {name} {value}",
48
49
  copyPrompt: "Скопіювати промпт",
50
+ default: "За замовчуванням",
49
51
  diagramError: "Не вдалося відобразити цю діаграму.",
52
+ forks: "Форки",
53
+ preview: "Попередній перегляд",
54
+ prop: "Властивість",
55
+ selectTab: "Вибрати вкладку",
56
+ showMore: "Показати більше",
57
+ stars: "Зірки",
58
+ tab: "Вкладка {n}",
59
+ type: "Тип",
60
+ update: "Оновлення",
50
61
  },
51
62
  feedback: {
52
63
  no: "Ні",
@@ -88,8 +99,10 @@ export const uk: UIStringsOverride = {
88
99
  assistantHint: "Отримайте миттєву відповідь від ШІ",
89
100
  button: "Пошук",
90
101
  devOnly: "Пошук доступний у production-збірці.",
102
+ docs: "Документація",
91
103
  error: "Щось пішло не так. Спробуйте ще раз.",
92
104
  label: "Пошук у документації",
105
+ latest: "остання",
93
106
  navigate: "навігація",
94
107
  noResults: "Нічого не знайдено.",
95
108
  open: "відкрити",
@@ -43,9 +43,20 @@ export const vi: UIStringsOverride = {
43
43
  title: "Nhật ký thay đổi",
44
44
  },
45
45
  content: {
46
+ code: "Mã",
46
47
  copyColor: "Sao chép màu {name} {value}",
47
48
  copyPrompt: "Sao chép prompt",
49
+ default: "Mặc định",
48
50
  diagramError: "Không thể hiển thị sơ đồ này.",
51
+ forks: "Fork",
52
+ preview: "Xem trước",
53
+ prop: "Thuộc tính",
54
+ selectTab: "Chọn tab",
55
+ showMore: "Hiển thị thêm",
56
+ stars: "Sao",
57
+ tab: "Tab {n}",
58
+ type: "Kiểu",
59
+ update: "Cập nhật",
49
60
  },
50
61
  feedback: {
51
62
  no: "Không",
@@ -87,8 +98,10 @@ export const vi: UIStringsOverride = {
87
98
  assistantHint: "Nhận câu trả lời tức thì từ AI",
88
99
  button: "Tìm kiếm",
89
100
  devOnly: "Tìm kiếm có sẵn trong bản dựng production.",
101
+ docs: "Tài liệu",
90
102
  error: "Đã xảy ra sự cố. Vui lòng thử lại.",
91
103
  label: "Tìm kiếm tài liệu",
104
+ latest: "mới nhất",
92
105
  navigate: "di chuyển",
93
106
  noResults: "Không tìm thấy kết quả.",
94
107
  open: "mở",
@@ -43,9 +43,20 @@ export const zhTW: UIStringsOverride = {
43
43
  title: "更新日誌",
44
44
  },
45
45
  content: {
46
+ code: "程式碼",
46
47
  copyColor: "複製顏色 {name} {value}",
47
48
  copyPrompt: "複製提示詞",
49
+ default: "預設值",
48
50
  diagramError: "無法顯示此圖表。",
51
+ forks: "複刻",
52
+ preview: "預覽",
53
+ prop: "屬性",
54
+ selectTab: "選擇分頁",
55
+ showMore: "顯示更多",
56
+ stars: "星標",
57
+ tab: "分頁 {n}",
58
+ type: "型別",
59
+ update: "更新",
49
60
  },
50
61
  feedback: {
51
62
  no: "沒有幫助",
@@ -87,8 +98,10 @@ export const zhTW: UIStringsOverride = {
87
98
  assistantHint: "從 AI 即時取得解答",
88
99
  button: "搜尋",
89
100
  devOnly: "搜尋在正式版組建中可用。",
101
+ docs: "文件",
90
102
  error: "發生了一些問題,請再試一次。",
91
103
  label: "搜尋文件",
104
+ latest: "最新",
92
105
  navigate: "導覽",
93
106
  noResults: "找不到結果。",
94
107
  open: "開啟",
@@ -43,9 +43,20 @@ export const zh: UIStringsOverride = {
43
43
  title: "更新日志",
44
44
  },
45
45
  content: {
46
+ code: "代码",
46
47
  copyColor: "复制颜色 {name} {value}",
47
48
  copyPrompt: "复制提示词",
49
+ default: "默认值",
48
50
  diagramError: "无法渲染此图表。",
51
+ forks: "复刻",
52
+ preview: "预览",
53
+ prop: "属性",
54
+ selectTab: "选择标签页",
55
+ showMore: "显示更多",
56
+ stars: "星标",
57
+ tab: "标签页 {n}",
58
+ type: "类型",
59
+ update: "更新",
49
60
  },
50
61
  feedback: {
51
62
  no: "没有帮助",
@@ -87,8 +98,10 @@ export const zh: UIStringsOverride = {
87
98
  assistantHint: "从 AI 即时获取答案",
88
99
  button: "搜索",
89
100
  devOnly: "搜索在生产构建中可用。",
101
+ docs: "文档",
90
102
  error: "出了点问题,请重试。",
91
103
  label: "搜索文档",
104
+ latest: "最新",
92
105
  navigate: "导航",
93
106
  noResults: "未找到结果。",
94
107
  open: "打开",
@@ -65,6 +65,13 @@ const buildRouteRewrites = (
65
65
  const { basePath, i18n } = project.config;
66
66
  const { contentRoot } = project.context;
67
67
  const rewrites = new Map<string, string>();
68
+ // Under i18n, authors write links in the unprefixed form (`/guides/setup`)
69
+ // and rendering moves them into the reader's locale — a form no page is
70
+ // routed at when the default locale keeps its prefix
71
+ // (`hideDefaultLocalePrefix: false`). So it is mapped too, to the
72
+ // unprefixed snapshot route, which rendering localizes the same way. Page
73
+ // routes win where the two forms coincide.
74
+ const logical = new Map<string, string>();
68
75
  for (const page of project.graph.pages) {
69
76
  // `sourcePath` marks a page backed by a local file, which excludes
70
77
  // generated and remote pages; the content-root check below excludes local
@@ -81,11 +88,19 @@ const buildRouteRewrites = (
81
88
  ) {
82
89
  continue;
83
90
  }
84
- const logical = versionizeRoute(page.versionKey, id);
91
+ const snapshot = versionizeRoute(page.versionKey, id);
85
92
  rewrites.set(
86
93
  stripBasePath(basePath, page.route),
87
- i18n ? localizeRoute(logical, page.locale, i18n) : logical
94
+ i18n ? localizeRoute(snapshot, page.locale, i18n) : snapshot
88
95
  );
96
+ if (i18n) {
97
+ logical.set(page.versionKey, snapshot);
98
+ }
99
+ }
100
+ for (const [route, snapshot] of logical) {
101
+ if (!rewrites.has(route)) {
102
+ rewrites.set(route, snapshot);
103
+ }
89
104
  }
90
105
  return rewrites;
91
106
  };
@@ -150,9 +150,12 @@ export const versionsDiagnostics = (
150
150
  for (const page of pages) {
151
151
  // The version-looking folder is the first segment of the source-local ref
152
152
  // (e.g. `v1.0/guide.md`), not the namespaced id (`filesystem:v1.0/guide.md`).
153
- const [first] = page.source.ref.split("/");
153
+ // Only a folder can be a snapshot: a root file like `v3-migration.md` has
154
+ // no further segments.
155
+ const [first, ...rest] = page.source.ref.split("/");
154
156
  if (
155
157
  first &&
158
+ rest.length > 0 &&
156
159
  !seen.has(first) &&
157
160
  VERSION_LIKE.test(first) &&
158
161
  !configured.has(first)
@@ -7,8 +7,11 @@ export const NODE_ADAPTER_PACKAGE = "@astrojs/node";
7
7
 
8
8
  /**
9
9
  * Options for {@link node}: `output`, `site`, and `base`, plus any option of
10
- * `@astrojs/node` forwarded verbatim (`mode`, `host`, `port`, …). Blume
11
- * defaults `mode` to `"standalone"`; an option you pass wins.
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.
12
15
  */
13
16
  export type NodeOptions = DeployOptions;
14
17
 
@@ -7,6 +7,7 @@ import { netlify, netlifyAdapterSchema } from "./netlify.ts";
7
7
  import type { NetlifyAdapter } from "./netlify.ts";
8
8
  import { node, nodeAdapterSchema } from "./node.ts";
9
9
  import type { NodeAdapter } from "./node.ts";
10
+ import { siteUrlSchema } from "./types.ts";
10
11
  import type { DeployOutput, StaticDeployment } from "./types.ts";
11
12
  import { vercel, vercelAdapterSchema } from "./vercel.ts";
12
13
  import type { VercelAdapter } from "./vercel.ts";
@@ -56,7 +57,7 @@ const staticDeploymentSchema = z.strictObject(
56
57
  {
57
58
  base: z.string().optional(),
58
59
  kind: z.literal("static").optional(),
59
- site: z.url().optional(),
60
+ site: siteUrlSchema.optional(),
60
61
  },
61
62
  {
62
63
  error: (issue) => (issue.code === "unrecognized_keys" ? HINT : undefined),
@@ -59,12 +59,24 @@ export interface StaticDeployment {
59
59
  site?: string;
60
60
  }
61
61
 
62
+ /**
63
+ * `deployment.site`: an absolute http(s) URL, which always has a host. A bare
64
+ * `z.url()` takes any scheme, so `localhost:4321` (scheme `localhost:`),
65
+ * `mailto:`, and `javascript:` all passed and became the prefix of every
66
+ * sitemap entry and canonical link.
67
+ */
68
+ export const siteUrlSchema = z.url({
69
+ error:
70
+ 'Use an absolute http(s) URL with a host, like "https://docs.example.com" or "http://localhost:4321".',
71
+ protocol: /^https?$/u,
72
+ });
73
+
62
74
  /** The named options' schema; every host adapter extends it with `z.json()` passthrough. */
63
75
  export const deployOptionsSchema = z
64
76
  .object({
65
77
  base: z.string().optional(),
66
78
  output: z.enum(["static", "server"]).optional(),
67
- site: z.url().optional(),
79
+ site: siteUrlSchema.optional(),
68
80
  })
69
81
  .catchall(z.json());
70
82
 
@@ -4,6 +4,7 @@ import { mkdir, readFile, writeFile } from "node:fs/promises";
4
4
  import { dirname, join, resolve } from "pathe";
5
5
 
6
6
  import { buildAgentReadability } from "../ai/agent-readability.ts";
7
+ import { advertisedConfig, servesMcp } from "../ai/agent-surface.ts";
7
8
  import {
8
9
  AI_CATALOG_PATH,
9
10
  ARD_MANIFEST_PATH,
@@ -15,6 +16,7 @@ import { buildLlmsFiles } from "../ai/llms.ts";
15
16
  import { markdownRoutePaths } from "../ai/markdown.ts";
16
17
  import {
17
18
  AGENT_SKILLS_DIR,
19
+ AGENT_SKILLS_INDEX_PATH,
18
20
  buildSkillsIndex,
19
21
  collectSkills,
20
22
  } from "../ai/skills.ts";
@@ -23,6 +25,7 @@ import {
23
25
  buildSignaturesDirectory,
24
26
  SIGNATURES_DIRECTORY_PATH,
25
27
  } from "../ai/web-bot-auth.ts";
28
+ import { discoverPagesSync } from "../core/custom-pages.ts";
26
29
  import type { BlumeProject } from "../core/project-graph.ts";
27
30
  import type { ResolvedConfig } from "../core/schema.ts";
28
31
  import { buildSearchIndex } from "../search/build.ts";
@@ -367,9 +370,26 @@ export const publishBuildArtifacts = async (
367
370
  });
368
371
 
369
372
  // Collected once: llms.txt lists the skills the build publishes below.
373
+ const userSkillsIndex = existsSync(
374
+ join(distDir, AGENT_SKILLS_INDEX_PATH.slice(1))
375
+ );
370
376
  const skills = await collectConfiguredSkills(project, distDir, logger);
377
+ // The discovery documents advertise what this build serves: no MCP server
378
+ // when a page took its route, no skills index when nothing was published.
379
+ const advertised: BlumeProject = {
380
+ ...project,
381
+ config: advertisedConfig(project.config, {
382
+ mcp: servesMcp(
383
+ project,
384
+ project.context.pagesRoot
385
+ ? discoverPagesSync(project.context.pagesRoot)
386
+ : []
387
+ ),
388
+ skills: skills.length > 0 || userSkillsIndex,
389
+ }),
390
+ };
371
391
  if (project.config.agents.llmsTxt.enabled) {
372
- await publishLlmsFiles(project, distDir, skills, logger);
392
+ await publishLlmsFiles(advertised, distDir, skills, logger);
373
393
  }
374
394
 
375
395
  const sitemapFiles = buildSitemapFiles(project);
@@ -388,7 +408,7 @@ export const publishBuildArtifacts = async (
388
408
  logger.info("Generated robots.txt");
389
409
  }
390
410
 
391
- const agentReadability = buildAgentReadability(project);
411
+ const agentReadability = buildAgentReadability(advertised);
392
412
  if (
393
413
  agentReadability &&
394
414
  !existsSync(join(distDir, "agent-readability.json"))
@@ -401,9 +421,9 @@ export const publishBuildArtifacts = async (
401
421
  logger.info("Generated agent-readability.json");
402
422
  }
403
423
 
404
- await emitWellKnownFiles(project.config, distDir, skills, logger);
424
+ await emitWellKnownFiles(advertised.config, distDir, skills, logger);
405
425
  await emitAgentSkills(project, distDir, skills, logger);
406
426
 
407
- await emitRedirectFiles(project, distDir, logger);
408
- await emitHeaderFiles(project, distDir, logger);
427
+ await emitRedirectFiles(advertised, distDir, logger);
428
+ await emitHeaderFiles(advertised, distDir, logger);
409
429
  };
@@ -39,8 +39,9 @@
39
39
  *
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
- * takes over: the homepage agent-discovery `Link` header and the Markdown
43
- * `charset=utf-8` (see `deploy/headers.ts`). The raw `.md`/`.mdx` URLs are
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
44
45
  * exempted from worker-first routing with negative rules, keeping their
45
46
  * `_headers` treatment and their zero-Worker serving path.
46
47
  *
@@ -84,6 +85,7 @@ import {
84
85
  OPENAPI_PATH,
85
86
  } from "../ai/api/paths.ts";
86
87
  import { normalizePath } from "../core/base-path.ts";
88
+ import { CONTENT_ASSETS_ROOT, SVG_ASSET_HEADERS } from "./headers.ts";
87
89
  import type { NotFoundVariants } from "./vercel-negotiation.ts";
88
90
 
89
91
  /** Filename of the generated wrapper Worker, next to the adapter's entry. */
@@ -314,8 +316,8 @@ export const buildNegotiationWorker = (
314
316
  // request is delegated to the Astro Worker untouched — except a missing
315
317
  // page, whose HTML 404 shell is swapped for the prerendered Markdown or JSON
316
318
  // 404 twin when the client prefers one. \`_headers\` does not apply to
317
- // worker-first routes, so the homepage Link header and the Markdown charset
318
- // are re-stamped here.
319
+ // worker-first routes, so the homepage Link header, the Markdown charset, and
320
+ // the sandbox on downloaded SVGs are re-stamped here.
319
321
  import server from ${JSON.stringify(options.mainSpecifier)};
320
322
 
321
323
  const ROUTES = new Set(${routes});
@@ -326,6 +328,16 @@ const HOME_LINK_HEADER = ${homeLinkHeader};
326
328
  const HOME_TOKENS = ${homeTokens};
327
329
  const REDIRECTS = ${redirects};
328
330
  const NOT_FOUND = ${notFound};
331
+ const SVG_ASSET_HEADERS = ${JSON.stringify(SVG_ASSET_HEADERS)};
332
+
333
+ // An SVG a content source downloaded: a document that could run script as the
334
+ // docs site if opened directly, so it goes out sandboxed. Matched on the
335
+ // decoded path, ignoring case — the file the assets binding serves.
336
+ const isSvgAsset = (pathname) => {
337
+ const path = safeDecode(pathname).toLowerCase();
338
+ const prefix = (safeDecode(BASE_PREFIX) + "${CONTENT_ASSETS_ROOT}/").toLowerCase();
339
+ return path.startsWith(prefix) && path.endsWith(".svg");
340
+ };
329
341
 
330
342
  // Configured redirects live in \`_redirects\`, which only the static layer
331
343
  // reads — a worker-first route never reaches it. Answering from this baked-in
@@ -526,6 +538,13 @@ export default {
526
538
  status: redirect[1],
527
539
  });
528
540
  }
541
+ if (isSvgAsset(url.pathname)) {
542
+ return withHeaders(await server.fetch(request, env, context), (headers) => {
543
+ for (const [name, value] of Object.entries(SVG_ASSET_HEADERS)) {
544
+ headers.set(name, value);
545
+ }
546
+ });
547
+ }
529
548
  if (request.method !== "GET" && request.method !== "HEAD") {
530
549
  return server.fetch(request, env, context);
531
550
  }
@@ -29,45 +29,44 @@ import type { ResolvedConfig } from "../core/schema.ts";
29
29
 
30
30
  /**
31
31
  * One rule per served extension. `.mdx` uses `text/markdown` to match the
32
- * runtime endpoint, which serves both variants as `text/markdown`. The raw
33
- * Markdown mirrors live under the page routes (which carry `basePath`), while
34
- * the `.txt` files (`llms.txt`, `llms-full.txt`) are written to the dist root
35
- * and served at the deployment base — so only the `.md`/`.mdx` rules take the
36
- * `basePath` layer.
32
+ * runtime endpoint, which serves both variants as `text/markdown`. Every rule
33
+ * sits at the deployment base, not under `basePath`: most Markdown mirrors
34
+ * live under the page routes, but the section home's mirror (`/docs.md`) sits
35
+ * beside `basePath`, and the synthesized `/index.md`, `/changelog.md`, and
36
+ * `/404.md` sit at the root, as do the `.txt` files (`llms.txt`,
37
+ * `llms-full.txt`).
37
38
  */
38
- const HEADER_RULES: readonly {
39
- contentType: string;
40
- ext: string;
41
- underBasePath: boolean;
42
- }[] = [
43
- {
44
- contentType: "text/markdown; charset=utf-8",
45
- ext: "md",
46
- underBasePath: true,
47
- },
48
- {
49
- contentType: "text/markdown; charset=utf-8",
50
- ext: "mdx",
51
- underBasePath: true,
52
- },
53
- {
54
- contentType: "text/plain; charset=utf-8",
55
- ext: "txt",
56
- underBasePath: false,
57
- },
39
+ const HEADER_RULES: readonly { contentType: string; ext: string }[] = [
40
+ { contentType: "text/markdown; charset=utf-8", ext: "md" },
41
+ { contentType: "text/markdown; charset=utf-8", ext: "mdx" },
42
+ { contentType: "text/plain; charset=utf-8", ext: "txt" },
58
43
  ];
59
44
 
60
45
  /** Where the generated endpoint serves content-source images from. */
61
- const CONTENT_ASSETS_ROOT = "/blume-assets";
46
+ export const CONTENT_ASSETS_ROOT = "/blume-assets";
62
47
 
63
48
  /**
64
49
  * The policy a content-source SVG is served with: an opaque origin with
65
50
  * scripts off, so an uploaded SVG that carries a `<script>` stays inert when
66
- * opened directly. The endpoint sends it from the server and dev runtimes;
67
- * {@link headerRules} carries it to static hosts.
51
+ * opened directly.
68
52
  */
69
53
  export const SVG_ASSET_POLICY = "sandbox";
70
54
 
55
+ /**
56
+ * The headers a content-source SVG is served with: the sandbox policy, and no
57
+ * MIME sniffing. The endpoint sends them from the dev runtime; every host
58
+ * gets them for the built files — static hosts through {@link headerRules},
59
+ * server builds through each platform's own mechanism (`deploy/platforms`).
60
+ */
61
+ export const SVG_ASSET_HEADERS = {
62
+ "Content-Security-Policy": SVG_ASSET_POLICY,
63
+ "X-Content-Type-Options": "nosniff",
64
+ } satisfies Record<string, string>;
65
+
66
+ /** The served glob of content-source SVGs, under the deployment base. */
67
+ export const svgAssetPath = (config: ResolvedConfig): string =>
68
+ `${normalizeBasePath(config.deployment.options.base)}${CONTENT_ASSETS_ROOT}/*.svg`;
69
+
71
70
  /**
72
71
  * One static-host header rule: a served path (a `*` glob spanning path
73
72
  * segments, or an exact path) and the header it sets there.
@@ -80,11 +79,9 @@ export interface HeaderRule {
80
79
 
81
80
  /**
82
81
  * The header rules every static host should apply, in `_headers` glob syntax.
83
- * The glob carries the served prefix (`{deployment.base}{basePath}` for the
84
- * Markdown mirrors, `{deployment.base}` for the root `.txt` files) so the rules
85
- * still match once the site is mounted under a subpath (`/docs/*.md`); the
86
- * wildcard spans path segments, so a nested route like `/docs/ja/intro.md`
87
- * matches too.
82
+ * The glob carries the deployment base so the rules still match once the site
83
+ * is mounted under a subpath (`/docs/*.md`); the wildcard spans path
84
+ * segments, so a nested route like `/docs/ja/intro.md` matches too.
88
85
  *
89
86
  * When a homepage `Link` header is provided (see `ai/link-headers.ts`), an
90
87
  * exact-path rule for the root page advertises the agent-discovery resources —
@@ -95,16 +92,11 @@ export const headerRules = (
95
92
  homeLinkHeader?: string | null
96
93
  ): HeaderRule[] => {
97
94
  const deployBase = normalizeBasePath(config.deployment.options.base);
98
- const rules: HeaderRule[] = HEADER_RULES.map((rule) => {
99
- const prefix = rule.underBasePath
100
- ? `${deployBase}${config.basePath}`
101
- : deployBase;
102
- return {
103
- name: "Content-Type",
104
- path: `${prefix}/*.${rule.ext}`,
105
- value: rule.contentType,
106
- };
107
- });
95
+ const rules: HeaderRule[] = HEADER_RULES.map((rule) => ({
96
+ name: "Content-Type",
97
+ path: `${deployBase}/*.${rule.ext}`,
98
+ value: rule.contentType,
99
+ }));
108
100
  if (homeLinkHeader) {
109
101
  rules.push({ name: "Link", path: `${deployBase}/`, value: homeLinkHeader });
110
102
  }
@@ -134,9 +126,8 @@ export const headerRules = (
134
126
  value: "*",
135
127
  });
136
128
  }
137
- // Published skills live at the deployment base, outside `basePath` — the
138
- // `.md` charset rule above misses them whenever a basePath is set, and the
139
- // RFC wants archives served as application/gzip explicitly.
129
+ // Published skills live at the deployment base, and the RFC wants their
130
+ // archives served as application/gzip explicitly.
140
131
  if (config.agents.skills) {
141
132
  rules.push(
142
133
  {
@@ -155,11 +146,9 @@ export const headerRules = (
155
146
  // are served from the docs origin, and an SVG opened directly is a document
156
147
  // that can run script. A sandbox policy keeps it inert; an `<img>` that
157
148
  // embeds it is unaffected.
158
- rules.push({
159
- name: "Content-Security-Policy",
160
- path: `${deployBase}${CONTENT_ASSETS_ROOT}/*.svg`,
161
- value: SVG_ASSET_POLICY,
162
- });
149
+ for (const [name, value] of Object.entries(SVG_ASSET_HEADERS)) {
150
+ rules.push({ name, path: svgAssetPath(config), value });
151
+ }
163
152
  return rules;
164
153
  };
165
154
 
@@ -175,6 +164,33 @@ export const buildNetlifyHeaders = (
175
164
  .map((rule) => `${rule.path}\n ${rule.name}: ${rule.value}`)
176
165
  .join("\n")}\n`;
177
166
 
167
+ /** One entry of Netlify's Frameworks API config `headers` array. */
168
+ export interface NetlifyConfigHeaders {
169
+ for: string;
170
+ values: Record<string, string>;
171
+ }
172
+
173
+ /**
174
+ * The {@link headerRules} as Netlify Frameworks API `headers` entries
175
+ * (`.netlify/v1/config.json`), one per path with every header it sets. A
176
+ * `netlify()` server build reads no `_headers` file, so it carries the same
177
+ * rules here, in the `for`/`values` form that follows `netlify.toml`'s
178
+ * `[[headers]]` and the same path syntax as `_headers`.
179
+ */
180
+ export const buildNetlifyConfigHeaders = (
181
+ config: ResolvedConfig,
182
+ homeLinkHeader?: string | null
183
+ ): NetlifyConfigHeaders[] => {
184
+ const byPath = new Map<string, Record<string, string>>();
185
+ for (const rule of headerRules(config, homeLinkHeader)) {
186
+ byPath.set(rule.path, {
187
+ ...byPath.get(rule.path),
188
+ [rule.name]: rule.value,
189
+ });
190
+ }
191
+ return [...byPath].map(([path, values]) => ({ for: path, values }));
192
+ };
193
+
178
194
  /** One entry of `vercel.json`'s `headers` array. */
179
195
  export interface VercelHeader {
180
196
  headers: { key: string; value: string }[];