blume 2.1.0 → 2.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (450) hide show
  1. package/CHANGELOG.md +110 -0
  2. package/dist/cli/{chunk-ga0pf4aj.js → chunk-0n9xkp3d.js} +12 -3
  3. package/dist/cli/{chunk-ga0pf4aj.js.map → chunk-0n9xkp3d.js.map} +3 -3
  4. package/dist/cli/{chunk-43zcf4gz.js → chunk-0pmb4tr4.js} +541 -368
  5. package/dist/cli/chunk-0pmb4tr4.js.map +35 -0
  6. package/dist/cli/{chunk-9geymqns.js → chunk-0w6hs479.js} +15 -15
  7. package/dist/cli/{chunk-xkz5xbtv.js → chunk-124fvmzq.js} +2802 -1422
  8. package/dist/cli/chunk-124fvmzq.js.map +220 -0
  9. package/dist/cli/chunk-150jk7km.js +52 -0
  10. package/dist/cli/chunk-150jk7km.js.map +10 -0
  11. package/dist/cli/{chunk-y4ykbark.js → chunk-1f882900.js} +2 -2
  12. package/dist/cli/{chunk-5ffywsmb.js → chunk-297ajdcv.js} +8 -6
  13. package/dist/cli/{chunk-5ffywsmb.js.map → chunk-297ajdcv.js.map} +3 -3
  14. package/dist/cli/{chunk-h2a48yhd.js → chunk-32khwcb6.js} +120 -16
  15. package/dist/cli/{chunk-h2a48yhd.js.map → chunk-32khwcb6.js.map} +10 -10
  16. package/dist/cli/{chunk-5cg2rzgf.js → chunk-36bxs9c7.js} +20 -49
  17. package/dist/cli/{chunk-5cg2rzgf.js.map → chunk-36bxs9c7.js.map} +4 -5
  18. package/dist/cli/{chunk-py62fewh.js → chunk-53jqw8z0.js} +3 -3
  19. package/dist/cli/{chunk-5wj2qwn2.js → chunk-58bt3hag.js} +4 -4
  20. package/dist/cli/{chunk-e7vvk5ya.js → chunk-5cmts6dv.js} +3 -3
  21. package/dist/cli/{chunk-y5h4r2y0.js → chunk-60hz265r.js} +15 -5
  22. package/dist/cli/chunk-60hz265r.js.map +10 -0
  23. package/dist/cli/chunk-74wfhv79.js +65 -0
  24. package/dist/cli/chunk-74wfhv79.js.map +10 -0
  25. package/dist/cli/{chunk-g2z9x01v.js → chunk-9qzgg6mj.js} +3 -3
  26. package/dist/cli/{chunk-q3wxxq39.js → chunk-a57yh0h9.js} +5 -5
  27. package/dist/cli/{chunk-713mngdq.js → chunk-ave1wcbr.js} +76 -10
  28. package/dist/cli/chunk-ave1wcbr.js.map +14 -0
  29. package/dist/cli/{chunk-2n4ps1b3.js → chunk-bqxaqn5p.js} +40 -22
  30. package/dist/cli/chunk-bqxaqn5p.js.map +15 -0
  31. package/dist/cli/{chunk-jnwj5kjj.js → chunk-bxwhtvw8.js} +20 -7
  32. package/dist/cli/{chunk-jnwj5kjj.js.map → chunk-bxwhtvw8.js.map} +3 -3
  33. package/dist/cli/chunk-ckrcm08h.js +324 -0
  34. package/dist/cli/chunk-ckrcm08h.js.map +14 -0
  35. package/dist/cli/{chunk-2f3ndkpw.js → chunk-desvm72z.js} +3 -3
  36. package/dist/cli/{chunk-gp84za0h.js → chunk-dhwtqnnd.js} +2 -2
  37. package/dist/cli/{chunk-4h8z9vv5.js → chunk-dpy8qdjf.js} +93 -47
  38. package/dist/cli/chunk-dpy8qdjf.js.map +17 -0
  39. package/dist/cli/{chunk-2f7n4qzt.js → chunk-dqhg54mr.js} +1 -1
  40. package/dist/cli/{chunk-0gz36akr.js → chunk-dv2z0rzf.js} +21 -5
  41. package/dist/cli/chunk-dv2z0rzf.js.map +12 -0
  42. package/dist/cli/{chunk-cm89scdv.js → chunk-es8h0v3j.js} +9 -11
  43. package/dist/cli/{chunk-cm89scdv.js.map → chunk-es8h0v3j.js.map} +3 -3
  44. package/dist/cli/{chunk-zhvd6adm.js → chunk-gfc5fqvf.js} +14 -14
  45. package/dist/cli/{chunk-nd1821sk.js → chunk-gxtg6ed2.js} +33 -19
  46. package/dist/cli/chunk-gxtg6ed2.js.map +10 -0
  47. package/dist/cli/{chunk-p804f3pe.js → chunk-hvcxng5f.js} +6 -6
  48. package/dist/cli/{chunk-ta27feev.js → chunk-j1x92mx9.js} +21 -2
  49. package/dist/cli/chunk-j1x92mx9.js.map +10 -0
  50. package/dist/cli/{chunk-rekqz4qg.js → chunk-k3gr1nm9.js} +19 -16
  51. package/dist/cli/chunk-k3gr1nm9.js.map +10 -0
  52. package/dist/cli/{chunk-1n228xqb.js → chunk-kdpzw38a.js} +2 -2
  53. package/dist/cli/{chunk-cy5bm0j0.js → chunk-ms9z66nz.js} +2 -2
  54. package/dist/cli/{chunk-cy5bm0j0.js.map → chunk-ms9z66nz.js.map} +2 -2
  55. package/dist/cli/{chunk-zf748avv.js → chunk-nbyvgnnm.js} +193 -286
  56. package/dist/cli/chunk-nbyvgnnm.js.map +36 -0
  57. package/dist/cli/{chunk-6bm0xpgd.js → chunk-s0bdnc7v.js} +2 -2
  58. package/dist/cli/{chunk-yxmk4pec.js → chunk-sc93apfs.js} +17 -16
  59. package/dist/cli/{chunk-yxmk4pec.js.map → chunk-sc93apfs.js.map} +3 -3
  60. package/dist/cli/{chunk-cmtcnh9f.js → chunk-tfycddbt.js} +2 -2
  61. package/dist/cli/{chunk-9xr4dgee.js → chunk-twy7psdz.js} +29 -16
  62. package/dist/cli/chunk-twy7psdz.js.map +10 -0
  63. package/dist/cli/{chunk-ntv4qrt7.js → chunk-y4msrwm9.js} +15 -15
  64. package/dist/cli/{chunk-yj17vs39.js → chunk-ydp3065p.js} +5 -5
  65. package/dist/cli/index.js +24 -19
  66. package/dist/cli/index.js.map +3 -3
  67. package/dist/types/ai/ask-context.d.ts +6 -2
  68. package/dist/types/ai/ask.d.ts +24 -5
  69. package/dist/types/ai/component-markdown.d.ts +2 -0
  70. package/dist/types/ai/openapi-components.d.ts +4 -1
  71. package/dist/types/analytics/clics.d.ts +37 -0
  72. package/dist/types/analytics/index.d.ts +2 -0
  73. package/dist/types/analytics/schema.d.ts +18 -0
  74. package/dist/types/astro/integration.d.ts +11 -4
  75. package/dist/types/astro/markdown-negotiation.d.ts +3 -0
  76. package/dist/types/astro/pages.d.ts +10 -0
  77. package/dist/types/cli/init/scaffold.d.ts +1 -1
  78. package/dist/types/components/content/api-field.d.ts +4 -1
  79. package/dist/types/components/content/api-page.d.ts +6 -1
  80. package/dist/types/components/openapi/code-samples.d.ts +29 -0
  81. package/dist/types/components/openapi/helpers.d.ts +22 -1
  82. package/dist/types/components/openapi/security.d.ts +10 -0
  83. package/dist/types/components/openapi/snippets.d.ts +60 -0
  84. package/dist/types/core/changelog-index.d.ts +8 -0
  85. package/dist/types/core/config-input.d.ts +37 -6
  86. package/dist/types/core/data.d.ts +4 -0
  87. package/dist/types/core/i18n-ui.d.ts +2 -0
  88. package/dist/types/core/links.d.ts +8 -0
  89. package/dist/types/core/powered-by.d.ts +12 -0
  90. package/dist/types/core/probe.d.ts +2 -0
  91. package/dist/types/core/project-graph.d.ts +6 -0
  92. package/dist/types/core/redirect-patterns.d.ts +11 -3
  93. package/dist/types/core/schema.d.ts +228 -3
  94. package/dist/types/core/sources/cache.d.ts +3 -0
  95. package/dist/types/core/sources/contentful-rich-text.d.ts +11 -2
  96. package/dist/types/core/sources/contentful.d.ts +17 -1
  97. package/dist/types/core/sources/lexical.d.ts +5 -0
  98. package/dist/types/core/sources/lower.d.ts +10 -2
  99. package/dist/types/core/sources/normalize.d.ts +20 -1
  100. package/dist/types/core/sources/notion.d.ts +8 -0
  101. package/dist/types/core/sources/remote.d.ts +37 -10
  102. package/dist/types/core/sources/sanity.d.ts +1 -1
  103. package/dist/types/core/types.d.ts +17 -0
  104. package/dist/types/deploy/adapter-output.d.ts +2 -1
  105. package/dist/types/deploy/adapters/cloudflare.d.ts +7 -5
  106. package/dist/types/deploy/adapters/node.d.ts +58 -23
  107. package/dist/types/deploy/adapters/registry.d.ts +10 -0
  108. package/dist/types/deploy/adapters/types.d.ts +3 -2
  109. package/dist/types/deploy/cloudflare-negotiation.d.ts +5 -2
  110. package/dist/types/deploy/node-headers.d.ts +12 -4
  111. package/dist/types/deploy/platforms/cloudflare.d.ts +10 -7
  112. package/dist/types/deploy/platforms/node.d.ts +3 -1
  113. package/dist/types/deploy/platforms/types.d.ts +18 -7
  114. package/dist/types/deploy/platforms/vercel.d.ts +21 -2
  115. package/dist/types/deploy/redirects.d.ts +17 -1
  116. package/dist/types/deploy/sitemap.d.ts +2 -1
  117. package/dist/types/deploy/vercel-negotiation.d.ts +44 -21
  118. package/dist/types/narration/build.d.ts +34 -12
  119. package/dist/types/narration/provider.d.ts +96 -8
  120. package/dist/types/openapi/checks.d.ts +27 -0
  121. package/dist/types/openapi/graphql.d.ts +6 -0
  122. package/dist/types/openapi/model.d.ts +15 -4
  123. package/dist/types/openapi/parse.d.ts +3 -0
  124. package/dist/types/openapi/references.d.ts +16 -0
  125. package/dist/types/openapi/sentence.d.ts +11 -1
  126. package/dist/types/ratelimit/index.d.ts +1 -1
  127. package/dist/types/ratelimit/schema.d.ts +8 -0
  128. package/dist/types/ratelimit/upstash.d.ts +39 -14
  129. package/dist/types/reference/graphql.d.ts +76 -1
  130. package/dist/types/reference/index.d.ts +1 -1
  131. package/dist/types/reference/openapi.d.ts +2 -2
  132. package/dist/types/reference/options.d.ts +37 -1
  133. package/dist/types/reference/schema.d.ts +84 -0
  134. package/dist/types/search/adapters/registry.d.ts +3 -0
  135. package/dist/types/search/adapters/typesense.d.ts +14 -4
  136. package/dist/types/search/build.d.ts +3 -1
  137. package/dist/types/search/orama-index.d.ts +9 -9
  138. package/dist/types/search/sync/algolia.d.ts +5 -2
  139. package/dist/types/search/sync/index.d.ts +3 -1
  140. package/dist/types/search/sync/typesense.d.ts +11 -7
  141. package/dist/types/seo/jsonld.d.ts +11 -6
  142. package/dist/types/sources/contentful.d.ts +16 -0
  143. package/dist/types/sources/github-releases.d.ts +8 -1
  144. package/dist/types/sources/notion.d.ts +8 -0
  145. package/dist/types/sources/payload.d.ts +2 -0
  146. package/dist/types/sources/registry.d.ts +63 -0
  147. package/dist/types/sources/shared.d.ts +3 -0
  148. package/dist/types/sources/strapi.d.ts +2 -0
  149. package/docs/01-quickstart.mdx +4 -2
  150. package/docs/02-deployment.mdx +59 -13
  151. package/docs/03-upgrading.mdx +2 -1
  152. package/docs/04-migrating.mdx +9 -9
  153. package/docs/08-faq.mdx +12 -12
  154. package/docs/advanced/changelog.mdx +30 -2
  155. package/docs/advanced/custom-pages.mdx +1 -1
  156. package/docs/advanced/skills.mdx +1 -1
  157. package/docs/cli/audit.mdx +30 -7
  158. package/docs/cli/doctor.mdx +6 -2
  159. package/docs/cli/evals.mdx +4 -4
  160. package/docs/cli/translate.mdx +1 -0
  161. package/docs/cli/validate.mdx +4 -3
  162. package/docs/configuration/analytics.mdx +19 -1
  163. package/docs/configuration/assistant.mdx +6 -2
  164. package/docs/configuration/customization.mdx +4 -2
  165. package/docs/configuration/export.mdx +5 -3
  166. package/docs/configuration/index.mdx +4 -2
  167. package/docs/configuration/narration.mdx +43 -6
  168. package/docs/configuration/rate-limiting.mdx +23 -2
  169. package/docs/configuration/search.mdx +37 -14
  170. package/docs/content/components.mdx +2 -2
  171. package/docs/content/frontmatter.mdx +5 -3
  172. package/docs/content/i18n.mdx +2 -2
  173. package/docs/content/includes.mdx +1 -1
  174. package/docs/content/navigation.mdx +8 -6
  175. package/docs/content/sources/contentful.mdx +9 -4
  176. package/docs/content/sources/custom.mdx +7 -3
  177. package/docs/content/sources/github-releases.mdx +12 -2
  178. package/docs/content/sources/index.mdx +3 -3
  179. package/docs/content/sources/notion.mdx +17 -2
  180. package/docs/content/sources/obsidian.mdx +3 -1
  181. package/docs/content/sources/payload.mdx +5 -2
  182. package/docs/content/sources/remote-mdx.mdx +6 -0
  183. package/docs/content/sources/sanity.mdx +4 -2
  184. package/docs/content/sources/strapi.mdx +5 -2
  185. package/docs/content/syntax.mdx +2 -2
  186. package/docs/discoverability/agent-discovery.mdx +1 -1
  187. package/docs/discoverability/index.mdx +1 -1
  188. package/docs/discoverability/json-api.mdx +2 -0
  189. package/docs/discoverability/llms-txt.mdx +1 -1
  190. package/docs/discoverability/markdown.mdx +3 -1
  191. package/docs/discoverability/mcp.mdx +6 -0
  192. package/docs/discoverability/sitemap-and-robots.mdx +1 -1
  193. package/docs/discoverability/structured-data.mdx +8 -6
  194. package/docs/references/api-pages.mdx +1 -1
  195. package/docs/references/asyncapi.mdx +5 -1
  196. package/docs/references/graphql.mdx +15 -1
  197. package/docs/references/openapi.mdx +64 -8
  198. package/package.json +9 -9
  199. package/skills/blume/SKILL.md +1 -1
  200. package/skills/blume-migrate/SKILL.md +2 -2
  201. package/skills/blume-migrate/assets/{oxfmt@0.67.0.patch → oxfmt@0.71.0.patch} +7 -7
  202. package/skills/blume-migrate/references/docusaurus.md +4 -4
  203. package/skills/blume-migrate/references/fumadocs.md +17 -13
  204. package/skills/blume-migrate/references/monorepo.md +5 -5
  205. package/skills/blume-migrate/references/nextra.md +8 -5
  206. package/skills/blume-migrate/references/starlight.md +9 -7
  207. package/src/ai/ask-context.ts +33 -3
  208. package/src/ai/ask-limits.ts +13 -0
  209. package/src/ai/ask.ts +24 -6
  210. package/src/ai/changelog-markdown.ts +11 -6
  211. package/src/ai/component-markdown.ts +1 -1
  212. package/src/ai/cors.ts +12 -3
  213. package/src/ai/mcp/query.ts +14 -6
  214. package/src/ai/openapi-components.ts +28 -4
  215. package/src/ai/relative-links.ts +4 -38
  216. package/src/analytics/clics.ts +58 -0
  217. package/src/analytics/head.ts +5 -0
  218. package/src/analytics/index.ts +2 -0
  219. package/src/analytics/schema.ts +2 -0
  220. package/src/astro/generate.ts +91 -56
  221. package/src/astro/integration.ts +72 -22
  222. package/src/astro/markdown-negotiation.ts +3 -0
  223. package/src/astro/module-types.ts +3 -1
  224. package/src/astro/pages.ts +23 -0
  225. package/src/astro/render-deps.ts +10 -5
  226. package/src/astro/runtime-deps.ts +23 -2
  227. package/src/astro/templates.ts +129 -81
  228. package/src/audit/catalog.ts +19 -3
  229. package/src/audit/checks/links.ts +14 -1
  230. package/src/audit/checks/network.ts +110 -6
  231. package/src/audit/checks/redirects.ts +6 -4
  232. package/src/audit/checks/robots.ts +58 -2
  233. package/src/audit/run.ts +3 -0
  234. package/src/audit/types.ts +2 -0
  235. package/src/audit/url.ts +14 -5
  236. package/src/blume-modules.d.ts +5 -1
  237. package/src/cli/args.ts +51 -0
  238. package/src/cli/build-failure.ts +23 -0
  239. package/src/cli/commands/audit.ts +10 -1
  240. package/src/cli/commands/dev.ts +18 -2
  241. package/src/cli/commands/doctor.ts +45 -12
  242. package/src/cli/commands/eval.ts +36 -4
  243. package/src/cli/commands/init.ts +6 -4
  244. package/src/cli/commands/validate.ts +10 -1
  245. package/src/cli/init/scaffold.ts +9 -6
  246. package/src/cli/prepare.ts +6 -3
  247. package/src/cli/required-secrets.ts +3 -2
  248. package/src/components/content/ApiPlayground.astro +6 -2
  249. package/src/components/content/api-field.ts +4 -1
  250. package/src/components/content/api-page.ts +31 -7
  251. package/src/components/content/mermaid-element.ts +15 -1
  252. package/src/components/content/view-switcher.ts +7 -4
  253. package/src/components/islands/hooks.ts +27 -1
  254. package/src/components/layout/NavTree.astro +49 -5
  255. package/src/components/layout/NavTreeScript.astro +18 -0
  256. package/src/components/layout/PageActions.astro +139 -24
  257. package/src/components/layout/PageLayout.astro +13 -3
  258. package/src/components/layout/RootLayout.astro +27 -14
  259. package/src/components/layout/Search.astro +17 -3
  260. package/src/components/layout/TableOfContents.astro +24 -17
  261. package/src/components/layout/analytics-client.ts +8 -8
  262. package/src/components/layout/head-scripts.ts +6 -1
  263. package/src/components/layout/search/algolia.ts +10 -1
  264. package/src/components/layout/search/analytics.ts +42 -14
  265. package/src/components/layout/search/endpoint.ts +48 -5
  266. package/src/components/layout/search/pagefind.ts +114 -5
  267. package/src/components/layout/search/types.ts +12 -0
  268. package/src/components/layout/search/typesense.ts +18 -6
  269. package/src/components/openapi/AsyncApiOperation.astro +14 -7
  270. package/src/components/openapi/Authorization.astro +10 -0
  271. package/src/components/openapi/GraphqlOperation.astro +5 -0
  272. package/src/components/openapi/Operation.astro +24 -1
  273. package/src/components/openapi/ParametersTable.astro +8 -4
  274. package/src/components/openapi/Playground.astro +7 -1
  275. package/src/components/openapi/RequestBody.astro +19 -2
  276. package/src/components/openapi/Responses.astro +1 -0
  277. package/src/components/openapi/SchemaTable.astro +15 -3
  278. package/src/components/openapi/async-snippets.ts +61 -5
  279. package/src/components/openapi/async.ts +123 -29
  280. package/src/components/openapi/graphql-helpers.ts +35 -12
  281. package/src/components/openapi/helpers.ts +36 -2
  282. package/src/components/openapi/message-model.ts +54 -11
  283. package/src/components/openapi/message.ts +14 -4
  284. package/src/components/openapi/operation-model.ts +16 -7
  285. package/src/components/openapi/playground-client.ts +77 -58
  286. package/src/components/openapi/playground-schema.ts +1 -1
  287. package/src/components/openapi/schema-tree.ts +44 -14
  288. package/src/components/openapi/security.ts +10 -0
  289. package/src/components/openapi/snippets.ts +5 -3
  290. package/src/components/openapi/validate-json.ts +4 -3
  291. package/src/components/powered-by-middleware.ts +33 -0
  292. package/src/core/changelog-index.ts +31 -1
  293. package/src/core/component-diagnostics.ts +53 -0
  294. package/src/core/component-overrides.ts +32 -0
  295. package/src/core/config-input.ts +42 -6
  296. package/src/core/data.ts +2 -0
  297. package/src/core/diagnostics.ts +5 -0
  298. package/src/core/i18n-ui.ts +2 -0
  299. package/src/core/includes.ts +1 -1
  300. package/src/core/links.ts +47 -17
  301. package/src/core/powered-by.ts +12 -0
  302. package/src/core/probe.ts +26 -6
  303. package/src/core/project-graph.ts +59 -18
  304. package/src/core/redirect-patterns.ts +36 -7
  305. package/src/core/schema.ts +26 -6
  306. package/src/core/sources/cache.ts +6 -0
  307. package/src/core/sources/contentful-rich-text.ts +29 -6
  308. package/src/core/sources/contentful.ts +135 -25
  309. package/src/core/sources/filesystem.ts +26 -4
  310. package/src/core/sources/github-releases.ts +12 -1
  311. package/src/core/sources/lexical.ts +15 -10
  312. package/src/core/sources/lower.ts +19 -3
  313. package/src/core/sources/mdx-remote.ts +62 -10
  314. package/src/core/sources/normalize.ts +227 -41
  315. package/src/core/sources/notion.ts +34 -10
  316. package/src/core/sources/obsidian.ts +16 -3
  317. package/src/core/sources/payload.ts +38 -15
  318. package/src/core/sources/remote.ts +72 -15
  319. package/src/core/sources/resolve.ts +11 -1
  320. package/src/core/sources/sanity.ts +43 -25
  321. package/src/core/sources/strapi.ts +2 -2
  322. package/src/core/types.ts +18 -0
  323. package/src/core/ui-packs/ar.ts +1 -0
  324. package/src/core/ui-packs/bg.ts +1 -0
  325. package/src/core/ui-packs/bn.ts +1 -0
  326. package/src/core/ui-packs/ca.ts +1 -0
  327. package/src/core/ui-packs/cs.ts +1 -0
  328. package/src/core/ui-packs/da.ts +1 -0
  329. package/src/core/ui-packs/de.ts +1 -0
  330. package/src/core/ui-packs/el.ts +1 -0
  331. package/src/core/ui-packs/es.ts +1 -0
  332. package/src/core/ui-packs/fa.ts +1 -0
  333. package/src/core/ui-packs/fi.ts +1 -0
  334. package/src/core/ui-packs/fr.ts +1 -0
  335. package/src/core/ui-packs/he.ts +1 -0
  336. package/src/core/ui-packs/hi.ts +1 -0
  337. package/src/core/ui-packs/hr.ts +1 -0
  338. package/src/core/ui-packs/hu.ts +1 -0
  339. package/src/core/ui-packs/id.ts +1 -0
  340. package/src/core/ui-packs/it.ts +1 -0
  341. package/src/core/ui-packs/ja.ts +1 -0
  342. package/src/core/ui-packs/ko.ts +1 -0
  343. package/src/core/ui-packs/nl.ts +1 -0
  344. package/src/core/ui-packs/no.ts +1 -0
  345. package/src/core/ui-packs/pl.ts +1 -0
  346. package/src/core/ui-packs/pt-br.ts +1 -0
  347. package/src/core/ui-packs/pt.ts +1 -0
  348. package/src/core/ui-packs/ro.ts +1 -0
  349. package/src/core/ui-packs/ru.ts +1 -0
  350. package/src/core/ui-packs/sk.ts +1 -0
  351. package/src/core/ui-packs/sr.ts +1 -0
  352. package/src/core/ui-packs/sv.ts +1 -0
  353. package/src/core/ui-packs/th.ts +1 -0
  354. package/src/core/ui-packs/tr.ts +1 -0
  355. package/src/core/ui-packs/uk.ts +1 -0
  356. package/src/core/ui-packs/vi.ts +1 -0
  357. package/src/core/ui-packs/zh-tw.ts +1 -0
  358. package/src/core/ui-packs/zh.ts +1 -0
  359. package/src/deploy/adapter-output.ts +7 -2
  360. package/src/deploy/adapters/cloudflare.ts +7 -5
  361. package/src/deploy/adapters/node.ts +47 -9
  362. package/src/deploy/adapters/types.ts +7 -3
  363. package/src/deploy/artifacts.ts +2 -1
  364. package/src/deploy/cloudflare-negotiation.ts +36 -7
  365. package/src/deploy/headers.ts +15 -1
  366. package/src/deploy/node-headers.ts +32 -20
  367. package/src/deploy/platforms/cloudflare.ts +14 -9
  368. package/src/deploy/platforms/netlify.ts +2 -1
  369. package/src/deploy/platforms/node.ts +12 -2
  370. package/src/deploy/platforms/types.ts +21 -7
  371. package/src/deploy/platforms/vercel.ts +115 -17
  372. package/src/deploy/redirects.ts +66 -6
  373. package/src/deploy/rss.ts +3 -1
  374. package/src/deploy/sitemap.ts +39 -5
  375. package/src/deploy/vercel-negotiation.ts +325 -103
  376. package/src/eval/agents.ts +18 -1
  377. package/src/eval/prompts.ts +6 -3
  378. package/src/eval/report.ts +34 -13
  379. package/src/eval/run.ts +5 -0
  380. package/src/markdown/base-links.ts +15 -14
  381. package/src/markdown/package-commands.ts +9 -8
  382. package/src/markdown/relative-links.ts +3 -2
  383. package/src/narration/build.ts +218 -40
  384. package/src/narration/provider.ts +89 -9
  385. package/src/openapi/checks.ts +273 -0
  386. package/src/openapi/graphql-build.ts +21 -0
  387. package/src/openapi/graphql.ts +11 -0
  388. package/src/openapi/model.ts +15 -3
  389. package/src/openapi/parse.ts +127 -2
  390. package/src/openapi/references.ts +43 -3
  391. package/src/openapi/render-mdx.ts +68 -8
  392. package/src/openapi/sentence.ts +29 -2
  393. package/src/openapi/source.ts +39 -14
  394. package/src/ratelimit/index.ts +1 -1
  395. package/src/ratelimit/runtime.ts +37 -19
  396. package/src/ratelimit/upstash.ts +40 -10
  397. package/src/reference/graphql.ts +15 -1
  398. package/src/reference/index.ts +1 -0
  399. package/src/reference/openapi.ts +2 -2
  400. package/src/reference/options.ts +29 -1
  401. package/src/registry/eject.ts +8 -2
  402. package/src/search/adapters/locale-scope.ts +30 -0
  403. package/src/search/adapters/typesense.ts +14 -4
  404. package/src/search/build.ts +21 -3
  405. package/src/search/orama-index.ts +163 -31
  406. package/src/search/source-pages.ts +29 -0
  407. package/src/search/sync/algolia.ts +99 -9
  408. package/src/search/sync/index.ts +52 -13
  409. package/src/search/sync/typesense.ts +72 -27
  410. package/src/seo/jsonld.ts +38 -20
  411. package/src/sources/contentful.ts +12 -0
  412. package/src/sources/github-releases.ts +7 -1
  413. package/src/sources/notion.ts +7 -0
  414. package/src/sources/shared.ts +3 -0
  415. package/src/theme/color.ts +9 -10
  416. package/src/theme/entry.ts +43 -6
  417. package/src/translate/validate.ts +23 -1
  418. package/dist/cli/chunk-0gz36akr.js.map +0 -12
  419. package/dist/cli/chunk-2n4ps1b3.js.map +0 -15
  420. package/dist/cli/chunk-43zcf4gz.js.map +0 -36
  421. package/dist/cli/chunk-4ejxbzee.js +0 -39
  422. package/dist/cli/chunk-4ejxbzee.js.map +0 -10
  423. package/dist/cli/chunk-4h8z9vv5.js.map +0 -16
  424. package/dist/cli/chunk-713mngdq.js.map +0 -14
  425. package/dist/cli/chunk-9xr4dgee.js.map +0 -10
  426. package/dist/cli/chunk-amrb6hgh.js +0 -145
  427. package/dist/cli/chunk-amrb6hgh.js.map +0 -11
  428. package/dist/cli/chunk-nd1821sk.js.map +0 -10
  429. package/dist/cli/chunk-rekqz4qg.js.map +0 -10
  430. package/dist/cli/chunk-ta27feev.js.map +0 -10
  431. package/dist/cli/chunk-xkz5xbtv.js.map +0 -213
  432. package/dist/cli/chunk-y5h4r2y0.js.map +0 -10
  433. package/dist/cli/chunk-zf748avv.js.map +0 -37
  434. /package/dist/cli/{chunk-9geymqns.js.map → chunk-0w6hs479.js.map} +0 -0
  435. /package/dist/cli/{chunk-y4ykbark.js.map → chunk-1f882900.js.map} +0 -0
  436. /package/dist/cli/{chunk-py62fewh.js.map → chunk-53jqw8z0.js.map} +0 -0
  437. /package/dist/cli/{chunk-5wj2qwn2.js.map → chunk-58bt3hag.js.map} +0 -0
  438. /package/dist/cli/{chunk-e7vvk5ya.js.map → chunk-5cmts6dv.js.map} +0 -0
  439. /package/dist/cli/{chunk-g2z9x01v.js.map → chunk-9qzgg6mj.js.map} +0 -0
  440. /package/dist/cli/{chunk-q3wxxq39.js.map → chunk-a57yh0h9.js.map} +0 -0
  441. /package/dist/cli/{chunk-2f3ndkpw.js.map → chunk-desvm72z.js.map} +0 -0
  442. /package/dist/cli/{chunk-gp84za0h.js.map → chunk-dhwtqnnd.js.map} +0 -0
  443. /package/dist/cli/{chunk-2f7n4qzt.js.map → chunk-dqhg54mr.js.map} +0 -0
  444. /package/dist/cli/{chunk-zhvd6adm.js.map → chunk-gfc5fqvf.js.map} +0 -0
  445. /package/dist/cli/{chunk-p804f3pe.js.map → chunk-hvcxng5f.js.map} +0 -0
  446. /package/dist/cli/{chunk-1n228xqb.js.map → chunk-kdpzw38a.js.map} +0 -0
  447. /package/dist/cli/{chunk-6bm0xpgd.js.map → chunk-s0bdnc7v.js.map} +0 -0
  448. /package/dist/cli/{chunk-cmtcnh9f.js.map → chunk-tfycddbt.js.map} +0 -0
  449. /package/dist/cli/{chunk-ntv4qrt7.js.map → chunk-y4msrwm9.js.map} +0 -0
  450. /package/dist/cli/{chunk-yj17vs39.js.map → chunk-ydp3065p.js.map} +0 -0
@@ -14,22 +14,104 @@
14
14
  * prerendered Markdown 404 body with the 404 status, instead of the HTML
15
15
  * shell, and one that prefers JSON (or asks for a `.json` URL) gets the
16
16
  * prerendered problem-details 404.
17
+ *
18
+ * Under `deployment.base` every route here matches the served URL, base
19
+ * included, and names the files where Blume moves them (`static/<base>/`, see
20
+ * {@link rebaseAdapterRoutes}); `base` is the normalized base, `""` for none.
17
21
  */
18
22
 
23
+ import { mountBasePath } from "../core/base-path.ts";
24
+ import { POWERED_BY_HEADERS } from "../core/powered-by.ts";
19
25
  import { SVG_ASSET_HEADERS } from "./headers.ts";
20
26
 
21
- /**
22
- * Regex for the `accept` header condition. Written to hold under both matching
23
- * semantics a router may apply — full-string and substring — by anchoring the
24
- * end and letting `(.*,)?` absorb any earlier list entries: it requires a
25
- * `text/markdown` or `text/x-markdown` entry terminated by `;`, `,`, or the end
26
- * of the header. Kept lookaround-free so it stays valid in RE2, and lowercase
27
- * only — real agents send lowercase media types, and q-values are not compared
28
- * (a client sending `text/markdown` at `q=0` is pathological). Browsers never
29
- * send `text/markdown`, so ordinary page requests are unaffected.
27
+ /*
28
+ * The `accept` header conditions. Dev and the Cloudflare Worker negotiate the
29
+ * way `prefersMarkdown` (`astro/markdown-negotiation.ts`) does: media types in
30
+ * any case, and Markdown only when its q-value is above zero and at least
31
+ * HTML's. A routing condition is a regex over the whole header, with no
32
+ * arithmetic, so the comparison is spelled as two routes per rewrite:
33
+ *
34
+ * - a Markdown entry at full weight (no q-value, or `q=1`) always wins, since
35
+ * nothing can outrank it;
36
+ * - a Markdown entry below full weight wins only when the header has no
37
+ * `text/html` entry above zero (a `missing` condition), since two q-values
38
+ * below 1 can't be compared. That case, like `text/markdown;q=0.9,
39
+ * text/html;q=0.8`, gets HTML on Vercel where dev and Cloudflare send
40
+ * Markdown.
41
+ *
42
+ * Every pattern is anchored at both ends and lets `(?:.*,)?` absorb the
43
+ * earlier list entries, so it holds under full-string and substring matching
44
+ * alike. Case-insensitivity is spelled out letter by letter and the patterns
45
+ * are lookaround-free, so they read the same in RE2, PCRE, and JavaScript.
46
+ * Like dev, only the first `q=` parameter of an entry counts, and a `q=`
47
+ * value is read as a decimal. Browsers never send `text/markdown`, so
48
+ * ordinary page requests are unaffected.
30
49
  */
31
- export const ACCEPT_MARKDOWN_HEADER_VALUE =
32
- "(.*,)?\\s*text/(x-)?markdown(\\s*[;,].*)?$";
50
+
51
+ /** `word` in any case, one character class per letter: `[Tt][Ee][Xx][Tt]`. */
52
+ const anyCase = (word: string): string =>
53
+ word.replaceAll(/[a-z]/gu, (letter) => `[${letter.toUpperCase()}${letter}]`);
54
+
55
+ /** An entry's parameters before its first `q=`: any that isn't one. */
56
+ const OTHER_PARAMS = String.raw`(?:;\s*(?:[^\sq;,][^;,]*|q(?:[^=;,][^;,]*)?)?)*`;
57
+
58
+ /** Everything after an entry's first `q=` value, up to the next entry. */
59
+ const LATER_PARAMS = "(?:;[^,]*)?";
60
+
61
+ /** A `q=` value of 1 or more: full weight. */
62
+ const FULL_WEIGHT = String.raw`0*[1-9][0-9]*(?:\.[0-9]*)?`;
63
+
64
+ /** A `q=` value above zero and below 1. */
65
+ const PARTIAL_WEIGHT = String.raw`0*\.[0-9]*[1-9][0-9]*`;
66
+
67
+ /** A `q=` value that isn't zero: it has a character besides `0` and `.`. */
68
+ const NONZERO_WEIGHT = String.raw`[\s0.]*[^\s0.;,][^;,]*`;
69
+
70
+ /** A header with an entry of `type` weighed by `weight`, or by no `q=` when it's optional. */
71
+ const acceptEntry = (type: string, weight: string, optional: boolean) =>
72
+ String.raw`^(?:.*,)?\s*${type}\s*${OTHER_PARAMS}(?:;\s*q=\s*${weight}\s*${LATER_PARAMS})${optional ? "?" : ""}(?:,.*)?$`;
73
+
74
+ /** A header with a `type` entry at full weight. */
75
+ const fullWeight = (type: string) => acceptEntry(type, FULL_WEIGHT, true);
76
+
77
+ /** A header with a `type` entry weighed above zero and below 1. */
78
+ const partialWeight = (type: string) =>
79
+ acceptEntry(type, PARTIAL_WEIGHT, false);
80
+
81
+ /** A header with a `text/html` entry weighed above zero. */
82
+ const ACCEPT_HTML = acceptEntry(anyCase("text/html"), NONZERO_WEIGHT, true);
83
+
84
+ const MARKDOWN_TYPE = `${anyCase("text/")}(?:${anyCase("x-")})?${anyCase("markdown")}`;
85
+
86
+ const JSON_TYPE = `${anyCase("application/")}(?:${anyCase("problem")}\\+)?${anyCase("json")}`;
87
+
88
+ /** One header condition on a Build Output route. */
89
+ export interface VercelCondition {
90
+ key?: string;
91
+ type: string;
92
+ value?: string;
93
+ }
94
+
95
+ /** The conditions of one route: all of `has`, none of `missing`. */
96
+ export interface AcceptConditions {
97
+ has: VercelCondition[];
98
+ missing?: VercelCondition[];
99
+ }
100
+
101
+ const accept = (value: string): VercelCondition => ({
102
+ key: "accept",
103
+ type: "header",
104
+ value,
105
+ });
106
+
107
+ /** The pair of conditions a route answering `type` is written once for each. */
108
+ const preferring = (type: string): AcceptConditions[] => [
109
+ { has: [accept(fullWeight(type))] },
110
+ { has: [accept(partialWeight(type))], missing: [accept(ACCEPT_HTML)] },
111
+ ];
112
+
113
+ /** When a client prefers `text/markdown` (or `text/x-markdown`) over HTML. */
114
+ export const ACCEPT_MARKDOWN_CONDITIONS = preferring(MARKDOWN_TYPE);
33
115
 
34
116
  /**
35
117
  * The JSON counterpart, for the problem-details 404: `application/json` or
@@ -37,8 +119,7 @@ export const ACCEPT_MARKDOWN_HEADER_VALUE =
37
119
  * (the catch-all wildcard does not match), so ordinary page requests are
38
120
  * unaffected.
39
121
  */
40
- export const ACCEPT_JSON_HEADER_VALUE =
41
- "(.*,)?\\s*application/(problem\\+)?json(\\s*[;,].*)?$";
122
+ export const ACCEPT_JSON_CONDITIONS = preferring(JSON_TYPE);
42
123
 
43
124
  /**
44
125
  * A Build Output API route — the subset these helpers read and write. Parsed
@@ -49,23 +130,29 @@ export interface VercelRoute {
49
130
  continue?: boolean;
50
131
  dest?: string;
51
132
  handle?: string;
52
- has?: { key?: string; type: string; value?: string }[];
133
+ has?: VercelCondition[];
53
134
  headers?: Record<string, string>;
135
+ missing?: VercelCondition[];
54
136
  src?: string;
55
137
  status?: number;
56
138
  }
57
139
 
140
+ /** `route` once per condition pair, so it fires when either holds. */
141
+ const conditioned = (
142
+ route: VercelRoute,
143
+ conditions: readonly AcceptConditions[]
144
+ ): VercelRoute[] => conditions.map((condition) => ({ ...route, ...condition }));
145
+
58
146
  /** Whether a parsed route field is a real string (the config is raw JSON). */
59
147
  const isString = (value: string | undefined): value is string =>
60
148
  typeof value === "string";
61
149
 
62
- const ACCEPT_MARKDOWN_CONDITION: VercelRoute["has"] = [
63
- { key: "accept", type: "header", value: ACCEPT_MARKDOWN_HEADER_VALUE },
64
- ];
65
-
66
- const ACCEPT_JSON_CONDITION: VercelRoute["has"] = [
67
- { key: "accept", type: "header", value: ACCEPT_JSON_HEADER_VALUE },
68
- ];
150
+ /** The Markdown conditions' `has` values, to recognize a route this module wrote. */
151
+ const MARKDOWN_VALUES = new Set(
152
+ ACCEPT_MARKDOWN_CONDITIONS.flatMap(({ has }) =>
153
+ has.map((condition) => condition.value)
154
+ )
155
+ );
69
156
 
70
157
  const VARY_ACCEPT = { vary: "Accept" };
71
158
 
@@ -78,44 +165,6 @@ const NOT_FOUND_JSON_DEST = "/404.json";
78
165
  /** The adapter's own not-found fallback — the anchor the Markdown 404 precedes. */
79
166
  const NOT_FOUND_HTML_DEST = "/404.html";
80
167
 
81
- /**
82
- * Miss-phase routes that answer a missing page with the Markdown 404 body: any
83
- * path when the client prefers Markdown, and any `.md`/`.mdx` URL (a request
84
- * for a raw-Markdown mirror that has no page wants Markdown back, not the HTML
85
- * shell). Both keep the 404 status. Spliced immediately before the adapter's
86
- * `/404.html` fallback, so they run after every server route (the MCP
87
- * endpoint, server islands, images) has had its turn and never hijack a
88
- * request one of those would have answered.
89
- */
90
- const NOT_FOUND_MARKDOWN_ROUTES: readonly VercelRoute[] = [
91
- {
92
- dest: NOT_FOUND_MARKDOWN_DEST,
93
- has: ACCEPT_MARKDOWN_CONDITION,
94
- headers: VARY_ACCEPT,
95
- src: "^/.*$",
96
- status: 404,
97
- },
98
- { dest: NOT_FOUND_MARKDOWN_DEST, src: "^/.*\\.mdx?$", status: 404 },
99
- ];
100
-
101
- /**
102
- * The JSON 404's miss-phase routes, the problem-details twin of the Markdown
103
- * ones: any path when the client prefers JSON, and any `.json` URL no file
104
- * backs. Spliced at the same anchor, after every server route — so the
105
- * `/api/` catch-all (which answers its own namespace with a problem document)
106
- * has already had its turn.
107
- */
108
- const NOT_FOUND_JSON_ROUTES: readonly VercelRoute[] = [
109
- {
110
- dest: NOT_FOUND_JSON_DEST,
111
- has: ACCEPT_JSON_CONDITION,
112
- headers: VARY_ACCEPT,
113
- src: "^/.*$",
114
- status: 404,
115
- },
116
- { dest: NOT_FOUND_JSON_DEST, src: "^/.*\\.json$", status: 404 },
117
- ];
118
-
119
168
  /** Which prerendered 404 twins the build emitted, so their routes get wired. */
120
169
  export interface NotFoundVariants {
121
170
  json?: boolean;
@@ -140,6 +189,53 @@ const REGEX_SPECIALS = /[$()*+.?[\]^{|}\\]/gu;
140
189
  const routePattern = (route: string): string =>
141
190
  encodeURI(route).replace(REGEX_SPECIALS, "\\$&");
142
191
 
192
+ /**
193
+ * The `src` matching the homepage: `^/$`, or the base itself under one, served
194
+ * without a trailing slash (the adapter's trailing-slash route strips it).
195
+ */
196
+ const homeSource = (base: string): string =>
197
+ `^${routePattern(mountBasePath(base, "/"))}$`;
198
+
199
+ /**
200
+ * Miss-phase routes that answer a missing page with the Markdown 404 body: any
201
+ * path when the client prefers Markdown, and any `.md`/`.mdx` URL (a request
202
+ * for a raw-Markdown mirror that has no page wants Markdown back, not the HTML
203
+ * shell). Both keep the 404 status. Spliced immediately before the adapter's
204
+ * `/404.html` fallback, so they run after every server route (the MCP
205
+ * endpoint, server islands, images) has had its turn and never hijack a
206
+ * request one of those would have answered.
207
+ */
208
+ const notFoundMarkdownRoutes = (base: string): VercelRoute[] => {
209
+ const dest = mountBasePath(base, NOT_FOUND_MARKDOWN_DEST);
210
+ const prefix = routePattern(base);
211
+ return [
212
+ ...conditioned(
213
+ { dest, headers: VARY_ACCEPT, src: `^${prefix}/.*$`, status: 404 },
214
+ ACCEPT_MARKDOWN_CONDITIONS
215
+ ),
216
+ { dest, src: `^${prefix}/.*\\.mdx?$`, status: 404 },
217
+ ];
218
+ };
219
+
220
+ /**
221
+ * The JSON 404's miss-phase routes, the problem-details twin of the Markdown
222
+ * ones: any path when the client prefers JSON, and any `.json` URL no file
223
+ * backs. Spliced at the same anchor, after every server route — so the
224
+ * `/api/` catch-all (which answers its own namespace with a problem document)
225
+ * has already had its turn.
226
+ */
227
+ const notFoundJsonRoutes = (base: string): VercelRoute[] => {
228
+ const dest = mountBasePath(base, NOT_FOUND_JSON_DEST);
229
+ const prefix = routePattern(base);
230
+ return [
231
+ ...conditioned(
232
+ { dest, headers: VARY_ACCEPT, src: `^${prefix}/.*$`, status: 404 },
233
+ ACCEPT_JSON_CONDITIONS
234
+ ),
235
+ { dest, src: `^${prefix}/.*\\.json$`, status: 404 },
236
+ ];
237
+ };
238
+
143
239
  /** Group patterns so each group's alternation stays under the `src` limit. */
144
240
  const chunkPatterns = (patterns: readonly string[]): string[][] => {
145
241
  const chunks: string[][] = [];
@@ -190,41 +286,49 @@ export interface NegotiationRoutes {
190
286
  * rewrite also stamps `x-markdown-tokens` — the estimated token count of the
191
287
  * homepage mirror (Cloudflare's Markdown for Agents convention). Only the home
192
288
  * route can carry it: the other rewrites are chunked alternations spanning
193
- * many pages, and a count is per-page.
289
+ * many pages, and a count is per-page. Under a `base`, each path is matched
290
+ * and rewritten where it's served (`/docs` → `/docs/index.md`).
194
291
  */
195
292
  export const buildNegotiationRoutes = (
196
293
  routePaths: readonly string[],
197
- homeTokens?: number
294
+ homeTokens?: number,
295
+ base = ""
198
296
  ): NegotiationRoutes => {
199
297
  const home = routePaths.includes("/");
200
298
  const rest = routePaths
201
299
  .filter((path) => path !== "/")
202
- .map((path) => routePattern(path));
300
+ .map((path) => routePattern(mountBasePath(base, path)));
203
301
  const chunks = chunkPatterns(rest);
204
302
 
205
303
  const rewriteRoutes: VercelRoute[] = home
206
- ? [
304
+ ? conditioned(
207
305
  {
208
- dest: "/index.md",
209
- has: ACCEPT_MARKDOWN_CONDITION,
306
+ dest: mountBasePath(base, "/index.md"),
210
307
  headers:
211
308
  homeTokens === undefined
212
309
  ? VARY_ACCEPT
213
310
  : { ...VARY_ACCEPT, "x-markdown-tokens": String(homeTokens) },
214
- src: "^/$",
311
+ src: homeSource(base),
215
312
  },
216
- ]
313
+ ACCEPT_MARKDOWN_CONDITIONS
314
+ )
217
315
  : [];
218
316
  for (const chunk of chunks) {
219
- rewriteRoutes.push({
220
- dest: "$1.md",
221
- has: ACCEPT_MARKDOWN_CONDITION,
222
- headers: VARY_ACCEPT,
223
- src: `^(${chunk.join("|")})/?$`,
224
- });
317
+ rewriteRoutes.push(
318
+ ...conditioned(
319
+ {
320
+ dest: "$1.md",
321
+ headers: VARY_ACCEPT,
322
+ src: `^(${chunk.join("|")})/?$`,
323
+ },
324
+ ACCEPT_MARKDOWN_CONDITIONS
325
+ )
326
+ );
225
327
  }
226
328
 
227
- const headerChunks = chunkPatterns(home ? ["/", ...rest] : rest);
329
+ const headerChunks = chunkPatterns(
330
+ home ? [routePattern(mountBasePath(base, "/")), ...rest] : rest
331
+ );
228
332
  const headerRoutes: VercelRoute[] = headerChunks.map((chunk) => ({
229
333
  continue: true,
230
334
  headers: VARY_ACCEPT,
@@ -234,9 +338,6 @@ export const buildNegotiationRoutes = (
234
338
  return { headerRoutes, rewriteRoutes };
235
339
  };
236
340
 
237
- /** The `src` of the injected homepage `Link` header route. */
238
- const HOME_SRC = "^/$";
239
-
240
341
  const ALLOW_ANY_ORIGIN = "*";
241
342
 
242
343
  /**
@@ -244,18 +345,18 @@ const ALLOW_ANY_ORIGIN = "*";
244
345
  * one static discovery document (see `crossOriginDiscoveryPaths`), so a
245
346
  * registry reading it from another origin isn't refused by the browser.
246
347
  */
247
- const corsRoute = (path: string): VercelRoute => ({
348
+ const corsRoute = (path: string, base: string): VercelRoute => ({
248
349
  continue: true,
249
350
  headers: { "access-control-allow-origin": ALLOW_ANY_ORIGIN },
250
- src: `^${routePattern(path)}$`,
351
+ src: `^${routePattern(mountBasePath(base, path))}$`,
251
352
  });
252
353
 
253
354
  /**
254
355
  * The `src` of the route stamping the content-source SVG headers: every `.svg`
255
- * under `/blume-assets/`. The adapter copies the static files into the Build
256
- * Output tree without the deployment base, like every path matched here.
356
+ * under `/blume-assets/`.
257
357
  */
258
- const SVG_ASSET_SRC = String.raw`^/blume-assets/.+\.svg$`;
358
+ const svgAssetSource = (base: string): string =>
359
+ String.raw`^${routePattern(base)}/blume-assets/.+\.svg$`;
259
360
 
260
361
  /**
261
362
  * A main-phase `continue` route stamping the sandbox headers
@@ -263,7 +364,7 @@ const SVG_ASSET_SRC = String.raw`^/blume-assets/.+\.svg$`;
263
364
  * static layer — where the prerendered asset endpoint's own headers never
264
365
  * reach.
265
366
  */
266
- const svgAssetRoute = (): VercelRoute => ({
367
+ const svgAssetRoute = (base: string): VercelRoute => ({
267
368
  continue: true,
268
369
  headers: Object.fromEntries(
269
370
  Object.entries(SVG_ASSET_HEADERS).map(([name, value]) => [
@@ -271,9 +372,34 @@ const svgAssetRoute = (): VercelRoute => ({
271
372
  value,
272
373
  ])
273
374
  ),
274
- src: SVG_ASSET_SRC,
375
+ src: svgAssetSource(base),
275
376
  });
276
377
 
378
+ /** The `src` of the `X-Powered-By` route: the base and every path beneath it. */
379
+ const poweredBySource = (base: string): string =>
380
+ `^${routePattern(base)}(?:/.*)?$`;
381
+
382
+ /**
383
+ * A main-phase `continue` route naming Blume in `X-Powered-By` (see
384
+ * `core/powered-by.ts`) on every response: the prerendered pages and files
385
+ * the static layer serves, which Blume's runtime middleware never sees, the
386
+ * functions, and the redirects.
387
+ */
388
+ const poweredByRoute = (base: string): VercelRoute => ({
389
+ continue: true,
390
+ headers: Object.fromEntries(
391
+ Object.entries(POWERED_BY_HEADERS).map(([name, value]) => [
392
+ name.toLowerCase(),
393
+ value,
394
+ ])
395
+ ),
396
+ src: poweredBySource(base),
397
+ });
398
+
399
+ /** Whether a route is the {@link poweredByRoute}. */
400
+ const isPoweredByRoute = (route: VercelRoute, base: string): boolean =>
401
+ route.continue === true && route.src === poweredBySource(base);
402
+
277
403
  /** Whether a route is a `corsRoute` — the same three-field shape test as the others. */
278
404
  const isCorsRoute = (route: VercelRoute): boolean =>
279
405
  route.continue === true &&
@@ -289,24 +415,27 @@ const isCorsRoute = (route: VercelRoute): boolean =>
289
415
  * the one re-added); the homepage `Link` route by its three-field
290
416
  * continue-with-link shape (the Build Output config is adapter-generated, so
291
417
  * no user-authored route competes in this file); the Markdown 404 routes by
292
- * their `/404.md` destination.
418
+ * their `/404.md` destination; the SVG and `X-Powered-By` routes by their
419
+ * `src`.
293
420
  */
294
- const isNegotiationRoute = (route: VercelRoute): boolean =>
295
- route.has?.some(
296
- (condition) => condition.value === ACCEPT_MARKDOWN_HEADER_VALUE
297
- ) === true ||
298
- (route.dest === NOT_FOUND_MARKDOWN_DEST && route.status === 404) ||
299
- (route.dest === NOT_FOUND_JSON_DEST && route.status === 404) ||
421
+ const isNegotiationRoute = (route: VercelRoute, base: string): boolean =>
422
+ route.has?.some((condition) => MARKDOWN_VALUES.has(condition.value)) ===
423
+ true ||
424
+ (route.dest === mountBasePath(base, NOT_FOUND_MARKDOWN_DEST) &&
425
+ route.status === 404) ||
426
+ (route.dest === mountBasePath(base, NOT_FOUND_JSON_DEST) &&
427
+ route.status === 404) ||
300
428
  (route.continue === true &&
301
429
  route.headers?.vary === "Accept" &&
302
430
  isString(route.src) &&
303
431
  Object.keys(route).length === 3) ||
304
432
  (route.continue === true &&
305
433
  isString(route.headers?.link) &&
306
- route.src === HOME_SRC &&
434
+ route.src === homeSource(base) &&
307
435
  Object.keys(route).length === 3) ||
308
436
  isCorsRoute(route) ||
309
- (route.continue === true && route.src === SVG_ASSET_SRC);
437
+ (route.continue === true && route.src === svgAssetSource(base)) ||
438
+ isPoweredByRoute(route, base);
310
439
 
311
440
  /**
312
441
  * Splice the negotiation routes into a Build Output `config.json`, plus — when
@@ -325,7 +454,11 @@ const isNegotiationRoute = (route: VercelRoute): boolean =>
325
454
  * phase right before the adapter's `/404.html` fallback — and nowhere when
326
455
  * that fallback is absent, since a `dest` with no file behind it would serve
327
456
  * nothing. With `svgAssets`, a main-phase route also sandboxes the SVGs a
328
- * content source downloaded (see {@link svgAssetRoute}). Returns the updated JSON
457
+ * content source downloaded (see {@link svgAssetRoute}). With `poweredBy`, a
458
+ * route at the head of the config, ahead of the adapter's redirects, names
459
+ * Blume on every response (see {@link poweredByRoute}). Under a `base`, every
460
+ * route matches and names paths beneath it, and the fallback anchor is the
461
+ * one {@link rebaseAdapterRoutes} moved there. Returns the updated JSON
329
462
  * text (tab-indented, like the adapter's own output), or `null` when there is
330
463
  * nowhere safe to splice: an unparsable config, no `routes` array, or no
331
464
  * `handle: "filesystem"` marker to anchor the splice.
@@ -338,7 +471,9 @@ export const injectNegotiationRoutes = (
338
471
  homeTokens?: number,
339
472
  notFound: NotFoundVariants = {},
340
473
  corsPaths: readonly string[] = [],
341
- svgAssets = false
474
+ svgAssets = false,
475
+ base = "",
476
+ poweredBy = false
342
477
  ): string | null => {
343
478
  const overrideEntries = Object.entries(contentTypeOverrides ?? {});
344
479
  let config: {
@@ -358,7 +493,9 @@ export const injectNegotiationRoutes = (
358
493
  // user's own override of the same path is simply refreshed.
359
494
  config.overrides = { ...config.overrides, [path]: { contentType } };
360
495
  }
361
- const routes = config.routes.filter((route) => !isNegotiationRoute(route));
496
+ const routes = config.routes.filter(
497
+ (route) => !isNegotiationRoute(route, base)
498
+ );
362
499
  const filesystemIndex = routes.findIndex(
363
500
  (route) => route.handle === "filesystem"
364
501
  );
@@ -367,30 +504,35 @@ export const injectNegotiationRoutes = (
367
504
  }
368
505
  const { headerRoutes, rewriteRoutes } = buildNegotiationRoutes(
369
506
  routePaths,
370
- homeTokens
507
+ homeTokens,
508
+ base
371
509
  );
372
510
  if (homeLinkHeader) {
373
511
  headerRoutes.push({
374
512
  continue: true,
375
513
  headers: { link: homeLinkHeader },
376
- src: HOME_SRC,
514
+ src: homeSource(base),
377
515
  });
378
516
  }
379
- headerRoutes.push(...corsPaths.map(corsRoute));
517
+ headerRoutes.push(...corsPaths.map((path) => corsRoute(path, base)));
380
518
  if (svgAssets) {
381
- headerRoutes.push(svgAssetRoute());
519
+ headerRoutes.push(svgAssetRoute(base));
382
520
  }
383
521
  // Headers first: `continue` routes accumulate, so a request the rewrite
384
522
  // route then terminates (Markdown negotiation on the homepage) still carries
385
523
  // the Link header.
386
524
  routes.splice(filesystemIndex, 0, ...headerRoutes, ...rewriteRoutes);
525
+ if (poweredBy) {
526
+ routes.unshift(poweredByRoute(base));
527
+ }
387
528
  const notFoundRoutes = [
388
- ...(notFound.markdown ? NOT_FOUND_MARKDOWN_ROUTES : []),
389
- ...(notFound.json ? NOT_FOUND_JSON_ROUTES : []),
529
+ ...(notFound.markdown ? notFoundMarkdownRoutes(base) : []),
530
+ ...(notFound.json ? notFoundJsonRoutes(base) : []),
390
531
  ];
391
532
  if (notFoundRoutes.length > 0) {
533
+ const fallbackDest = mountBasePath(base, NOT_FOUND_HTML_DEST);
392
534
  const fallbackIndex = routes.findIndex(
393
- (route) => route.status === 404 && route.dest === NOT_FOUND_HTML_DEST
535
+ (route) => route.status === 404 && route.dest === fallbackDest
394
536
  );
395
537
  if (fallbackIndex !== -1) {
396
538
  routes.splice(fallbackIndex, 0, ...notFoundRoutes);
@@ -435,3 +577,83 @@ export const injectRedirectRoutes = (
435
577
  config.routes = routes;
436
578
  return `${JSON.stringify(config, null, "\t")}\n`;
437
579
  };
580
+
581
+ /**
582
+ * The trailing-slash route `trailingSlash: "never"` gives the adapter's config
583
+ * (`^/(.*)/$` → `/$1`): it keeps whatever path it strips the slash from, base
584
+ * and all, so it already serves a based site.
585
+ */
586
+ const isTrailingSlashRoute = (route: VercelRoute): boolean =>
587
+ route.headers?.Location === "/$1";
588
+
589
+ /** The pattern body of the root route, anchored (`^/$`) or not (`/`). */
590
+ const ROOT_BODY = /^\/\$?$/u;
591
+
592
+ /**
593
+ * Re-anchor an adapter route pattern under the base, whose escaped form is
594
+ * `prefix`: each matches a base-less path from its start, and the root route
595
+ * becomes the base itself, served without a trailing slash.
596
+ */
597
+ const rebaseSource = (src: string, prefix: string): string => {
598
+ const body = src.startsWith("^") ? src.slice(1) : src;
599
+ return `^${prefix}${ROOT_BODY.test(body) ? "$" : body}`;
600
+ };
601
+
602
+ /**
603
+ * Move the adapter's routes under `deployment.base`, to match the static files
604
+ * Blume moves to `static/<base>/`. `@astrojs/vercel` routes a server build as
605
+ * if it were served from the root: its route patterns (the `_astro/` cache
606
+ * header, every server route) match base-less paths, its 404 fallback names
607
+ * `/404.html`, and it joins the base onto each redirect with no slash on the
608
+ * `from` side and a second time on the `to` side, which Astro hands it
609
+ * already based (`^/docsold$` → `/docs/docs/new`). Each pattern is
610
+ * re-anchored under the base and the fallback names the base's `404.html`;
611
+ * the adapter's redirects make way for `redirectRoutes`, the exact redirects
612
+ * as the host matches them. Returns the updated JSON text, or `null` when
613
+ * there is nowhere safe to splice: an unparsable config, no `routes` array,
614
+ * or no `handle: "filesystem"` marker.
615
+ */
616
+ export const rebaseAdapterRoutes = (
617
+ configText: string,
618
+ base: string,
619
+ redirectRoutes: readonly VercelRoute[]
620
+ ): string | null => {
621
+ let config: { routes?: VercelRoute[] };
622
+ try {
623
+ config = JSON.parse(configText);
624
+ } catch {
625
+ return null;
626
+ }
627
+ if (!Array.isArray(config.routes)) {
628
+ return null;
629
+ }
630
+ const filesystemIndex = config.routes.findIndex(
631
+ (route) => route.handle === "filesystem"
632
+ );
633
+ if (filesystemIndex === -1) {
634
+ return null;
635
+ }
636
+ const prefix = routePattern(base);
637
+ const routes = config.routes.flatMap((route, index): VercelRoute[] => {
638
+ // Ahead of the filesystem handler, the adapter writes its redirects and
639
+ // nothing else with a `Location`.
640
+ if (index < filesystemIndex && isString(route.headers?.Location)) {
641
+ return isTrailingSlashRoute(route) ? [route] : [];
642
+ }
643
+ const rebased = { ...route };
644
+ if (isString(route.src)) {
645
+ rebased.src = rebaseSource(route.src, prefix);
646
+ }
647
+ if (route.status === 404 && route.dest === NOT_FOUND_HTML_DEST) {
648
+ rebased.dest = mountBasePath(base, NOT_FOUND_HTML_DEST);
649
+ }
650
+ return [rebased];
651
+ });
652
+ routes.splice(
653
+ routes.findIndex((route) => route.handle === "filesystem"),
654
+ 0,
655
+ ...redirectRoutes
656
+ );
657
+ config.routes = routes;
658
+ return `${JSON.stringify(config, null, "\t")}\n`;
659
+ };
@@ -188,7 +188,24 @@ const CLAUDE_READER_MAX_TURNS = "25";
188
188
  const CLAUDE_JUDGE_MAX_TURNS = "1";
189
189
 
190
190
  const claudeArgs = (context: InvocationContext): string[] => {
191
- const base = ["-p", "--output-format", "json", "--strict-mcp-config"];
191
+ // `--tools ""` turns off every Claude Code built-in, including ones the
192
+ // deny list below doesn't name (`Monitor` runs shell commands, `Skill`
193
+ // loads the user's skills), so a reader has only the docs MCP tools and the
194
+ // judge has no tools at all. The deny list stays as a second layer.
195
+ // `--setting-sources ""` is codex's `--ignore-user-config`: no settings
196
+ // files, so the user's hooks, plugins, and CLAUDE.md can't hand the reader
197
+ // context the docs never gave it. Not `--bare`, which also stops reading a
198
+ // subscription login and fails with "Not logged in".
199
+ const base = [
200
+ "-p",
201
+ "--output-format",
202
+ "json",
203
+ "--strict-mcp-config",
204
+ "--tools",
205
+ "",
206
+ "--setting-sources",
207
+ "",
208
+ ];
192
209
  if (context.mcp) {
193
210
  const allowed = MCP_TOOL_NAMES.map(
194
211
  (tool) => `mcp__${MCP_SERVER_NAME}__${tool}`
@@ -40,8 +40,11 @@ Reply with ONLY this JSON object on a single line, no markdown fences:
40
40
  {"pass": true|false, "score": 0.0-1.0, "missing": ["expected facts absent or contradicted"], "notes": "one sentence"}`;
41
41
  };
42
42
 
43
- /** The `--fix` handoff prompt: where the report is and the ground rules. */
44
- export const evalFixPrompt = (reportPath: string): string =>
43
+ /**
44
+ * The `--fix` handoff prompt: where the report is, the ground rules, and the
45
+ * `blume eval` command that verifies the fixes (the run's own flags).
46
+ */
47
+ export const evalFixPrompt = (reportPath: string, command: string): string =>
45
48
  `Fix the documentation gaps found by \`blume eval\` in this project.
46
49
 
47
50
  The full report is at ${reportPath}. It is JSON: each entry in \`eval.results\` with status "fail" is one question the documentation could not answer. Each carries the \`question\`, the \`expected\` facts, the judge's \`missing\` facts, and the reader agent's \`answer\` (what the docs currently convey). The matching \`diagnostics\` entry names the source \`file\` of the page that should answer it.
@@ -51,7 +54,7 @@ Work through every failed question:
51
54
  2. Edit the documentation so it states the missing facts explicitly. Add prose, not filler; keep the page's voice.
52
55
  3. Never delete questions from the evals file or weaken expected facts.
53
56
 
54
- When you are done, run \`blume eval\` to verify, and repeat until every question passes.`;
57
+ When you are done, run \`${command}\` to verify, and repeat until every question passes.`;
55
58
 
56
59
  /** The `eval init` prompt: draft a starter evals file from the docs. */
57
60
  export const initPrompt = (evalsPath: string): string =>