blume 2.0.1 → 2.0.3

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 (492) hide show
  1. package/CHANGELOG.md +147 -0
  2. package/dist/cli/{chunk-qwsrynx5.js → chunk-1d7ve1dm.js} +38 -19
  3. package/dist/cli/{chunk-qwsrynx5.js.map → chunk-1d7ve1dm.js.map} +4 -4
  4. package/dist/cli/{chunk-s6jhgk0q.js → chunk-2eytanqx.js} +2 -2
  5. package/dist/cli/{chunk-kdp5q7ke.js → chunk-2hsdwb9n.js} +18 -19
  6. package/dist/cli/{chunk-kdp5q7ke.js.map → chunk-2hsdwb9n.js.map} +2 -2
  7. package/dist/cli/{chunk-hdpx1tax.js → chunk-2z928egk.js} +5 -5
  8. package/dist/cli/{chunk-jwyddg7y.js → chunk-35d4wj9f.js} +30 -24
  9. package/dist/cli/chunk-35d4wj9f.js.map +15 -0
  10. package/dist/cli/{chunk-ah61y8py.js → chunk-364znk6q.js} +2 -2
  11. package/dist/cli/{chunk-jts8mvcz.js → chunk-3em5wd2y.js} +25 -6
  12. package/dist/cli/{chunk-jts8mvcz.js.map → chunk-3em5wd2y.js.map} +3 -3
  13. package/dist/cli/{chunk-s1p84fyh.js → chunk-5f86nr5m.js} +63 -22
  14. package/dist/cli/chunk-5f86nr5m.js.map +11 -0
  15. package/dist/cli/{chunk-2hn4b8z7.js → chunk-5m5nmvyq.js} +170 -76
  16. package/dist/cli/chunk-5m5nmvyq.js.map +13 -0
  17. package/dist/cli/{chunk-epjnccmv.js → chunk-5xvm6tfj.js} +18 -15
  18. package/dist/cli/chunk-5xvm6tfj.js.map +10 -0
  19. package/dist/cli/{chunk-fz5wtpmh.js → chunk-6dsbexzp.js} +18 -15
  20. package/dist/cli/chunk-6dsbexzp.js.map +10 -0
  21. package/dist/cli/{chunk-vtk4a6dg.js → chunk-8ktnccpt.js} +1 -1
  22. package/dist/cli/{chunk-6hsn950k.js → chunk-9t7a85s3.js} +178 -48
  23. package/dist/cli/chunk-9t7a85s3.js.map +10 -0
  24. package/dist/cli/{chunk-fxypxtvm.js → chunk-a58773jm.js} +2 -2
  25. package/dist/cli/{chunk-mb2919y2.js → chunk-acanzt5p.js} +17 -6
  26. package/dist/cli/chunk-acanzt5p.js.map +10 -0
  27. package/dist/cli/{chunk-27g6wdth.js → chunk-akbpwfxc.js} +90 -26
  28. package/dist/cli/chunk-akbpwfxc.js.map +10 -0
  29. package/dist/cli/{chunk-wm7js3j9.js → chunk-b07cmahc.js} +2 -2
  30. package/dist/cli/{chunk-wgm7m9qk.js → chunk-c8chx29p.js} +179 -51
  31. package/dist/cli/chunk-c8chx29p.js.map +36 -0
  32. package/dist/cli/{chunk-qs4q5p4e.js → chunk-crgn1q09.js} +32 -14
  33. package/dist/cli/chunk-crgn1q09.js.map +10 -0
  34. package/dist/cli/chunk-e04dxsz1.js +39 -0
  35. package/dist/cli/chunk-e04dxsz1.js.map +10 -0
  36. package/dist/cli/{chunk-yt5n7ppj.js → chunk-ey84smr6.js} +17 -7
  37. package/dist/cli/chunk-ey84smr6.js.map +10 -0
  38. package/dist/cli/{chunk-f2z5v128.js → chunk-g4hq16wv.js} +14 -15
  39. package/dist/cli/{chunk-f2z5v128.js.map → chunk-g4hq16wv.js.map} +2 -2
  40. package/dist/cli/{chunk-fa25z98p.js → chunk-ga0pf4aj.js} +9 -8
  41. package/dist/cli/chunk-ga0pf4aj.js.map +11 -0
  42. package/dist/cli/{chunk-kpf8rrjc.js → chunk-hm3vjy5s.js} +109 -58
  43. package/dist/cli/chunk-hm3vjy5s.js.map +19 -0
  44. package/dist/cli/{chunk-fs23ddbb.js → chunk-j85vccga.js} +625 -750
  45. package/dist/cli/chunk-j85vccga.js.map +36 -0
  46. package/dist/cli/{chunk-ch6g3ar0.js → chunk-p3v96n38.js} +6 -6
  47. package/dist/cli/{chunk-ch6g3ar0.js.map → chunk-p3v96n38.js.map} +3 -3
  48. package/dist/cli/{chunk-q5163e60.js → chunk-p73c0m7w.js} +21 -19
  49. package/dist/cli/chunk-p73c0m7w.js.map +11 -0
  50. package/dist/cli/{chunk-dh8cwk36.js → chunk-pehfxfta.js} +24 -9
  51. package/dist/cli/chunk-pehfxfta.js.map +10 -0
  52. package/dist/cli/{chunk-qkqwkpte.js → chunk-pv29h0wf.js} +3688 -1313
  53. package/dist/cli/chunk-pv29h0wf.js.map +190 -0
  54. package/dist/cli/{chunk-6vm74dry.js → chunk-q58y5e6a.js} +10 -10
  55. package/dist/cli/{chunk-6vm74dry.js.map → chunk-q58y5e6a.js.map} +3 -3
  56. package/dist/cli/{chunk-zxcczpyx.js → chunk-r20tn01b.js} +1 -1
  57. package/dist/cli/{chunk-m3vmjgmq.js → chunk-r9rcc4w7.js} +16 -8
  58. package/dist/cli/chunk-r9rcc4w7.js.map +10 -0
  59. package/dist/cli/{chunk-5shv93fd.js → chunk-tkacnehg.js} +2 -2
  60. package/dist/cli/{chunk-zxh4d9vy.js → chunk-tzmab476.js} +4 -4
  61. package/dist/cli/{chunk-yw7dm696.js → chunk-vg9r4eb9.js} +24 -11
  62. package/dist/cli/chunk-vg9r4eb9.js.map +14 -0
  63. package/dist/cli/{chunk-79jhk4py.js → chunk-wrr3j9w9.js} +260 -132
  64. package/dist/cli/chunk-wrr3j9w9.js.map +35 -0
  65. package/dist/cli/{chunk-6crbhc3x.js → chunk-yfyb25rh.js} +54 -27
  66. package/dist/cli/chunk-yfyb25rh.js.map +14 -0
  67. package/dist/cli/index.js +162 -35
  68. package/dist/cli/index.js.map +4 -4
  69. package/dist/types/ai/agent-surface.d.ts +32 -0
  70. package/dist/types/ai/api-catalog.d.ts +7 -1
  71. package/dist/types/ai/ask-context.d.ts +7 -0
  72. package/dist/types/ai/component-markdown.d.ts +4 -4
  73. package/dist/types/ai/link-headers.d.ts +8 -1
  74. package/dist/types/ai/openapi-components.d.ts +5 -2
  75. package/dist/types/ai/relative-links.d.ts +11 -4
  76. package/dist/types/ai/skills.d.ts +4 -1
  77. package/dist/types/ai/static-expression.d.ts +28 -0
  78. package/dist/types/ai/tar.d.ts +1 -3
  79. package/dist/types/analytics/databuddy.d.ts +43 -0
  80. package/dist/types/analytics/index.d.ts +4 -0
  81. package/dist/types/analytics/one-dollar-stats.d.ts +48 -0
  82. package/dist/types/analytics/schema.d.ts +28 -0
  83. package/dist/types/astro/integration.d.ts +3 -2
  84. package/dist/types/cli/env.d.ts +5 -0
  85. package/dist/types/cli/init/scaffold.d.ts +19 -3
  86. package/dist/types/cli/init/starter-spec.d.ts +11 -0
  87. package/dist/types/core/base-path.d.ts +24 -1
  88. package/dist/types/core/config-input.d.ts +13 -11
  89. package/dist/types/core/config.d.ts +2 -2
  90. package/dist/types/core/directive-diagnostics.d.ts +12 -0
  91. package/dist/types/core/graph.d.ts +2 -0
  92. package/dist/types/core/heading-markers.d.ts +5 -7
  93. package/dist/types/core/i18n-ui.d.ts +31 -0
  94. package/dist/types/core/i18n.d.ts +9 -1
  95. package/dist/types/core/last-modified.d.ts +10 -0
  96. package/dist/types/core/links.d.ts +3 -1
  97. package/dist/types/core/load-module.d.ts +10 -0
  98. package/dist/types/core/locale-links.d.ts +12 -2
  99. package/dist/types/core/meta.d.ts +13 -1
  100. package/dist/types/core/nav-diagnostics.d.ts +10 -0
  101. package/dist/types/core/navigation.d.ts +38 -0
  102. package/dist/types/core/ordering-prefix.d.ts +4 -0
  103. package/dist/types/core/safe-links.d.ts +3 -1
  104. package/dist/types/core/schema.d.ts +59 -13
  105. package/dist/types/core/sources/github-releases.d.ts +5 -0
  106. package/dist/types/core/sources/lower.d.ts +38 -13
  107. package/dist/types/core/sources/normalize.d.ts +23 -2
  108. package/dist/types/core/sources/remote.d.ts +11 -1
  109. package/dist/types/core/sources/resolve.d.ts +12 -0
  110. package/dist/types/core/sources/types.d.ts +31 -0
  111. package/dist/types/core/sources/watch.d.ts +12 -5
  112. package/dist/types/core/standard-schema.d.ts +5 -0
  113. package/dist/types/core/types.d.ts +9 -0
  114. package/dist/types/deploy/adapters/node.d.ts +5 -2
  115. package/dist/types/deploy/adapters/types.d.ts +7 -0
  116. package/dist/types/deploy/artifacts.d.ts +6 -4
  117. package/dist/types/deploy/cloudflare-negotiation.d.ts +3 -2
  118. package/dist/types/deploy/headers.d.ts +36 -7
  119. package/dist/types/deploy/node-headers.d.ts +43 -8
  120. package/dist/types/deploy/platforms/netlify.d.ts +27 -2
  121. package/dist/types/deploy/platforms/node.d.ts +6 -5
  122. package/dist/types/deploy/platforms/types.d.ts +15 -0
  123. package/dist/types/deploy/platforms/vercel.d.ts +3 -2
  124. package/dist/types/deploy/redirects.d.ts +31 -15
  125. package/dist/types/deploy/vercel-negotiation.d.ts +3 -2
  126. package/dist/types/markdown/directives.d.ts +62 -0
  127. package/dist/types/markdown/features.d.ts +21 -0
  128. package/dist/types/markdown/mdast.d.ts +63 -0
  129. package/dist/types/openapi/asyncapi.d.ts +4 -2
  130. package/dist/types/openapi/model.d.ts +20 -6
  131. package/dist/types/search/sync/algolia.d.ts +3 -1
  132. package/docs/01-quickstart.mdx +3 -2
  133. package/docs/02-deployment.mdx +20 -9
  134. package/docs/08-faq.mdx +10 -3
  135. package/docs/advanced/changelog.mdx +1 -1
  136. package/docs/advanced/custom-pages.mdx +7 -5
  137. package/docs/cli/audit.mdx +19 -3
  138. package/docs/cli/doctor.mdx +2 -2
  139. package/docs/cli/evals.mdx +4 -4
  140. package/docs/cli/index.mdx +4 -1
  141. package/docs/cli/translate.mdx +4 -4
  142. package/docs/cli/version.mdx +1 -1
  143. package/docs/configuration/analytics.mdx +42 -2
  144. package/docs/configuration/assistant.mdx +1 -1
  145. package/docs/configuration/customization.mdx +6 -3
  146. package/docs/configuration/index.mdx +5 -3
  147. package/docs/configuration/search.mdx +2 -2
  148. package/docs/content/components.mdx +2 -2
  149. package/docs/content/frontmatter.mdx +5 -1
  150. package/docs/content/i18n.mdx +3 -1
  151. package/docs/content/index.mdx +8 -4
  152. package/docs/content/islands.mdx +1 -1
  153. package/docs/content/meta.mdx +7 -3
  154. package/docs/content/navigation.mdx +29 -4
  155. package/docs/content/sources.mdx +18 -16
  156. package/docs/content/syntax.mdx +14 -0
  157. package/docs/content/versioning.mdx +2 -1
  158. package/docs/discoverability/agent-discovery.mdx +22 -8
  159. package/docs/discoverability/index.mdx +4 -3
  160. package/docs/discoverability/llms-txt.mdx +2 -5
  161. package/docs/discoverability/markdown.mdx +6 -4
  162. package/docs/discoverability/mcp.mdx +4 -0
  163. package/docs/discoverability/metadata.mdx +3 -2
  164. package/docs/discoverability/open-graph.mdx +8 -4
  165. package/docs/discoverability/rss.mdx +4 -2
  166. package/docs/references/asyncapi.mdx +2 -2
  167. package/docs/references/graphql.mdx +2 -2
  168. package/docs/references/openapi.mdx +6 -4
  169. package/package.json +1 -1
  170. package/skills/blume-migrate/SKILL.md +6 -6
  171. package/skills/blume-migrate/references/docusaurus.md +6 -6
  172. package/skills/blume-migrate/references/fumadocs.md +1 -1
  173. package/skills/blume-migrate/references/mintlify.md +3 -3
  174. package/skills/blume-migrate/references/monorepo.md +1 -1
  175. package/skills/blume-migrate/references/nextra.md +2 -2
  176. package/skills/blume-migrate/references/starlight.md +5 -5
  177. package/skills/blume-migrate/scripts/mintlify-codemod.mjs +9 -4
  178. package/skills/blume-update-docs/references/audit-checklist.md +1 -1
  179. package/src/ai/agent-readability.ts +2 -2
  180. package/src/ai/agent-surface.ts +56 -0
  181. package/src/ai/ai-catalog.ts +6 -2
  182. package/src/ai/api/handlers.ts +2 -2
  183. package/src/ai/api/spec.ts +17 -4
  184. package/src/ai/api-catalog.ts +13 -3
  185. package/src/ai/ask-context.ts +14 -2
  186. package/src/ai/ask-data.ts +26 -12
  187. package/src/ai/changelog-markdown.ts +2 -2
  188. package/src/ai/component-markdown.ts +43 -37
  189. package/src/ai/link-headers.ts +22 -8
  190. package/src/ai/llms.ts +22 -10
  191. package/src/ai/markdown.ts +23 -11
  192. package/src/ai/mcp/discovery.ts +2 -2
  193. package/src/ai/mcp/query.ts +2 -2
  194. package/src/ai/mcp/server.ts +80 -9
  195. package/src/ai/openapi-components.ts +22 -6
  196. package/src/ai/relative-links.ts +99 -15
  197. package/src/ai/serializers.ts +2 -1
  198. package/src/ai/skills.ts +14 -1
  199. package/src/ai/static-expression.ts +416 -0
  200. package/src/ai/tar.ts +139 -12
  201. package/src/ai/visibility.ts +45 -14
  202. package/src/analytics/databuddy.ts +67 -0
  203. package/src/analytics/head.ts +8 -0
  204. package/src/analytics/index.ts +7 -0
  205. package/src/analytics/one-dollar-stats.ts +86 -0
  206. package/src/analytics/posthog.ts +21 -3
  207. package/src/analytics/schema.ts +4 -0
  208. package/src/astro/generate.ts +72 -28
  209. package/src/astro/include-hmr.ts +31 -12
  210. package/src/astro/include-refresh.ts +19 -8
  211. package/src/astro/integration.ts +108 -14
  212. package/src/astro/runtime-modules.ts +28 -13
  213. package/src/astro/templates.ts +139 -57
  214. package/src/audit/catalog.ts +2 -2
  215. package/src/audit/checks/assets.ts +25 -5
  216. package/src/audit/checks/content.ts +20 -2
  217. package/src/audit/checks/i18n.ts +46 -8
  218. package/src/audit/checks/indexability.ts +39 -19
  219. package/src/audit/checks/links.ts +22 -1
  220. package/src/audit/checks/llms.ts +6 -3
  221. package/src/audit/checks/network.ts +3 -1
  222. package/src/audit/checks/og-image.ts +10 -0
  223. package/src/audit/checks/robots.ts +6 -1
  224. package/src/audit/checks/sitemap.ts +56 -31
  225. package/src/audit/checks/social.ts +21 -2
  226. package/src/audit/crawl.ts +88 -15
  227. package/src/audit/graph.ts +3 -1
  228. package/src/audit/report.ts +54 -17
  229. package/src/audit/run.ts +13 -9
  230. package/src/audit/snapshot.ts +5 -0
  231. package/src/audit/types.ts +18 -0
  232. package/src/audit/url.ts +37 -6
  233. package/src/cli/args.ts +32 -0
  234. package/src/cli/build-failure.ts +50 -0
  235. package/src/cli/commands/audit.ts +11 -1
  236. package/src/cli/commands/build.ts +19 -5
  237. package/src/cli/commands/check.ts +2 -0
  238. package/src/cli/commands/doctor.ts +3 -1
  239. package/src/cli/commands/eval.ts +20 -18
  240. package/src/cli/commands/init.ts +15 -5
  241. package/src/cli/commands/preview.ts +15 -0
  242. package/src/cli/commands/sync.ts +2 -0
  243. package/src/cli/commands/translate.ts +18 -18
  244. package/src/cli/commands/upgrade.ts +11 -0
  245. package/src/cli/commands/validate.ts +3 -1
  246. package/src/cli/dev-lock.ts +157 -37
  247. package/src/cli/eject-scripts.ts +32 -7
  248. package/src/cli/env.ts +12 -1
  249. package/src/cli/init/scaffold.ts +117 -11
  250. package/src/cli/init/starter-spec.ts +235 -0
  251. package/src/cli/report-format.ts +11 -6
  252. package/src/components/colors.ts +19 -0
  253. package/src/components/content/AccordionItem.astro +26 -22
  254. package/src/components/content/Badge.astro +7 -12
  255. package/src/components/content/Card.astro +2 -2
  256. package/src/components/content/Component.astro +11 -5
  257. package/src/components/content/Expandable.astro +5 -1
  258. package/src/components/content/Frame.astro +2 -7
  259. package/src/components/content/GithubInfo.astro +12 -2
  260. package/src/components/content/Prompt.astro +2 -7
  261. package/src/components/content/Tab.astro +0 -1
  262. package/src/components/content/Tabs.astro +58 -5
  263. package/src/components/content/Tile.astro +1 -1
  264. package/src/components/content/Tooltip.astro +69 -7
  265. package/src/components/content/Tree.astro +7 -2
  266. package/src/components/content/TypeTable.astro +10 -5
  267. package/src/components/content/Update.astro +8 -2
  268. package/src/components/content/auto-type-table.ts +4 -1
  269. package/src/components/content/badge-color.ts +20 -0
  270. package/src/components/content/base-href.ts +14 -22
  271. package/src/components/content/inline-markdown.ts +27 -7
  272. package/src/components/copy-feedback.ts +35 -8
  273. package/src/components/islands/assistant.tsx +37 -6
  274. package/src/components/islands/base-path.ts +47 -10
  275. package/src/components/islands/hooks.ts +42 -21
  276. package/src/components/islands/webmcp.ts +16 -10
  277. package/src/components/layout/Banner.astro +23 -4
  278. package/src/components/layout/Breadcrumbs.astro +5 -2
  279. package/src/components/layout/DiscoveryLinks.astro +9 -5
  280. package/src/components/layout/Header.astro +23 -10
  281. package/src/components/layout/LanguageSwitcher.astro +2 -2
  282. package/src/components/layout/Logo.astro +5 -0
  283. package/src/components/layout/NavSelector.astro +8 -3
  284. package/src/components/layout/NavTabMenu.astro +133 -0
  285. package/src/components/layout/NavTree.astro +31 -12
  286. package/src/components/layout/NavTreeCache.astro +5 -2
  287. package/src/components/layout/NavTreeScript.astro +45 -5
  288. package/src/components/layout/PageActions.astro +4 -3
  289. package/src/components/layout/PageLayout.astro +28 -13
  290. package/src/components/layout/Pagination.astro +3 -3
  291. package/src/components/layout/ReferenceLayout.astro +4 -1
  292. package/src/components/layout/RootLayout.astro +25 -12
  293. package/src/components/layout/Search.astro +38 -14
  294. package/src/components/layout/VersionBanner.astro +2 -2
  295. package/src/components/layout/analytics-client.ts +14 -0
  296. package/src/components/layout/toc-active.ts +41 -0
  297. package/src/components/layout/toc-element.ts +8 -14
  298. package/src/components/openapi/ApiTagOperations.astro +2 -2
  299. package/src/components/openapi/AsyncApiOperation.astro +7 -4
  300. package/src/components/openapi/GraphqlChip.astro +2 -2
  301. package/src/components/openapi/GraphqlType.astro +11 -3
  302. package/src/components/openapi/MessageComposer.astro +1 -1
  303. package/src/components/openapi/Operation.astro +23 -5
  304. package/src/components/openapi/PanelTabs.astro +4 -1
  305. package/src/components/openapi/Playground.astro +8 -2
  306. package/src/components/openapi/RequestPanel.astro +13 -6
  307. package/src/components/openapi/SchemaProperty.astro +11 -33
  308. package/src/components/openapi/SchemaTable.astro +34 -52
  309. package/src/components/openapi/async.ts +38 -6
  310. package/src/components/openapi/helpers.ts +100 -12
  311. package/src/components/openapi/message-composer.ts +14 -1
  312. package/src/components/openapi/message-model.ts +12 -2
  313. package/src/components/openapi/message.ts +21 -3
  314. package/src/components/openapi/operation-model.ts +119 -17
  315. package/src/components/openapi/panel.ts +31 -5
  316. package/src/components/openapi/param-style.ts +181 -0
  317. package/src/components/openapi/playground-client.ts +110 -23
  318. package/src/components/openapi/playground-schema.ts +25 -6
  319. package/src/components/openapi/request.ts +192 -26
  320. package/src/components/openapi/schema-tree.ts +209 -0
  321. package/src/components/openapi/snippets.ts +113 -19
  322. package/src/components/openapi/validate-json.ts +1 -1
  323. package/src/components/openapi/ws-client.ts +18 -2
  324. package/src/core/base-path.ts +69 -8
  325. package/src/core/config-input.ts +13 -11
  326. package/src/core/config.ts +18 -4
  327. package/src/core/diagnostics.ts +219 -30
  328. package/src/core/directive-diagnostics.ts +99 -0
  329. package/src/core/frontmatter.ts +21 -18
  330. package/src/core/graph.ts +120 -11
  331. package/src/core/heading-markers.ts +5 -18
  332. package/src/core/i18n-ui.ts +39 -2
  333. package/src/core/i18n.ts +44 -5
  334. package/src/core/last-modified.ts +25 -3
  335. package/src/core/links.ts +7 -4
  336. package/src/core/load-module.ts +20 -0
  337. package/src/core/locale-links.ts +22 -18
  338. package/src/core/manifest.ts +3 -2
  339. package/src/core/meta.ts +89 -12
  340. package/src/core/nav-diagnostics.ts +56 -1
  341. package/src/core/navigation.ts +272 -83
  342. package/src/core/ordering-prefix.ts +27 -0
  343. package/src/core/project-graph.ts +50 -6
  344. package/src/core/safe-href.ts +53 -1
  345. package/src/core/safe-links.ts +11 -2
  346. package/src/core/schema.ts +94 -18
  347. package/src/core/sources/assets.ts +83 -41
  348. package/src/core/sources/contentful-rich-text.ts +28 -20
  349. package/src/core/sources/contentful.ts +25 -12
  350. package/src/core/sources/filesystem.ts +25 -3
  351. package/src/core/sources/github-releases.ts +131 -11
  352. package/src/core/sources/lexical.ts +30 -20
  353. package/src/core/sources/lower.ts +276 -56
  354. package/src/core/sources/mdx-remote.ts +52 -16
  355. package/src/core/sources/normalize.ts +456 -119
  356. package/src/core/sources/notion.ts +77 -34
  357. package/src/core/sources/obsidian.ts +40 -9
  358. package/src/core/sources/payload.ts +1 -0
  359. package/src/core/sources/portable-text.ts +85 -49
  360. package/src/core/sources/remote.ts +18 -2
  361. package/src/core/sources/resolve.ts +52 -33
  362. package/src/core/sources/sanity.ts +1 -0
  363. package/src/core/sources/strapi-blocks.ts +28 -19
  364. package/src/core/sources/strapi.ts +1 -0
  365. package/src/core/sources/types.ts +31 -0
  366. package/src/core/sources/watch.ts +20 -7
  367. package/src/core/standard-schema.ts +10 -6
  368. package/src/core/types.ts +9 -0
  369. package/src/core/ui-packs/ar.ts +13 -0
  370. package/src/core/ui-packs/bg.ts +13 -0
  371. package/src/core/ui-packs/bn.ts +13 -0
  372. package/src/core/ui-packs/ca.ts +13 -0
  373. package/src/core/ui-packs/cs.ts +13 -0
  374. package/src/core/ui-packs/da.ts +13 -0
  375. package/src/core/ui-packs/de.ts +13 -0
  376. package/src/core/ui-packs/el.ts +13 -0
  377. package/src/core/ui-packs/es.ts +13 -0
  378. package/src/core/ui-packs/fa.ts +13 -0
  379. package/src/core/ui-packs/fi.ts +13 -0
  380. package/src/core/ui-packs/fr.ts +13 -0
  381. package/src/core/ui-packs/he.ts +13 -0
  382. package/src/core/ui-packs/hi.ts +13 -0
  383. package/src/core/ui-packs/hr.ts +13 -0
  384. package/src/core/ui-packs/hu.ts +13 -0
  385. package/src/core/ui-packs/id.ts +13 -0
  386. package/src/core/ui-packs/it.ts +13 -0
  387. package/src/core/ui-packs/ja.ts +13 -0
  388. package/src/core/ui-packs/ko.ts +13 -0
  389. package/src/core/ui-packs/nl.ts +13 -0
  390. package/src/core/ui-packs/no.ts +13 -0
  391. package/src/core/ui-packs/pl.ts +13 -0
  392. package/src/core/ui-packs/pt-br.ts +13 -0
  393. package/src/core/ui-packs/pt.ts +13 -0
  394. package/src/core/ui-packs/ro.ts +13 -0
  395. package/src/core/ui-packs/ru.ts +13 -0
  396. package/src/core/ui-packs/sk.ts +13 -0
  397. package/src/core/ui-packs/sr.ts +13 -0
  398. package/src/core/ui-packs/sv.ts +13 -0
  399. package/src/core/ui-packs/th.ts +13 -0
  400. package/src/core/ui-packs/tr.ts +13 -0
  401. package/src/core/ui-packs/uk.ts +13 -0
  402. package/src/core/ui-packs/vi.ts +13 -0
  403. package/src/core/ui-packs/zh-tw.ts +13 -0
  404. package/src/core/ui-packs/zh.ts +13 -0
  405. package/src/core/version-cut.ts +69 -17
  406. package/src/core/versions.ts +4 -1
  407. package/src/deploy/adapters/node.ts +5 -2
  408. package/src/deploy/adapters/registry.ts +2 -1
  409. package/src/deploy/adapters/types.ts +13 -1
  410. package/src/deploy/artifacts.ts +52 -11
  411. package/src/deploy/cloudflare-negotiation.ts +27 -16
  412. package/src/deploy/headers.ts +75 -55
  413. package/src/deploy/node-headers.ts +148 -27
  414. package/src/deploy/platforms/cloudflare.ts +13 -3
  415. package/src/deploy/platforms/netlify.ts +83 -5
  416. package/src/deploy/platforms/node.ts +8 -5
  417. package/src/deploy/platforms/static.ts +2 -0
  418. package/src/deploy/platforms/types.ts +15 -0
  419. package/src/deploy/platforms/vercel.ts +10 -3
  420. package/src/deploy/redirects.ts +68 -21
  421. package/src/deploy/robots.ts +2 -2
  422. package/src/deploy/rss.ts +4 -3
  423. package/src/deploy/sitemap.ts +7 -5
  424. package/src/deploy/vercel-negotiation.ts +35 -3
  425. package/src/eval/agents.ts +10 -2
  426. package/src/eval/run.ts +25 -0
  427. package/src/markdown/base-links.ts +74 -29
  428. package/src/markdown/directives.ts +242 -36
  429. package/src/markdown/features.ts +17 -0
  430. package/src/markdown/index.ts +13 -9
  431. package/src/markdown/mdast.ts +5 -2
  432. package/src/markdown/relative-links.ts +15 -27
  433. package/src/markdown/route-snapshot.ts +37 -0
  434. package/src/og/card.ts +149 -9
  435. package/src/og/derive.ts +156 -5
  436. package/src/og/index.ts +1 -0
  437. package/src/openapi/asyncapi.ts +4 -2
  438. package/src/openapi/graphql-build.ts +28 -2
  439. package/src/openapi/model.ts +99 -27
  440. package/src/openapi/proxy.ts +63 -10
  441. package/src/openapi/render-mdx.ts +51 -3
  442. package/src/registry/eject.ts +297 -43
  443. package/src/search/adapters/version-scope.ts +30 -0
  444. package/src/search/documents.ts +89 -27
  445. package/src/search/popular.ts +2 -1
  446. package/src/search/sync/algolia.ts +36 -2
  447. package/src/seo/jsonld.ts +7 -3
  448. package/src/sources/registry.ts +5 -0
  449. package/src/theme/entry.ts +11 -4
  450. package/src/translate/agents.ts +6 -1
  451. package/src/translate/ledger.ts +26 -3
  452. package/src/translate/meta.ts +76 -24
  453. package/src/translate/run.ts +14 -8
  454. package/src/translate/validate.ts +14 -2
  455. package/src/translate/work-list.ts +91 -19
  456. package/src/upgrade/upgrade.ts +36 -4
  457. package/dist/cli/chunk-27g6wdth.js.map +0 -10
  458. package/dist/cli/chunk-2hn4b8z7.js.map +0 -12
  459. package/dist/cli/chunk-6crbhc3x.js.map +0 -14
  460. package/dist/cli/chunk-6hsn950k.js.map +0 -10
  461. package/dist/cli/chunk-79jhk4py.js.map +0 -35
  462. package/dist/cli/chunk-82bbrxdn.js +0 -51
  463. package/dist/cli/chunk-82bbrxdn.js.map +0 -10
  464. package/dist/cli/chunk-abh8yjkn.js +0 -31
  465. package/dist/cli/chunk-abh8yjkn.js.map +0 -10
  466. package/dist/cli/chunk-ce574jw2.js +0 -23
  467. package/dist/cli/chunk-ce574jw2.js.map +0 -10
  468. package/dist/cli/chunk-dh8cwk36.js.map +0 -10
  469. package/dist/cli/chunk-epjnccmv.js.map +0 -10
  470. package/dist/cli/chunk-fa25z98p.js.map +0 -11
  471. package/dist/cli/chunk-fs23ddbb.js.map +0 -35
  472. package/dist/cli/chunk-fz5wtpmh.js.map +0 -10
  473. package/dist/cli/chunk-jwyddg7y.js.map +0 -15
  474. package/dist/cli/chunk-kpf8rrjc.js.map +0 -19
  475. package/dist/cli/chunk-m3vmjgmq.js.map +0 -10
  476. package/dist/cli/chunk-mb2919y2.js.map +0 -10
  477. package/dist/cli/chunk-q5163e60.js.map +0 -11
  478. package/dist/cli/chunk-qkqwkpte.js.map +0 -182
  479. package/dist/cli/chunk-qs4q5p4e.js.map +0 -10
  480. package/dist/cli/chunk-s1p84fyh.js.map +0 -10
  481. package/dist/cli/chunk-wgm7m9qk.js.map +0 -36
  482. package/dist/cli/chunk-yt5n7ppj.js.map +0 -10
  483. package/dist/cli/chunk-yw7dm696.js.map +0 -14
  484. /package/dist/cli/{chunk-s6jhgk0q.js.map → chunk-2eytanqx.js.map} +0 -0
  485. /package/dist/cli/{chunk-hdpx1tax.js.map → chunk-2z928egk.js.map} +0 -0
  486. /package/dist/cli/{chunk-ah61y8py.js.map → chunk-364znk6q.js.map} +0 -0
  487. /package/dist/cli/{chunk-vtk4a6dg.js.map → chunk-8ktnccpt.js.map} +0 -0
  488. /package/dist/cli/{chunk-fxypxtvm.js.map → chunk-a58773jm.js.map} +0 -0
  489. /package/dist/cli/{chunk-wm7js3j9.js.map → chunk-b07cmahc.js.map} +0 -0
  490. /package/dist/cli/{chunk-zxcczpyx.js.map → chunk-r20tn01b.js.map} +0 -0
  491. /package/dist/cli/{chunk-5shv93fd.js.map → chunk-tkacnehg.js.map} +0 -0
  492. /package/dist/cli/{chunk-zxh4d9vy.js.map → chunk-tzmab476.js.map} +0 -0
@@ -14,14 +14,17 @@ import { BLUME_IGNORE_DIRS } from "../core/sources/watch.ts";
14
14
  import { trimChar } from "../core/trim.ts";
15
15
  import type { ProjectContext } from "../core/types.ts";
16
16
  import { deployPassthrough } from "../deploy/adapters/types.ts";
17
- import { SVG_ASSET_POLICY } from "../deploy/headers.ts";
17
+ import { SVG_ASSET_HEADERS } from "../deploy/headers.ts";
18
18
  import { deployPlatform } from "../deploy/platforms/index.ts";
19
19
  import { adapterRoot, distDir } from "../deploy/platforms/paths.ts";
20
20
  import { applyBaseToAstroRedirects } from "../deploy/redirects.ts";
21
21
  import type { OgCache } from "../og/cache.ts";
22
- import type { OgFont, OgFontFamilies } from "../og/card.ts";
22
+ import type { OgFont, OgFontFamilies, OgGoogleFont } from "../og/card.ts";
23
23
  import type { MixedbreadOptions } from "../search/adapters/mixedbread.ts";
24
- import type { ResolvedSearchAdapter } from "../search/adapters/registry.ts";
24
+ import type {
25
+ ResolvedSearchAdapter,
26
+ SearchAdapterKind,
27
+ } from "../search/adapters/registry.ts";
25
28
  import { buildFontEntries, fontLocaleCodes } from "../theme/fonts.ts";
26
29
  import { importSpecifier, wrapperPropsType } from "./component-slots.ts";
27
30
  import type { ExampleSpec } from "./examples.ts";
@@ -398,14 +401,32 @@ export interface ClientFeatures {
398
401
  /** Every feature on: what a checkout that predates the detection shipped. */
399
402
  const ALL_CLIENT_FEATURES: ClientFeatures = { epub: true, mermaid: true };
400
403
 
404
+ /**
405
+ * The npm module each browser-queried search adapter's client imports (see
406
+ * `components/layout/search/`). The `<Search>` dialog lazy-imports that client
407
+ * on first open, after the dev dep optimizer's startup run, so the first
408
+ * search of a fresh `blume dev` session discovered the library, re-ran the
409
+ * optimizer, and reloaded the page. Pagefind loads its script from the build
410
+ * output and Mixedbread queries through the server, so neither has one.
411
+ */
412
+ const SEARCH_CLIENT_DEPS: Partial<Record<SearchAdapterKind, string>> = {
413
+ algolia: "algoliasearch/lite",
414
+ flexsearch: "flexsearch",
415
+ orama: "@orama/orama",
416
+ "orama-cloud": "@oramacloud/client",
417
+ typesense: "typesense",
418
+ };
419
+
401
420
  const resolveOptimizeDeps = (options: {
402
421
  aliases: Record<string, string> | undefined;
403
422
  context: ProjectContext;
404
423
  features: ClientFeatures;
405
424
  needsReact: boolean;
406
425
  reactCompilerPath: string | null | undefined;
426
+ searchKind: SearchAdapterKind;
407
427
  }): OptimizeDepsConfig => {
408
428
  const { context, features } = options;
429
+ const searchDep = SEARCH_CLIENT_DEPS[options.searchKind];
409
430
  const optimizeDepsEntries = [
410
431
  ...(context.pagesRoot ? [`${context.pagesRoot}/**/*.astro`] : []),
411
432
  `${context.root}/islands/**/*.{jsx,svelte,tsx,vue}`,
@@ -418,6 +439,8 @@ const resolveOptimizeDeps = (options: {
418
439
  // Mermaid costs the dev server seconds at startup for nothing otherwise.
419
440
  ...(features.mermaid ? ["blume > mermaid"] : []),
420
441
  ...(features.epub ? ["blume > epub-gen-memory/bundle"] : []),
442
+ // Only the configured search adapter's client library.
443
+ ...(searchDep ? [`blume > ${searchDep}`] : []),
421
444
  // Astro's own client-router/prefetch virtual modules are deliberately NOT
422
445
  // forced in here: they read Vite `define`-injected constants
423
446
  // (__PREFETCH_PREFETCH_ALL__ and friends) that a pre-bundled copy loses,
@@ -538,7 +561,7 @@ const blumeIntegrationOptions = (options: {
538
561
  buildArtifactsRoot: ".",
539
562
  contentRoutes: options.contentRoutes,
540
563
  homeLinkHeader:
541
- buildHomeLinkHeader(options.config, options.contentRoutes) ??
564
+ buildHomeLinkHeader(options.config, options.contentRoutes, "dev") ??
542
565
  undefined,
543
566
  pages: options.pages,
544
567
  }
@@ -623,6 +646,7 @@ export const astroConfigTemplate = (options: {
623
646
  features,
624
647
  needsReact,
625
648
  reactCompilerPath: options.reactCompilerPath,
649
+ searchKind: config.search.provider.kind,
626
650
  });
627
651
 
628
652
  const {
@@ -660,7 +684,8 @@ export const astroConfigTemplate = (options: {
660
684
  const basedRedirects = applyBaseToAstroRedirects(
661
685
  config.redirects,
662
686
  config.basePath,
663
- deployment.options.base ?? ""
687
+ deployment.options.base ?? "",
688
+ new Set(contentRoutes)
664
689
  );
665
690
  const redirectsOption =
666
691
  basedRedirects.length > 0
@@ -892,6 +917,10 @@ ${userConfigSetup}export default defineConfig({
892
917
  // \`/bundle\` subpath that is actually imported: optimizing the package
893
918
  // root leaves that entry out. Production (Rollup) already handles the
894
919
  // interop, so all of this only affects dev.
920
+ //
921
+ // The search adapter's client library rides the list for the same reason
922
+ // as the compiler runtime: the search dialog lazy-imports it on first
923
+ // open, and discovering it then re-optimizes and reloads the page.
895
924
  optimizeDeps: {
896
925
  entries: ${JSON.stringify(optimizeDepsEntries)},
897
926
  include: ${JSON.stringify(optimizeDepsInclude)},
@@ -1246,9 +1275,15 @@ export const askEndpointTemplate = (
1246
1275
  "messages",
1247
1276
  ...backend.template.fields,
1248
1277
  ];
1278
+ // The request's signal aborts when the reader closes the panel mid-answer
1279
+ // (the client drops the connection): passing it on stops the model call,
1280
+ // which otherwise kept generating, and billing, to completion into a closed
1281
+ // connection. `streamText` ends an aborted call through its abort path, not
1282
+ // `onError`, so the reader leaving is never logged as a provider error.
1249
1283
  const call = ` const result = streamText({
1250
1284
  ${streamFields.join(",\n ")},
1251
1285
  ${onError}
1286
+ abortSignal: request.signal,
1252
1287
  });`;
1253
1288
  const stream = grounded
1254
1289
  ? ` const instructions =
@@ -1377,7 +1412,10 @@ export const createSearch = () => create(${JSON.stringify(provider.options)});
1377
1412
  * site uses the feature and `null` otherwise, so an unused library is absent
1378
1413
  * from the module graph (never bundled, never pre-bundled in dev) rather than
1379
1414
  * merely lazy. Regenerated on every pass: a page that gains a mermaid fence
1380
- * turns the loader on.
1415
+ * turns the loader on. The EPUB module is named — even in a type — only when
1416
+ * `export.epub` is on: an ejected app depends on `epub-gen-memory` only then,
1417
+ * and a type import of a package it can't resolve fails its type check under
1418
+ * a strict linker such as pnpm.
1381
1419
  */
1382
1420
  export const featuresTemplate = (features: ClientFeatures): string =>
1383
1421
  `// Generated by Blume. Do not edit.
@@ -1394,9 +1432,13 @@ export const loadMermaid: (() => Promise<unknown>) | null = ${
1394
1432
  };
1395
1433
 
1396
1434
  /** The EPUB generator's browser bundle; null when export.epub is off. */
1397
- export const loadEpub:
1435
+ ${
1436
+ features.epub
1437
+ ? `export const loadEpub:
1398
1438
  | (() => Promise<typeof import("epub-gen-memory/bundle")>)
1399
- | null = ${features.epub ? '() => import("epub-gen-memory/bundle")' : "null"};
1439
+ | null = () => import("epub-gen-memory/bundle");`
1440
+ : "export const loadEpub: (() => Promise<unknown>) | null = null;"
1441
+ }
1400
1442
  `;
1401
1443
 
1402
1444
  /**
@@ -1561,12 +1603,13 @@ export function GET({ props }: { props: { route: string } }) {
1561
1603
  * materializes under `.blume/public/blume-assets` (see
1562
1604
  * `core/sources/assets.ts`). That directory is NOT Astro's `publicDir`
1563
1605
  * (which points at the user project's `public/`), so without this endpoint
1564
- * those references 404.
1606
+ * those references 404. `null` for an ejected app, which copies those
1607
+ * files into its own `public/blume-assets` instead.
1565
1608
  * Prerendered: every asset becomes a static file in the build output; the dev
1566
1609
  * server renders on demand, so new images appear without a restart.
1567
1610
  */
1568
1611
  export const contentAssetsEndpointTemplate = (
1569
- stagedAssetsDir: string
1612
+ stagedAssetsDir: string | null
1570
1613
  ): string =>
1571
1614
  `// Generated by Blume. Do not edit.
1572
1615
  import { existsSync } from "node:fs";
@@ -1588,18 +1631,29 @@ const TYPES: Record<string, string> = {
1588
1631
  ".ico": "image/x-icon",
1589
1632
  ".jpeg": "image/jpeg",
1590
1633
  ".jpg": "image/jpeg",
1634
+ ".mov": "video/quicktime",
1635
+ ".mp4": "video/mp4",
1636
+ ".ogv": "video/ogg",
1591
1637
  ".png": "image/png",
1592
1638
  ".svg": "image/svg+xml",
1593
1639
  ".tiff": "image/tiff",
1640
+ ".webm": "video/webm",
1594
1641
  ".webp": "image/webp",
1595
1642
  };
1596
1643
 
1644
+ const extensionOf = (path: string): string =>
1645
+ path.slice(path.lastIndexOf(".")).toLowerCase();
1646
+
1597
1647
  const contentType = (path: string): string =>
1598
- TYPES[path.slice(path.lastIndexOf(".")).toLowerCase()] ??
1599
- "application/octet-stream";
1648
+ TYPES[extensionOf(path)] ?? "application/octet-stream";
1649
+
1650
+ // A source downloads only images and videos; anything else in the staged
1651
+ // directory (a page an older build saved under its URL's extension) is never
1652
+ // published from the docs origin.
1653
+ const isMedia = (path: string): boolean => Object.hasOwn(TYPES, extensionOf(path));
1600
1654
 
1601
1655
  const stagedParams = async (): Promise<string[]> => {
1602
- if (!existsSync(STAGED_DIR)) {
1656
+ if (STAGED_DIR === null || !existsSync(STAGED_DIR)) {
1603
1657
  return [];
1604
1658
  }
1605
1659
  const entries = await readdir(STAGED_DIR, {
@@ -1607,7 +1661,7 @@ const stagedParams = async (): Promise<string[]> => {
1607
1661
  withFileTypes: true,
1608
1662
  });
1609
1663
  return entries
1610
- .filter((entry) => entry.isFile())
1664
+ .filter((entry) => entry.isFile() && isMedia(entry.name))
1611
1665
  .map((entry) =>
1612
1666
  relative(STAGED_DIR, join(entry.parentPath, entry.name)).replaceAll(
1613
1667
  "\\\\",
@@ -1628,6 +1682,9 @@ const resolveAsset = (asset: string): string | null => {
1628
1682
  if (asset.startsWith("content/")) {
1629
1683
  return files[asset.slice("content/".length)] ?? null;
1630
1684
  }
1685
+ if (STAGED_DIR === null) {
1686
+ return null;
1687
+ }
1631
1688
  const abs = resolve(STAGED_DIR, asset);
1632
1689
  // Traversal guard: the dev server renders on demand, so the param is
1633
1690
  // attacker-controlled there — never step outside the staged directory.
@@ -1637,7 +1694,7 @@ const resolveAsset = (asset: string): string | null => {
1637
1694
  // asset — and a bare prefix also admits a sibling directory that merely
1638
1695
  // shares the name.
1639
1696
  const rel = relative(STAGED_DIR, abs);
1640
- if (rel === "" || rel.startsWith("..") || isAbsolute(rel)) {
1697
+ if (rel === "" || rel.startsWith("..") || isAbsolute(rel) || !isMedia(rel)) {
1641
1698
  return null;
1642
1699
  }
1643
1700
  return existsSync(abs) ? abs : null;
@@ -1651,11 +1708,12 @@ export const GET: APIRoute = async ({ params }) => {
1651
1708
  const body = await readFile(path);
1652
1709
  const type = contentType(path);
1653
1710
  // An SVG opened directly is a document that can run script; the sandbox
1654
- // keeps an uploaded one inert on the docs origin. Static hosts get the same
1655
- // header from the build's header rules.
1711
+ // keeps an uploaded one inert on the docs origin. This endpoint
1712
+ // prerenders, so these headers reach dev only: a build serves the files
1713
+ // with the same headers from its host's rules (see deploy/headers.ts).
1656
1714
  const headers: Record<string, string> =
1657
1715
  type === "image/svg+xml"
1658
- ? { "Content-Security-Policy": ${JSON.stringify(SVG_ASSET_POLICY)}, "Content-Type": type }
1716
+ ? { ...${JSON.stringify(SVG_ASSET_HEADERS)}, "Content-Type": type }
1659
1717
  : { "Content-Type": type };
1660
1718
  return new Response(new Uint8Array(body), { headers });
1661
1719
  };
@@ -1769,7 +1827,7 @@ export const navFragmentTemplate = (): string =>
1769
1827
  `---
1770
1828
  // Generated by Blume. Do not edit.
1771
1829
  import NavTree from "blume/components/layout/NavTree.astro";
1772
- import { withBase } from "blume/components/islands/base-path.ts";
1830
+ import { withMountedBase } from "blume/components/islands/base-path.ts";
1773
1831
  import {
1774
1832
  hiddenDefaultLocale,
1775
1833
  navGroupIds,
@@ -1808,7 +1866,7 @@ if (!(variant && node && node.kind === "group")) {
1808
1866
  }
1809
1867
  const strings =
1810
1868
  locale === "default" ? data.ui.nav : (data.uiByLocale[locale] ?? data.ui).nav;
1811
- const fragmentBase = withBase(\`/blume-nav/\${version}/\${locale}\`);
1869
+ const fragmentBase = withMountedBase(\`/blume-nav/\${version}/\${locale}\`);
1812
1870
  ---
1813
1871
 
1814
1872
  <NavTree
@@ -1832,6 +1890,7 @@ export const ogEndpointTemplate = (
1832
1890
  * Absent, every card renders on every build.
1833
1891
  */
1834
1892
  cache?: OgCache;
1893
+ fallbacks?: OgGoogleFont[];
1835
1894
  families?: OgFontFamilies;
1836
1895
  fonts?: OgFont[];
1837
1896
  /**
@@ -1845,7 +1904,7 @@ export const ogEndpointTemplate = (
1845
1904
  ): string =>
1846
1905
  `// Generated by Blume. Do not edit.
1847
1906
  import { cachedOgImage } from "blume/og";
1848
- import type { OgCache, OgFont, OgFontFamilies } from "blume/og";
1907
+ import type { OgCache, OgFont, OgFontFamilies, OgGoogleFont } from "blume/og";
1849
1908
  import data from "blume:data";
1850
1909
 
1851
1910
  export const prerender = true;
@@ -1863,6 +1922,9 @@ const fonts: OgFont[] = ${JSON.stringify(og.fonts ?? [])};
1863
1922
  const families: OgFontFamilies | undefined = ${
1864
1923
  og.families ? JSON.stringify(og.families) : "undefined"
1865
1924
  };
1925
+ // Script fallbacks (Noto families, the configured locales' first). A card
1926
+ // fetches them only when its text has a glyph Takumi's built-in font lacks.
1927
+ const fallbacks: OgGoogleFont[] = ${JSON.stringify(og.fallbacks ?? [])};
1866
1928
 
1867
1929
  // Rendered cards are kept on disk between builds, keyed by everything that
1868
1930
  // decides their pixels, so a rebuild renders only the cards whose title,
@@ -1935,6 +1997,7 @@ export async function GET({ props }: { props: CardProps }) {
1935
1997
  accent: data.config.og.palette?.accent ?? data.config.theme.accent.light,
1936
1998
  brand: data.config.title,
1937
1999
  description: props.description ?? data.config.og.description,
2000
+ fallbacks,
1938
2001
  families,
1939
2002
  fonts,
1940
2003
  logo: data.config.og.logo,
@@ -2130,8 +2193,8 @@ export const catchAllPageTemplate = (options: {
2130
2193
  import { getEntry, render } from "astro:content";
2131
2194
  import type { CollectionKey } from "astro:content";
2132
2195
  import RootLayout from "blume/components/layout/RootLayout.astro";
2133
- import { withBase } from "blume/components/islands/base-path.ts";
2134
- import { stripBasePath, withBasePath } from "blume/core/base-path.ts";
2196
+ import { withBase, withMountedBase } from "blume/components/islands/base-path.ts";
2197
+ import { mountBasePath, stripBasePath } from "blume/core/base-path.ts";
2135
2198
  import { resolveSlot } from "blume/components/layout/overrides.ts";
2136
2199
  ${componentImports}
2137
2200
  import data from "blume:data";
@@ -2196,12 +2259,16 @@ const base = data.config.site ? data.config.site.replace(/\\/$/, "") : null;
2196
2259
  const ogPath = data.config.og.enabled
2197
2260
  ? encodeURI(\`/og/\${route === "/" ? "index" : route.slice(1)}.png\`)
2198
2261
  : null;
2199
- const ogRel = seo.image ?? ogPath;
2200
2262
  // Absolute URLs also carry the deployment base (the page is served under it):
2201
2263
  // \`site + base + path\`. Only absolutize root-relative paths: \`seo.image\` may be
2202
- // an external URL, which passes through verbatim (mirrors PageLayout).
2203
- const ogImage =
2204
- ogRel && base && ogRel.startsWith("/") ? \`\${base}\${withBase(ogRel)}\` : ogRel;
2264
+ // an external URL, which passes through verbatim (mirrors PageLayout). An
2265
+ // authored \`seo.image\` keeps a base written by hand; the generated card is
2266
+ // always mounted under it.
2267
+ const absoluteOg = (path: string, based: (path: string) => string) =>
2268
+ base && path.startsWith("/") ? \`\${base}\${based(path)}\` : path;
2269
+ const ogImage = seo.image
2270
+ ? absoluteOg(seo.image, withBase)
2271
+ : ogPath && absoluteOg(ogPath, withMountedBase);
2205
2272
  // Blume's generated card has known dimensions the layout can declare; a user's
2206
2273
  // \`seo.image\` could be any size or format, so it gets none.
2207
2274
  const ogGenerated = !seo.image && Boolean(ogPath);
@@ -2210,7 +2277,7 @@ const ogGenerated = !seo.image && Boolean(ogPath);
2210
2277
  // itself (a guest post crediting its own author) over the configured default.
2211
2278
  const x = { ...data.config.x, ...(seo.x?.creator ? { creator: seo.x.creator } : {}) };
2212
2279
 
2213
- const basedRoute = withBase(route);
2280
+ const basedRoute = withMountedBase(route);
2214
2281
 
2215
2282
  // Locale resolution. With i18n on, pick the active locale's nav + dictionary,
2216
2283
  // build hreflang alternates, and derive the language-switcher targets.
@@ -2258,7 +2325,7 @@ const contentDir = i18n
2258
2325
  : "ltr";
2259
2326
  // The root route keeps its trailing slash (\`https://site/\`) so canonical and
2260
2327
  // hreflang URLs byte-match the sitemap's <loc> for the home page.
2261
- const absolute = (path: string) => base + withBase(path);
2328
+ const absolute = (path: string) => base + withMountedBase(path);
2262
2329
 
2263
2330
  // A fallback page renders the fallback locale's page at this locale's URL, so
2264
2331
  // its canonical names the page it copies, and search engines never rank the
@@ -2303,7 +2370,7 @@ const xDefault = defaultAlt && base ? absolute(defaultAlt.path) : null;
2303
2370
  // carries sits *after* the base (\`/docs/ja/guide\`), while \`localePrefix\` is
2304
2371
  // base-less (\`/ja\`). Stripping and re-adding a locale therefore happen in
2305
2372
  // base-less space, with the base re-applied at the end — the same
2306
- // \`withBasePath(basePath, localizeRoute(...))\` composition the manifest uses to
2373
+ // \`mountBasePath(basePath, localizeRoute(...))\` composition the manifest uses to
2307
2374
  // build every real route. Done in based space, \`stripLocale\` matches nothing
2308
2375
  // and \`localizeRoute\` prepends a second prefix (\`/ja/docs/ja/guide\`). Only a
2309
2376
  // switcher entry for a locale with no real translation reaches this fallback:
@@ -2311,7 +2378,7 @@ const xDefault = defaultAlt && base ? absolute(defaultAlt.path) : null;
2311
2378
  // generated reference (which has no \`alternates\` at all) for every locale
2312
2379
  // but its own.
2313
2380
  const mountLocalized = (logical: string, codeArg: string) =>
2314
- withBasePath(data.config.basePath, localizeRoute(logical, codeArg));
2381
+ mountBasePath(data.config.basePath, localizeRoute(logical, codeArg));
2315
2382
  const logicalRoute = i18n
2316
2383
  ? stripLocale(stripBasePath(data.config.basePath, route), locale)
2317
2384
  : route;
@@ -2340,7 +2407,7 @@ const versionRootFor = (id: string) => {
2340
2407
  const logical = id ? \`/\${id}\` : "/";
2341
2408
  return i18n
2342
2409
  ? mountLocalized(logical, locale)
2343
- : withBasePath(data.config.basePath, logical);
2410
+ : mountBasePath(data.config.basePath, logical);
2344
2411
  };
2345
2412
  const samePageSwitch = versionsConfig
2346
2413
  ? versionsConfig.switcher.redirect === "same-page"
@@ -2440,7 +2507,7 @@ const LayoutComponent = resolveSlot(layoutOverrides.Layout, RootLayout);
2440
2507
  exportEpub={${options.exportEpub}}${
2441
2508
  options.navFragments
2442
2509
  ? `
2443
- navFragmentBase={withBase(\`/blume-nav/\${version || "current"}/\${i18n && localePrefix(locale) ? locale : "default"}\`)}`
2510
+ navFragmentBase={withMountedBase(\`/blume-nav/\${version || "current"}/\${i18n && localePrefix(locale) ? locale : "default"}\`)}`
2444
2511
  : ""
2445
2512
  }
2446
2513
  openInChat={data.config.openInChat}
@@ -2493,7 +2560,7 @@ export const changelogIndexTemplate = (options: {
2493
2560
  // Generated by Blume. Do not edit.
2494
2561
  import { getCollection } from "astro:content";
2495
2562
  import RootLayout from "blume/components/layout/RootLayout.astro";
2496
- import { withBase } from "blume/components/islands/base-path.ts";
2563
+ import { withMountedBase } from "blume/components/islands/base-path.ts";
2497
2564
  import { resolveSlot } from "blume/components/layout/overrides.ts";
2498
2565
  import { resolveDateFormatOptions } from "blume/core/date-format.ts";
2499
2566
  import { layoutOverrides } from "../generated/components.ts";
@@ -2592,7 +2659,7 @@ const items = changelogEntries.map((entry) => {
2592
2659
  return {
2593
2660
  date: date ? formatRowDate(date) : null,
2594
2661
  dateTime: date ? date.toISOString().slice(0, 10) : null,
2595
- href: route ? withBase(route) : null,
2662
+ href: route ? withMountedBase(route) : null,
2596
2663
  id: slugify(label),
2597
2664
  label,
2598
2665
  tag: entry.data.changelog?.category ?? null,
@@ -2628,14 +2695,14 @@ for (const item of items) {
2628
2695
 
2629
2696
  const base = data.config.site ? data.config.site.replace(/\\/$/, "") : null;
2630
2697
  // The canonical URL carries the deployment base (the page is served under it),
2631
- // matching how the catch-all canonicalizes via \`withBase(route)\`.
2632
- const basedRoute = withBase("/changelog");
2698
+ // matching how the catch-all canonicalizes via \`withMountedBase(route)\`.
2699
+ const basedRoute = withMountedBase("/changelog");
2633
2700
  const canonical = base ? base + basedRoute : null;
2634
2701
 
2635
2702
  // The generated OG card for this route (the /og endpoint emits it alongside
2636
2703
  // the content-route cards), absolutized like the catch-all's so crawlers get
2637
2704
  // a full URL when the site is known.
2638
- const ogPath = data.config.og.enabled ? withBase("/og/changelog.png") : null;
2705
+ const ogPath = data.config.og.enabled ? withMountedBase("/og/changelog.png") : null;
2639
2706
  const ogImage = ogPath && base ? base + ogPath : ogPath;
2640
2707
 
2641
2708
  // The page chrome (h1, title, description) comes from the translatable
@@ -2757,7 +2824,8 @@ export const notFoundPageTemplate = (): string => `---
2757
2824
  // Generated by Blume. Do not edit. Override by adding \`pages/404.astro\`.
2758
2825
  import Icon from "blume/components/Icon.astro";
2759
2826
  import PageLayout from "blume/components/layout/PageLayout.astro";
2760
- import { withBase } from "blume/components/islands/base-path.ts";
2827
+ import { withMountedBase } from "blume/components/islands/base-path.ts";
2828
+ import { mountBasePath } from "blume/core/base-path.ts";
2761
2829
  import data from "blume:data";
2762
2830
 
2763
2831
  export const prerender = true;
@@ -2781,22 +2849,27 @@ const htmlLang = i18n ? i18n.defaultLocale : "en";
2781
2849
  // their resolved target when the section has no index page of its own.
2782
2850
  const suggestions = [
2783
2851
  ...data.navigation.tabs.map((tab) => ({
2784
- href: withBase(tab.href ?? tab.path),
2852
+ href: withMountedBase(tab.href ?? tab.path),
2785
2853
  label: tab.label,
2786
2854
  })),
2787
2855
  ...(data.config.discovery.sitemap
2788
- ? [{ href: withBase("/sitemap.xml"), label: nf.sitemap }]
2856
+ ? [{ href: withMountedBase("/sitemap.xml"), label: nf.sitemap }]
2789
2857
  : []),
2790
2858
  ...(data.config.discovery.llmsTxt
2791
- ? [{ href: withBase("/llms.txt"), label: nf.llms }]
2859
+ ? [{ href: withMountedBase("/llms.txt"), label: nf.llms }]
2792
2860
  : []),
2793
2861
  ];
2794
2862
 
2795
2863
  // Every host serves this one page for any missing URL, so a reader who
2796
2864
  // mistyped \`/ar/…\` would get the default locale's message. The other locales'
2797
2865
  // copy rides along, and the script below swaps it in when the URL's first
2798
- // segment names one of them — the message, its language and direction, the
2799
- // tab title, and the home link (to that locale's root).
2866
+ // segment past the site's base names one of them — the message, its language
2867
+ // and direction, the tab title, and the home link (to that locale's root).
2868
+ // Localized routes sit under both bases (\`{deployment.base}/{basePath}/ar/…\`),
2869
+ // so the script strips the two together, trailing slash included.
2870
+ const { basePath } = data.config;
2871
+ const siteRoot = withMountedBase(mountBasePath(basePath, "/"));
2872
+ const localeBase = siteRoot.endsWith("/") ? siteRoot : siteRoot + "/";
2800
2873
  const localized = Object.fromEntries(
2801
2874
  (i18n?.locales ?? [])
2802
2875
  .filter((l) => l.code !== i18n?.defaultLocale && data.uiByLocale[l.code])
@@ -2808,7 +2881,7 @@ const localized = Object.fromEntries(
2808
2881
  description: strings.description,
2809
2882
  dir: l.dir ?? "ltr",
2810
2883
  home: strings.home,
2811
- homeHref: withBase("/" + l.code),
2884
+ homeHref: withMountedBase(mountBasePath(basePath, "/" + l.code)),
2812
2885
  suggestions: strings.suggestions,
2813
2886
  title: strings.title,
2814
2887
  },
@@ -2837,7 +2910,7 @@ const localizedJson = JSON.stringify(localized).replaceAll("<", "\\\\u003c");
2837
2910
  >
2838
2911
  <div
2839
2912
  class="mx-auto grid w-full max-w-5xl gap-12 px-6 py-20 sm:py-28 md:grid-cols-[3fr_2fr] md:gap-16 lg:gap-24 lg:py-36"
2840
- data-base={withBase("/")}
2913
+ data-base={localeBase}
2841
2914
  data-blume-not-found
2842
2915
  >
2843
2916
  <div class="flex flex-col items-start">
@@ -2858,7 +2931,7 @@ const localizedJson = JSON.stringify(localized).replaceAll("<", "\\\\u003c");
2858
2931
  <a
2859
2932
  class="mt-8 inline-flex items-center gap-1.5 rounded-full bg-accent py-2 pe-4 ps-3.5 text-sm font-medium text-accent-foreground transition-opacity hover:opacity-90"
2860
2933
  data-nf-home
2861
- href={withBase("/")}
2934
+ href={withMountedBase("/")}
2862
2935
  >
2863
2936
  <Icon class="rtl:-scale-x-100" name="arrow-left" size={14} />
2864
2937
  <span data-nf="home">{nf.home}</span>
@@ -2940,7 +3013,7 @@ const localizedJson = JSON.stringify(localized).replaceAll("<", "\\\\u003c");
2940
3013
  */
2941
3014
  export const notFoundMarkdownTemplate =
2942
3015
  (): string => `// Generated by Blume. Do not edit. Override by adding \`pages/404.astro\`.
2943
- import { withBase } from "blume/components/islands/base-path.ts";
3016
+ import { withMountedBase } from "blume/components/islands/base-path.ts";
2944
3017
  import { absoluteUrl } from "blume/core/site-url.ts";
2945
3018
  import data from "blume:data";
2946
3019
 
@@ -2951,7 +3024,7 @@ const nf = data.ui.notFound;
2951
3024
  // Absolute for internal routes when the site is known; an external tab href
2952
3025
  // passes through untouched.
2953
3026
  const href = (path: string): string => {
2954
- const based = withBase(path);
3027
+ const based = withMountedBase(path);
2955
3028
  return data.config.site && based.startsWith("/") && !based.startsWith("//")
2956
3029
  ? absoluteUrl(data.config.site, based)
2957
3030
  : based;
@@ -3010,7 +3083,7 @@ export function GET() {
3010
3083
  export const notFoundJsonTemplate =
3011
3084
  (): string => `// Generated by Blume. Do not edit. Override by adding \`pages/404.astro\`.
3012
3085
  import { problem } from "blume/ai/api/problem.ts";
3013
- import { withBase } from "blume/components/islands/base-path.ts";
3086
+ import { withMountedBase } from "blume/components/islands/base-path.ts";
3014
3087
  import { absoluteUrl } from "blume/core/site-url.ts";
3015
3088
  import data from "blume:data";
3016
3089
 
@@ -3021,7 +3094,7 @@ const nf = data.ui.notFound;
3021
3094
  // Absolute for internal routes when the site is known; an external tab href
3022
3095
  // passes through untouched.
3023
3096
  const href = (path: string): string => {
3024
- const based = withBase(path);
3097
+ const based = withMountedBase(path);
3025
3098
  return data.config.site && based.startsWith("/") && !based.startsWith("//")
3026
3099
  ? absoluteUrl(data.config.site, based)
3027
3100
  : based;
@@ -3196,7 +3269,7 @@ ${wrapperPropsType("Example")}
3196
3269
  /**
3197
3270
  * The route prefix `<Component />` preview frames are served under:
3198
3271
  * `{basePath}/blume-examples/<example path>`. `deployment.base` is layered on
3199
- * top by Astro (components apply it with `withBase`).
3272
+ * top by Astro (components apply it with `withMountedBase`).
3200
3273
  */
3201
3274
  export const examplesRouteBase = (basePath: string): string =>
3202
3275
  `${basePath}/blume-examples`;
@@ -3300,11 +3373,20 @@ const Example = entry.Component;
3300
3373
  root.dataset.theme = theme;
3301
3374
  root.classList.toggle("dark", theme === "dark");
3302
3375
  };
3303
- const stored = () =>
3304
- localStorage.getItem("blume-theme") ??
3305
- (matchMedia("(prefers-color-scheme: dark)").matches
3306
- ? "dark"
3307
- : "light");
3376
+ // Blocked storage (a sandboxed or privacy-locked frame) throws on
3377
+ // access; it falls through to the OS setting like an unset preference.
3378
+ const stored = () => {
3379
+ let theme = null;
3380
+ try {
3381
+ theme = localStorage.getItem("blume-theme");
3382
+ } catch {}
3383
+ return (
3384
+ theme ??
3385
+ (matchMedia("(prefers-color-scheme: dark)").matches
3386
+ ? "dark"
3387
+ : "light")
3388
+ );
3389
+ };
3308
3390
  try {
3309
3391
  const host = window.parent.document.documentElement;
3310
3392
  apply(host.dataset.theme ?? stored());
@@ -583,7 +583,7 @@ export const CHECKS = [
583
583
  },
584
584
  {
585
585
  category: "sitemap",
586
- fix: "Sitemaps must be valid XML in the sitemaps.org urlset format.",
586
+ fix: "Sitemaps must be valid XML in the sitemaps.org urlset or sitemap index format.",
587
587
  id: "BLUME_AUDIT_SITEMAP_INVALID",
588
588
  severity: "error",
589
589
  tier: "static",
@@ -684,7 +684,7 @@ export const CHECKS = [
684
684
  },
685
685
  {
686
686
  category: "ai",
687
- fix: "Rebuild so llms.txt matches the site; if the page is deliberately excluded, mark it `seo.noindex` or `sidebar.hidden`.",
687
+ fix: "Rebuild so llms.txt matches the site; if the page is deliberately excluded, set `ai.exclude: true` in its front matter.",
688
688
  id: "BLUME_AUDIT_LLMS_TXT_PAGE_MISSING",
689
689
  severity: "warning",
690
690
  tier: "static",
@@ -11,6 +11,13 @@ import type {
11
11
  } from "../types.ts";
12
12
  import { resolveHref, siteOrigin } from "../url.ts";
13
13
 
14
+ /**
15
+ * Vercel's image optimization route (`/_vercel/image?url=…&w=…`), which
16
+ * `imageService` on the Vercel adapter points `<img>` at. The platform
17
+ * serves it; the build never writes a file there.
18
+ */
19
+ const VERCEL_IMAGE_ENDPOINT = "/_vercel/image";
20
+
14
21
  const formatBytes = (bytes: number): string =>
15
22
  bytes < 1024 * 1024
16
23
  ? `${Math.round(bytes / 1024)} kB`
@@ -44,17 +51,30 @@ const resolveAsset = (
44
51
  }
45
52
 
46
53
  const origin = siteOrigin(context.project.config.deployment.options.site);
47
- const resolved = resolveHref(
48
- page.url,
49
- asset.src,
50
- origin,
51
- normalizeBasePath(context.project.config.deployment.options.base)
54
+ const deployBase = normalizeBasePath(
55
+ context.project.config.deployment.options.base
52
56
  );
57
+ const resolved = resolveHref(page.url, asset.src, origin, deployBase);
53
58
  // A subresource on another origin (a CDN, an analytics script) is outside the
54
59
  // build; we can't check whether it exists without the network.
55
60
  if (resolved.kind === "external" || resolved.kind === "ignored") {
56
61
  return null;
57
62
  }
63
+ // Vercel's image optimization endpoint answers these at request time, at the
64
+ // host root whatever the base.
65
+ if (resolved.path === VERCEL_IMAGE_ENDPOINT) {
66
+ return null;
67
+ }
68
+ if (resolved.kind === "outside-base") {
69
+ found.push(
70
+ finding(
71
+ missingId,
72
+ pageSite(context, page),
73
+ `Page references ${asset.src}, which is missing deployment.base (${deployBase}), so the deployed site does not serve it.`
74
+ )
75
+ );
76
+ return null;
77
+ }
58
78
 
59
79
  const bytes = context.files.get(resolved.path);
60
80
  if (bytes === undefined) {
@@ -38,6 +38,22 @@ const frontmatterDate = (source: string): Date | null => {
38
38
  }
39
39
  };
40
40
 
41
+ /**
42
+ * API reference pages (OpenAPI, AsyncAPI, GraphQL) are generated from a spec
43
+ * and have no front matter to edit: their title comes from the operation's
44
+ * summary (the spec's own title on the overview page) and their description
45
+ * from its description or summary. A fix naming front matter would send the
46
+ * reader to a file that doesn't exist.
47
+ */
48
+ const isGeneratedReference = (page: PageSnapshot): boolean =>
49
+ page.route?.source.name === "openapi";
50
+
51
+ const GENERATED_TITLE_FIX =
52
+ "This page is generated from an API spec, which titles it with the operation's `summary` (the spec's own title on the overview page) — rewrite it there.";
53
+
54
+ const GENERATED_DESCRIPTION_FIX =
55
+ "This page is generated from an API spec, which describes it with the operation's `description` or `summary` (the spec's own description on the overview page) — rewrite it there.";
56
+
41
57
  const titleChecks = (
42
58
  context: AuditContext,
43
59
  page: PageSnapshot
@@ -74,7 +90,8 @@ const titleChecks = (
74
90
  finding(
75
91
  "BLUME_AUDIT_TITLE_LENGTH",
76
92
  pageSite(context, page, ["title"]),
77
- `Title renders ${width} columns wide — too ${direction} (aim for ${titleMin}–${titleMax}).`
93
+ `Title renders ${width} columns wide — too ${direction} (aim for ${titleMin}–${titleMax}).`,
94
+ isGeneratedReference(page) ? GENERATED_TITLE_FIX : undefined
78
95
  )
79
96
  );
80
97
  }
@@ -123,7 +140,8 @@ const descriptionChecks = (
123
140
  finding(
124
141
  "BLUME_AUDIT_DESCRIPTION_LENGTH",
125
142
  pageSite(context, page, ["description"]),
126
- `Meta description renders ${width} columns wide — too ${direction} (aim for ${descriptionMin}–${descriptionMax}).`
143
+ `Meta description renders ${width} columns wide — too ${direction} (aim for ${descriptionMin}–${descriptionMax}).`,
144
+ isGeneratedReference(page) ? GENERATED_DESCRIPTION_FIX : undefined
127
145
  )
128
146
  );
129
147
  }