blume 1.7.3 → 2.0.0

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 (673) hide show
  1. package/AGENTS.md +19 -0
  2. package/CHANGELOG.md +217 -0
  3. package/README.md +34 -21
  4. package/dist/cli/chunk-11j0384y.js +148 -0
  5. package/dist/cli/chunk-11j0384y.js.map +10 -0
  6. package/dist/cli/{chunk-rqy0s5wh.js → chunk-1w8dp3qb.js} +17 -16
  7. package/dist/cli/{chunk-rqy0s5wh.js.map → chunk-1w8dp3qb.js.map} +3 -3
  8. package/dist/cli/chunk-2q1dwty4.js +75 -0
  9. package/dist/cli/chunk-2q1dwty4.js.map +11 -0
  10. package/dist/cli/chunk-41za066z.js +122 -0
  11. package/dist/cli/chunk-41za066z.js.map +11 -0
  12. package/dist/cli/chunk-5a2z0198.js +133 -0
  13. package/dist/cli/chunk-5a2z0198.js.map +10 -0
  14. package/dist/cli/{chunk-5g0w1e2c.js → chunk-6k8vp3ta.js} +25 -13
  15. package/dist/cli/{chunk-5g0w1e2c.js.map → chunk-6k8vp3ta.js.map} +4 -4
  16. package/dist/cli/{chunk-5qk08vmp.js → chunk-79njf86q.js} +133 -54
  17. package/dist/cli/chunk-79njf86q.js.map +11 -0
  18. package/dist/cli/{chunk-e7f42gdj.js → chunk-7ez8ny0t.js} +2 -2
  19. package/dist/cli/{chunk-nn13znc2.js → chunk-88cpgt6h.js} +1 -1
  20. package/dist/cli/chunk-a9kptbw5.js +361 -0
  21. package/dist/cli/chunk-a9kptbw5.js.map +14 -0
  22. package/dist/cli/chunk-abh8yjkn.js +31 -0
  23. package/dist/cli/chunk-abh8yjkn.js.map +10 -0
  24. package/dist/cli/chunk-b5aj94ah.js +91 -0
  25. package/dist/cli/chunk-b5aj94ah.js.map +10 -0
  26. package/dist/cli/{chunk-b27xqwn9.js → chunk-bctazmbk.js} +9 -5
  27. package/dist/cli/chunk-bctazmbk.js.map +10 -0
  28. package/dist/cli/chunk-beat36xx.js +279 -0
  29. package/dist/cli/chunk-beat36xx.js.map +10 -0
  30. package/dist/cli/chunk-bnbmcwfb.js +145 -0
  31. package/dist/cli/chunk-bnbmcwfb.js.map +11 -0
  32. package/dist/cli/{chunk-0xjyb285.js → chunk-bw22s759.js} +15 -5
  33. package/dist/cli/{chunk-0xjyb285.js.map → chunk-bw22s759.js.map} +4 -4
  34. package/dist/cli/chunk-by2290sx.js +39 -0
  35. package/dist/cli/chunk-by2290sx.js.map +10 -0
  36. package/dist/cli/chunk-d1tadaw7.js +79 -0
  37. package/dist/cli/chunk-d1tadaw7.js.map +10 -0
  38. package/dist/cli/{chunk-ahnw3kxw.js → chunk-d80hr03s.js} +24 -19
  39. package/dist/cli/chunk-d80hr03s.js.map +15 -0
  40. package/dist/cli/chunk-ernrthtr.js +97 -0
  41. package/dist/cli/chunk-ernrthtr.js.map +10 -0
  42. package/dist/cli/chunk-f2972sbt.js +374 -0
  43. package/dist/cli/chunk-f2972sbt.js.map +10 -0
  44. package/dist/cli/{chunk-vacwm2hv.js → chunk-f7t03s3g.js} +2 -2
  45. package/dist/cli/{chunk-2z47ypj8.js → chunk-fa25z98p.js} +16 -3
  46. package/dist/cli/chunk-fa25z98p.js.map +11 -0
  47. package/dist/cli/{chunk-cjtn640a.js → chunk-fh5hj5jt.js} +43 -20
  48. package/dist/cli/chunk-fh5hj5jt.js.map +10 -0
  49. package/dist/cli/{chunk-yg63d42r.js → chunk-j8mw0za6.js} +86 -57
  50. package/dist/cli/chunk-j8mw0za6.js.map +35 -0
  51. package/dist/cli/{chunk-ct47dqpx.js → chunk-jts8mvcz.js} +67 -7
  52. package/dist/cli/{chunk-2mzebbbz.js.map → chunk-jts8mvcz.js.map} +6 -4
  53. package/dist/cli/{chunk-ps4m1xh4.js → chunk-mnqj32sj.js} +505 -542
  54. package/dist/cli/chunk-mnqj32sj.js.map +12 -0
  55. package/dist/cli/{chunk-8cjtbafj.js → chunk-mwt1k8n7.js} +100 -374
  56. package/dist/cli/chunk-mwt1k8n7.js.map +10 -0
  57. package/dist/cli/chunk-nk3ts2xk.js +51 -0
  58. package/dist/cli/chunk-nk3ts2xk.js.map +10 -0
  59. package/dist/cli/{chunk-3r45185y.js → chunk-pat2zzwc.js} +9 -11
  60. package/dist/cli/{chunk-3r45185y.js.map → chunk-pat2zzwc.js.map} +2 -2
  61. package/dist/cli/chunk-pnnvybbk.js +176 -0
  62. package/dist/cli/chunk-pnnvybbk.js.map +11 -0
  63. package/dist/cli/{chunk-4x36ddpw.js → chunk-sqn5t4q0.js} +81 -87
  64. package/dist/cli/chunk-sqn5t4q0.js.map +10 -0
  65. package/dist/cli/{chunk-bvwwhd84.js → chunk-tzne8qfq.js} +15 -15
  66. package/dist/cli/{chunk-bvwwhd84.js.map → chunk-tzne8qfq.js.map} +1 -1
  67. package/dist/cli/chunk-xaz13gwg.js +12449 -0
  68. package/dist/cli/chunk-xaz13gwg.js.map +182 -0
  69. package/dist/cli/chunk-y3e45rc8.js +102 -0
  70. package/dist/cli/chunk-y3e45rc8.js.map +10 -0
  71. package/dist/cli/chunk-z01ze5c1.js +261 -0
  72. package/dist/cli/chunk-z01ze5c1.js.map +10 -0
  73. package/dist/cli/{chunk-k79xp7av.js → chunk-z1f5arsg.js} +219 -689
  74. package/dist/cli/chunk-z1f5arsg.js.map +36 -0
  75. package/dist/cli/{chunk-1jefwnfs.js → chunk-zg2gtj10.js} +1076 -2592
  76. package/dist/cli/chunk-zg2gtj10.js.map +35 -0
  77. package/dist/cli/{chunk-dwgcp5sm.js → chunk-zxccj738.js} +1 -1
  78. package/dist/cli/index.js +214 -57
  79. package/dist/cli/index.js.map +8 -7
  80. package/dist/types/ai/agent-readability.d.ts +52 -0
  81. package/dist/types/ai/ai-catalog.d.ts +42 -0
  82. package/dist/types/ai/api/paths.d.ts +17 -0
  83. package/dist/types/ai/api-catalog.d.ts +18 -0
  84. package/dist/types/ai/ask.d.ts +368 -0
  85. package/dist/types/ai/changelog-markdown.d.ts +2 -0
  86. package/dist/types/ai/component-markdown.d.ts +2 -2
  87. package/dist/types/ai/index.d.ts +24 -0
  88. package/dist/types/ai/link-headers.d.ts +24 -0
  89. package/dist/types/ai/llms.d.ts +25 -0
  90. package/dist/types/ai/markdown.d.ts +45 -0
  91. package/dist/types/ai/mcp/discovery.d.ts +68 -0
  92. package/dist/types/ai/mcp/tools.d.ts +16 -0
  93. package/dist/types/ai/openapi-components.d.ts +43 -0
  94. package/dist/types/ai/relative-links.d.ts +26 -0
  95. package/dist/types/ai/serializers.d.ts +15 -0
  96. package/dist/types/ai/skills.d.ts +42 -0
  97. package/dist/types/ai/tar.d.ts +25 -0
  98. package/dist/types/ai/visibility.d.ts +17 -0
  99. package/dist/types/ai/web-bot-auth.d.ts +16 -0
  100. package/dist/types/analytics/adobe.d.ts +35 -0
  101. package/dist/types/analytics/amplitude.d.ts +50 -0
  102. package/dist/types/analytics/clarity.d.ts +31 -0
  103. package/dist/types/analytics/clearbit.d.ts +29 -0
  104. package/dist/types/analytics/cloudflare.d.ts +41 -0
  105. package/dist/types/analytics/fathom.d.ts +41 -0
  106. package/dist/types/analytics/google-analytics.d.ts +44 -0
  107. package/dist/types/analytics/google-tag-manager.d.ts +40 -0
  108. package/dist/types/analytics/head.d.ts +30 -0
  109. package/dist/types/analytics/heap.d.ts +40 -0
  110. package/dist/types/analytics/hightouch.d.ts +44 -0
  111. package/dist/types/analytics/hotjar.d.ts +33 -0
  112. package/dist/types/analytics/index.d.ts +61 -0
  113. package/dist/types/analytics/inline.d.ts +17 -0
  114. package/dist/types/analytics/logrocket.d.ts +44 -0
  115. package/dist/types/analytics/mixpanel.d.ts +67 -0
  116. package/dist/types/analytics/pirsch.d.ts +42 -0
  117. package/dist/types/analytics/plausible.d.ts +56 -0
  118. package/dist/types/analytics/posthog.d.ts +45 -0
  119. package/dist/types/analytics/schema.d.ts +320 -0
  120. package/dist/types/analytics/script.d.ts +46 -0
  121. package/dist/types/analytics/segment.d.ts +50 -0
  122. package/dist/types/analytics/vercel.d.ts +48 -0
  123. package/dist/types/astro/integration.d.ts +76 -0
  124. package/dist/types/astro/markdown-negotiation.d.ts +23 -0
  125. package/dist/types/astro/module-types.d.ts +14 -0
  126. package/dist/types/astro/pages.d.ts +44 -0
  127. package/dist/types/cli/env.d.ts +12 -0
  128. package/dist/types/cli/init/scaffold.d.ts +154 -0
  129. package/dist/types/components/layout/nav-utils.d.ts +11 -0
  130. package/dist/types/core/adapter.d.ts +47 -0
  131. package/dist/types/core/api-name.d.ts +7 -0
  132. package/dist/types/core/changelog-index.d.ts +13 -0
  133. package/dist/types/core/config-input.d.ts +186 -515
  134. package/dist/types/core/config.d.ts +60 -34
  135. package/dist/types/core/content-assets.d.ts +76 -0
  136. package/dist/types/core/custom-pages.d.ts +33 -0
  137. package/dist/types/core/data.d.ts +27 -12
  138. package/dist/types/core/define-components.d.ts +12 -9
  139. package/dist/types/core/deployment-env.d.ts +6 -11
  140. package/dist/types/core/frontmatter.d.ts +10 -0
  141. package/dist/types/core/graph.d.ts +18 -0
  142. package/dist/types/core/heading-markers.d.ts +54 -0
  143. package/dist/types/core/i18n-ui.d.ts +6 -2
  144. package/dist/types/core/i18n.d.ts +87 -0
  145. package/dist/types/core/includes.d.ts +138 -0
  146. package/dist/types/core/last-modified.d.ts +47 -0
  147. package/dist/types/core/links.d.ts +95 -0
  148. package/dist/types/core/locale-links.d.ts +60 -0
  149. package/dist/types/core/manifest.d.ts +17 -0
  150. package/dist/types/core/meta.d.ts +38 -0
  151. package/dist/types/core/nav-diagnostics.d.ts +26 -0
  152. package/dist/types/core/navigation.d.ts +6 -0
  153. package/dist/types/core/node-require.d.ts +19 -0
  154. package/dist/types/core/package-json.d.ts +13 -0
  155. package/dist/types/core/probe.d.ts +42 -0
  156. package/dist/types/core/project-graph.d.ts +51 -0
  157. package/dist/types/core/project.d.ts +2 -0
  158. package/dist/types/core/safe-href.d.ts +2 -0
  159. package/dist/types/core/safe-links.d.ts +26 -0
  160. package/dist/types/core/schema.d.ts +2282 -452
  161. package/dist/types/core/site-url.d.ts +16 -0
  162. package/dist/types/core/sources/assets.d.ts +36 -0
  163. package/dist/types/core/sources/cache.d.ts +37 -0
  164. package/dist/types/core/sources/collection.d.ts +28 -0
  165. package/dist/types/core/sources/contentful-rich-text.d.ts +31 -0
  166. package/dist/types/core/sources/contentful.d.ts +40 -0
  167. package/dist/types/core/sources/filesystem.d.ts +24 -0
  168. package/dist/types/core/sources/github-releases.d.ts +31 -0
  169. package/dist/types/core/sources/json.d.ts +30 -0
  170. package/dist/types/core/sources/lexical.d.ts +19 -0
  171. package/dist/types/core/sources/lower.d.ts +75 -0
  172. package/dist/types/core/sources/mdx-remote.d.ts +28 -0
  173. package/dist/types/core/sources/normalize.d.ts +147 -0
  174. package/dist/types/core/sources/notion.d.ts +131 -0
  175. package/dist/types/core/sources/obsidian.d.ts +46 -0
  176. package/dist/types/core/sources/payload.d.ts +39 -0
  177. package/dist/types/core/sources/portable-text.d.ts +42 -0
  178. package/dist/types/core/sources/read.d.ts +23 -0
  179. package/dist/types/core/sources/remote.d.ts +74 -0
  180. package/dist/types/core/sources/resolve.d.ts +19 -0
  181. package/dist/types/core/sources/sanity.d.ts +43 -0
  182. package/dist/types/core/sources/strapi-blocks.d.ts +12 -0
  183. package/dist/types/core/sources/strapi.d.ts +33 -0
  184. package/dist/types/core/sources/types.d.ts +7 -0
  185. package/dist/types/core/sources/watch.d.ts +45 -0
  186. package/dist/types/core/text-width.d.ts +11 -0
  187. package/dist/types/core/types.d.ts +15 -1
  188. package/dist/types/core/unrecognized-keys.d.ts +7 -0
  189. package/dist/types/core/versions.d.ts +72 -0
  190. package/dist/types/core/yaml.d.ts +9 -0
  191. package/dist/types/deploy/adapter-output.d.ts +45 -0
  192. package/dist/types/deploy/adapters/cloudflare.d.ts +40 -0
  193. package/dist/types/deploy/adapters/index.d.ts +29 -0
  194. package/dist/types/deploy/adapters/netlify.d.ts +37 -0
  195. package/dist/types/deploy/adapters/node.d.ts +37 -0
  196. package/dist/types/deploy/adapters/registry.d.ts +133 -0
  197. package/dist/types/deploy/adapters/types.d.ts +71 -0
  198. package/dist/types/deploy/adapters/vercel.d.ts +38 -0
  199. package/dist/types/deploy/artifacts.d.ts +65 -0
  200. package/dist/types/deploy/cloudflare-negotiation.d.ts +196 -0
  201. package/dist/types/deploy/function-bundle.d.ts +80 -0
  202. package/dist/types/deploy/headers.d.ts +50 -0
  203. package/dist/types/deploy/node-headers.d.ts +42 -0
  204. package/dist/types/deploy/platforms/cloudflare.d.ts +40 -0
  205. package/dist/types/deploy/platforms/index.d.ts +18 -0
  206. package/dist/types/deploy/platforms/netlify.d.ts +13 -0
  207. package/dist/types/deploy/platforms/node.d.ts +11 -0
  208. package/dist/types/deploy/platforms/paths.d.ts +27 -0
  209. package/dist/types/deploy/platforms/static.d.ts +10 -0
  210. package/dist/types/deploy/platforms/types.d.ts +104 -0
  211. package/dist/types/deploy/platforms/vercel.d.ts +32 -0
  212. package/dist/types/deploy/redirects.d.ts +53 -0
  213. package/dist/types/deploy/robots.d.ts +8 -0
  214. package/dist/types/deploy/rss.d.ts +31 -0
  215. package/dist/types/deploy/sitemap.d.ts +21 -0
  216. package/dist/types/deploy/vercel-negotiation.d.ts +109 -0
  217. package/dist/types/markdown/code-title.d.ts +32 -0
  218. package/dist/types/markdown/fence-meta.d.ts +23 -0
  219. package/dist/types/markdown/themes.d.ts +3 -3
  220. package/dist/types/openapi/asyncapi.d.ts +129 -0
  221. package/dist/types/openapi/graphql-build.d.ts +8 -0
  222. package/dist/types/openapi/graphql.d.ts +122 -0
  223. package/dist/types/openapi/model.d.ts +158 -0
  224. package/dist/types/openapi/parse.d.ts +57 -0
  225. package/dist/types/openapi/references.d.ts +37 -25
  226. package/dist/types/openapi/render-mdx.d.ts +33 -0
  227. package/dist/types/openapi/sentence.d.ts +7 -0
  228. package/dist/types/openapi/signature.d.ts +10 -0
  229. package/dist/types/openapi/source.d.ts +22 -0
  230. package/dist/types/openapi/spec-dependency-error.d.ts +10 -0
  231. package/dist/types/reference/asyncapi.d.ts +166 -0
  232. package/dist/types/reference/graphql.d.ts +181 -0
  233. package/dist/types/reference/index.d.ts +32 -0
  234. package/dist/types/reference/openapi.d.ts +165 -0
  235. package/dist/types/reference/options.d.ts +157 -0
  236. package/dist/types/reference/scalar.d.ts +136 -0
  237. package/dist/types/reference/schema.d.ts +630 -0
  238. package/dist/types/search/adapters/algolia.d.ts +32 -0
  239. package/dist/types/search/adapters/flexsearch.d.ts +12 -0
  240. package/dist/types/search/adapters/index.d.ts +31 -0
  241. package/dist/types/search/adapters/mixedbread.d.ts +22 -0
  242. package/dist/types/search/adapters/orama-cloud.d.ts +33 -0
  243. package/dist/types/search/adapters/orama.d.ts +13 -0
  244. package/dist/types/search/adapters/pagefind.d.ts +12 -0
  245. package/dist/types/search/adapters/registry.d.ts +228 -0
  246. package/dist/types/search/adapters/types.d.ts +34 -0
  247. package/dist/types/search/adapters/typesense.d.ts +42 -0
  248. package/dist/types/search/build.d.ts +23 -0
  249. package/dist/types/search/documents.d.ts +90 -0
  250. package/dist/types/search/facets.d.ts +3 -0
  251. package/dist/types/search/sync/algolia.d.ts +14 -0
  252. package/dist/types/search/sync/index.d.ts +14 -0
  253. package/dist/types/search/sync/orama-cloud.d.ts +10 -0
  254. package/dist/types/search/sync/typesense.d.ts +14 -0
  255. package/dist/types/sources/contentful.d.ts +68 -0
  256. package/dist/types/sources/custom.d.ts +20 -0
  257. package/dist/types/sources/filesystem.d.ts +42 -0
  258. package/dist/types/sources/github-releases.d.ts +46 -0
  259. package/dist/types/sources/index.d.ts +48 -0
  260. package/dist/types/sources/mdx-remote.d.ts +63 -0
  261. package/dist/types/sources/notion.d.ts +65 -0
  262. package/dist/types/sources/obsidian.d.ts +34 -0
  263. package/dist/types/sources/payload.d.ts +68 -0
  264. package/dist/types/sources/registry.d.ts +1414 -0
  265. package/dist/types/sources/sanity.d.ts +69 -0
  266. package/dist/types/sources/shared.d.ts +46 -0
  267. package/dist/types/sources/strapi.d.ts +66 -0
  268. package/dist/types/theme/icon-kind.d.ts +11 -0
  269. package/dist/types/theme/icons.d.ts +20 -0
  270. package/docs/01-quickstart.mdx +18 -18
  271. package/docs/02-deployment.mdx +50 -26
  272. package/docs/03-upgrading.mdx +351 -0
  273. package/docs/04-migrating.mdx +58 -0
  274. package/docs/08-faq.mdx +3 -3
  275. package/docs/advanced/blog.mdx +13 -6
  276. package/docs/advanced/changelog.mdx +23 -35
  277. package/docs/advanced/custom-pages.mdx +20 -8
  278. package/docs/advanced/meta.ts +1 -8
  279. package/docs/advanced/skills.mdx +8 -0
  280. package/docs/cli/audit.mdx +646 -0
  281. package/docs/cli/doctor.mdx +29 -0
  282. package/docs/{reference/eval.mdx → cli/evals.mdx} +9 -8
  283. package/docs/cli/index.mdx +105 -0
  284. package/docs/cli/meta.ts +7 -0
  285. package/docs/{reference → cli}/translate.mdx +1 -1
  286. package/docs/cli/validate.mdx +41 -0
  287. package/docs/cli/version.mdx +40 -0
  288. package/docs/configuration/analytics.mdx +350 -59
  289. package/docs/configuration/ask-ai.mdx +162 -61
  290. package/docs/configuration/customization.mdx +15 -9
  291. package/docs/configuration/index.mdx +50 -31
  292. package/docs/configuration/search.mdx +75 -54
  293. package/docs/configuration/theming.mdx +24 -15
  294. package/docs/content/components.mdx +21 -5
  295. package/docs/{reference → content}/frontmatter.mdx +35 -1
  296. package/docs/content/i18n.mdx +8 -6
  297. package/docs/content/includes.mdx +2 -4
  298. package/docs/content/index.mdx +1 -1
  299. package/docs/content/islands.mdx +10 -5
  300. package/docs/content/meta.mdx +1 -1
  301. package/docs/content/meta.ts +1 -0
  302. package/docs/content/navigation.mdx +9 -7
  303. package/docs/content/sources.mdx +145 -50
  304. package/docs/content/syntax.mdx +16 -12
  305. package/docs/content/versioning.mdx +3 -3
  306. package/docs/discoverability/agent-discovery.mdx +11 -11
  307. package/docs/discoverability/index.mdx +4 -4
  308. package/docs/discoverability/json-api.mdx +4 -4
  309. package/docs/discoverability/llms-txt.mdx +6 -6
  310. package/docs/discoverability/markdown.mdx +4 -4
  311. package/docs/discoverability/mcp.mdx +11 -11
  312. package/docs/discoverability/sitemap-and-robots.mdx +3 -3
  313. package/docs/index.mdx +2 -2
  314. package/docs/references/asyncapi.mdx +59 -0
  315. package/docs/{advanced → references}/graphql.mdx +45 -32
  316. package/docs/{reference → references}/meta.ts +2 -2
  317. package/docs/references/openapi.mdx +171 -0
  318. package/docs/references/scalar.mdx +64 -0
  319. package/package.json +42 -8
  320. package/skills/blume/SKILL.md +21 -8
  321. package/skills/blume-migrate/SKILL.md +22 -21
  322. package/skills/blume-migrate/assets/oxfmt@0.67.0.patch +49 -0
  323. package/skills/blume-migrate/references/docusaurus.md +5 -4
  324. package/skills/blume-migrate/references/fumadocs.md +4 -4
  325. package/skills/blume-migrate/references/mintlify.md +23 -8
  326. package/skills/blume-migrate/references/monorepo.md +6 -6
  327. package/skills/blume-migrate/references/starlight.md +4 -4
  328. package/src/ai/agent-readability.ts +21 -22
  329. package/src/ai/ai-catalog.ts +50 -32
  330. package/src/ai/api/handlers.ts +46 -11
  331. package/src/ai/api-catalog.ts +8 -8
  332. package/src/ai/ask-data.ts +1 -1
  333. package/src/ai/ask.ts +624 -100
  334. package/src/ai/changelog-markdown.ts +91 -0
  335. package/src/ai/component-markdown.ts +328 -12
  336. package/src/ai/index.ts +45 -0
  337. package/src/ai/link-headers.ts +7 -6
  338. package/src/ai/llms.ts +20 -15
  339. package/src/ai/markdown.ts +35 -5
  340. package/src/ai/mcp/data.ts +9 -5
  341. package/src/ai/openapi-components.ts +4 -1
  342. package/src/ai/relative-links.ts +170 -0
  343. package/src/ai/serializers.ts +2 -2
  344. package/src/ai/skills.ts +1 -1
  345. package/src/ai/web-bot-auth.ts +2 -2
  346. package/src/analytics/adobe.ts +46 -0
  347. package/src/analytics/amplitude.ts +79 -0
  348. package/src/analytics/clarity.ts +46 -0
  349. package/src/analytics/clearbit.ts +47 -0
  350. package/src/analytics/cloudflare.ts +67 -0
  351. package/src/analytics/fathom.ts +64 -0
  352. package/src/analytics/google-analytics.ts +84 -0
  353. package/src/analytics/google-tag-manager.ts +61 -0
  354. package/src/analytics/head.ts +130 -0
  355. package/src/analytics/heap.ts +65 -0
  356. package/src/analytics/hightouch.ts +77 -0
  357. package/src/analytics/hotjar.ts +47 -0
  358. package/src/analytics/index.ts +71 -0
  359. package/src/analytics/inline.ts +21 -0
  360. package/src/analytics/logrocket.ts +71 -0
  361. package/src/analytics/mixpanel.ts +92 -0
  362. package/src/analytics/pirsch.ts +66 -0
  363. package/src/analytics/plausible.ts +83 -0
  364. package/src/analytics/posthog.ts +78 -0
  365. package/src/analytics/schema.ts +60 -0
  366. package/src/analytics/script.ts +60 -0
  367. package/src/analytics/segment.ts +85 -0
  368. package/src/analytics/vercel.ts +50 -0
  369. package/src/astro/adapter-root.ts +7 -9
  370. package/src/astro/component-slots.ts +131 -91
  371. package/src/astro/generate.ts +112 -345
  372. package/src/astro/integration.ts +2 -6
  373. package/src/astro/pages.ts +13 -101
  374. package/src/astro/render-deps.ts +379 -0
  375. package/src/astro/runtime-deps.ts +201 -0
  376. package/src/astro/templates.ts +554 -541
  377. package/src/audit/agent.ts +24 -0
  378. package/src/audit/catalog.ts +2 -2
  379. package/src/audit/checks/assets.ts +2 -2
  380. package/src/audit/checks/dns-aid.ts +1 -1
  381. package/src/audit/checks/duplicates.ts +3 -1
  382. package/src/audit/checks/i18n.ts +1 -1
  383. package/src/audit/checks/indexability.ts +7 -7
  384. package/src/audit/checks/links.ts +2 -2
  385. package/src/audit/checks/llms.ts +6 -6
  386. package/src/audit/checks/network.ts +3 -3
  387. package/src/audit/checks/og-image.ts +2 -2
  388. package/src/audit/checks/robots.ts +1 -1
  389. package/src/audit/checks/sitemap.ts +2 -2
  390. package/src/audit/checks/social.ts +1 -1
  391. package/src/audit/run.ts +4 -3
  392. package/src/audit/terms.ts +31 -0
  393. package/src/audit/url.ts +13 -13
  394. package/src/cli/command-meta.ts +10 -0
  395. package/src/cli/commands/audit.ts +39 -20
  396. package/src/cli/commands/build.ts +71 -316
  397. package/src/cli/commands/check.ts +1 -0
  398. package/src/cli/commands/dev.ts +40 -1
  399. package/src/cli/commands/doctor.ts +90 -14
  400. package/src/cli/commands/eject.ts +44 -7
  401. package/src/cli/commands/eval.ts +2 -2
  402. package/src/cli/commands/init.ts +161 -41
  403. package/src/cli/commands/migrate.ts +121 -0
  404. package/src/cli/commands/preview.ts +7 -1
  405. package/src/cli/commands/translate.ts +2 -2
  406. package/src/cli/commands/upgrade.ts +141 -0
  407. package/src/cli/commands/version.ts +57 -44
  408. package/src/cli/eject-scripts.ts +121 -6
  409. package/src/cli/index.ts +11 -1
  410. package/src/cli/init/install.ts +70 -0
  411. package/src/cli/init/questions.ts +7 -0
  412. package/src/cli/init/scaffold.ts +459 -75
  413. package/src/cli/lazy-command.ts +37 -1
  414. package/src/cli/prepare.ts +40 -6
  415. package/src/cli/required-secrets.ts +38 -11
  416. package/src/cli/unknown-flags.ts +266 -0
  417. package/src/cli/yarn-pnp.ts +52 -0
  418. package/src/components/content/AccordionItem.astro +7 -1
  419. package/src/components/content/Card.astro +4 -3
  420. package/src/components/content/ColorItem.astro +22 -4
  421. package/src/components/content/GithubInfo.astro +2 -2
  422. package/src/components/content/Prompt.astro +25 -25
  423. package/src/components/content/Tabs.astro +3 -1
  424. package/src/components/content/Tile.astro +2 -3
  425. package/src/components/content/Tooltip.astro +57 -9
  426. package/src/components/content/content-strings.ts +34 -0
  427. package/src/components/content/diff.ts +1 -1
  428. package/src/components/content/mermaid-element.ts +13 -2
  429. package/src/components/content/prompt-markdown.ts +292 -0
  430. package/src/components/content/tooltip-id.ts +41 -0
  431. package/src/components/islands/hooks.ts +25 -2
  432. package/src/components/layout/Analytics.astro +21 -79
  433. package/src/components/layout/Banner.astro +3 -1
  434. package/src/components/layout/DiscoveryLinks.astro +69 -0
  435. package/src/components/layout/Header.astro +83 -20
  436. package/src/components/layout/LanguageSwitcher.astro +4 -1
  437. package/src/components/layout/Logo.astro +23 -2
  438. package/src/components/layout/NavSelector.astro +13 -2
  439. package/src/components/layout/NavTree.astro +12 -3
  440. package/src/components/layout/NavTreeScript.astro +17 -3
  441. package/src/components/layout/PageActions.astro +3 -3
  442. package/src/components/layout/PageFeedback.astro +11 -1
  443. package/src/components/layout/PageLayout.astro +52 -5
  444. package/src/components/layout/ReferenceLayout.astro +21 -12
  445. package/src/components/layout/RootLayout.astro +69 -66
  446. package/src/components/layout/Search.astro +30 -6
  447. package/src/components/layout/WebMcp.astro +1 -1
  448. package/src/components/layout/analytics-client.ts +72 -15
  449. package/src/components/layout/drawer-inert.ts +113 -15
  450. package/src/components/layout/dropdown-clamp.ts +105 -0
  451. package/src/components/layout/head-scripts.ts +15 -6
  452. package/src/components/layout/nav-utils.ts +17 -0
  453. package/src/components/layout/search/algolia.ts +8 -8
  454. package/src/components/layout/search/orama-cloud.ts +9 -7
  455. package/src/components/layout/search/typesense.ts +11 -17
  456. package/src/components/openapi/MessageComposer.astro +2 -2
  457. package/src/components/openapi/Playground.astro +2 -2
  458. package/src/components/openapi/playground-client.ts +79 -10
  459. package/src/core/changelog-index.ts +24 -0
  460. package/src/core/component-overrides.ts +399 -154
  461. package/src/core/config-input.ts +188 -553
  462. package/src/core/config.ts +130 -41
  463. package/src/core/custom-pages.ts +105 -0
  464. package/src/core/data.ts +31 -12
  465. package/src/core/define-components.ts +12 -9
  466. package/src/core/deployment-env.ts +18 -74
  467. package/src/core/diagnostics.ts +14 -7
  468. package/src/core/graph.ts +69 -25
  469. package/src/core/i18n-ui.ts +6 -2
  470. package/src/core/i18n.ts +17 -0
  471. package/src/core/includes.ts +156 -38
  472. package/src/core/last-modified.ts +6 -11
  473. package/src/core/links.ts +166 -2
  474. package/src/core/manifest.ts +10 -3
  475. package/src/core/navigation.ts +115 -16
  476. package/src/core/new-tab.ts +35 -0
  477. package/src/core/node-require.ts +21 -0
  478. package/src/core/project-graph.ts +42 -26
  479. package/src/core/project.ts +9 -4
  480. package/src/core/request-body.ts +61 -0
  481. package/src/core/safe-href.ts +28 -0
  482. package/src/core/safe-links.ts +68 -0
  483. package/src/core/schema.ts +517 -739
  484. package/src/core/server-features.ts +8 -9
  485. package/src/core/sources/assets.ts +47 -11
  486. package/src/core/sources/collection.ts +67 -0
  487. package/src/core/sources/contentful-rich-text.ts +285 -0
  488. package/src/core/sources/contentful.ts +173 -0
  489. package/src/core/sources/github-releases.ts +51 -1
  490. package/src/core/sources/json.ts +71 -0
  491. package/src/core/sources/lexical.ts +195 -0
  492. package/src/core/sources/lower.ts +226 -0
  493. package/src/core/sources/normalize.ts +52 -2
  494. package/src/core/sources/notion.ts +39 -28
  495. package/src/core/sources/payload.ts +135 -0
  496. package/src/core/sources/portable-text.ts +11 -16
  497. package/src/core/sources/remote.ts +226 -0
  498. package/src/core/sources/resolve.ts +104 -166
  499. package/src/core/sources/sanity.ts +16 -49
  500. package/src/core/sources/strapi-blocks.ts +124 -0
  501. package/src/core/sources/strapi.ts +191 -0
  502. package/src/core/sources/types.ts +12 -1
  503. package/src/core/types.ts +15 -1
  504. package/src/core/ui-packs/ar.ts +6 -2
  505. package/src/core/ui-packs/bg.ts +6 -2
  506. package/src/core/ui-packs/bn.ts +6 -2
  507. package/src/core/ui-packs/ca.ts +3 -1
  508. package/src/core/ui-packs/cs.ts +6 -2
  509. package/src/core/ui-packs/da.ts +6 -2
  510. package/src/core/ui-packs/de.ts +6 -2
  511. package/src/core/ui-packs/el.ts +3 -1
  512. package/src/core/ui-packs/es.ts +3 -1
  513. package/src/core/ui-packs/fa.ts +6 -2
  514. package/src/core/ui-packs/fi.ts +6 -2
  515. package/src/core/ui-packs/fr.ts +3 -1
  516. package/src/core/ui-packs/he.ts +6 -2
  517. package/src/core/ui-packs/hi.ts +6 -2
  518. package/src/core/ui-packs/hr.ts +6 -2
  519. package/src/core/ui-packs/hu.ts +6 -2
  520. package/src/core/ui-packs/id.ts +6 -2
  521. package/src/core/ui-packs/it.ts +3 -1
  522. package/src/core/ui-packs/ja.ts +3 -1
  523. package/src/core/ui-packs/ko.ts +3 -1
  524. package/src/core/ui-packs/nl.ts +6 -2
  525. package/src/core/ui-packs/no.ts +6 -2
  526. package/src/core/ui-packs/pl.ts +6 -2
  527. package/src/core/ui-packs/pt-br.ts +3 -1
  528. package/src/core/ui-packs/pt.ts +3 -1
  529. package/src/core/ui-packs/ro.ts +6 -2
  530. package/src/core/ui-packs/ru.ts +6 -2
  531. package/src/core/ui-packs/sk.ts +6 -2
  532. package/src/core/ui-packs/sr.ts +6 -2
  533. package/src/core/ui-packs/sv.ts +6 -2
  534. package/src/core/ui-packs/th.ts +3 -1
  535. package/src/core/ui-packs/tr.ts +6 -2
  536. package/src/core/ui-packs/uk.ts +6 -2
  537. package/src/core/ui-packs/vi.ts +3 -1
  538. package/src/core/ui-packs/zh-tw.ts +3 -1
  539. package/src/core/ui-packs/zh.ts +3 -1
  540. package/src/core/unrecognized-keys.ts +10 -0
  541. package/src/core/version-cut.ts +116 -8
  542. package/src/deploy/adapter-output.ts +57 -97
  543. package/src/deploy/adapters/cloudflare.ts +39 -0
  544. package/src/deploy/adapters/index.ts +41 -0
  545. package/src/deploy/adapters/netlify.ts +34 -0
  546. package/src/deploy/adapters/node.ts +34 -0
  547. package/src/deploy/adapters/registry.ts +127 -0
  548. package/src/deploy/adapters/types.ts +92 -0
  549. package/src/deploy/adapters/vercel.ts +35 -0
  550. package/src/deploy/artifacts.ts +55 -27
  551. package/src/deploy/cloudflare-negotiation.ts +179 -100
  552. package/src/deploy/function-bundle.ts +18 -3
  553. package/src/deploy/headers.ts +124 -23
  554. package/src/deploy/node-headers.ts +198 -0
  555. package/src/deploy/platforms/cloudflare.ts +312 -0
  556. package/src/deploy/platforms/index.ts +67 -0
  557. package/src/deploy/platforms/netlify.ts +52 -0
  558. package/src/deploy/platforms/node.ts +40 -0
  559. package/src/deploy/platforms/paths.ts +42 -0
  560. package/src/deploy/platforms/static.ts +29 -0
  561. package/src/deploy/platforms/types.ts +112 -0
  562. package/src/deploy/platforms/vercel.ts +193 -0
  563. package/src/deploy/redirects.ts +30 -18
  564. package/src/deploy/robots.ts +3 -3
  565. package/src/deploy/rss.ts +2 -2
  566. package/src/deploy/sitemap.ts +2 -2
  567. package/src/deploy/vercel-negotiation.ts +2 -2
  568. package/src/eval/agents.ts +32 -1
  569. package/src/eval/findings.ts +19 -11
  570. package/src/eval/report.ts +10 -2
  571. package/src/markdown/external-links.ts +65 -0
  572. package/src/markdown/include.ts +45 -27
  573. package/src/markdown/index.ts +34 -9
  574. package/src/markdown/inline-code.ts +12 -6
  575. package/src/markdown/relative-links.ts +324 -0
  576. package/src/markdown/themes.ts +3 -3
  577. package/src/migrate/migrate.ts +149 -0
  578. package/src/openapi/parse.ts +64 -16
  579. package/src/openapi/proxy.ts +62 -10
  580. package/src/openapi/references.ts +147 -147
  581. package/src/openapi/render-mdx.ts +32 -10
  582. package/src/openapi/scalar.ts +15 -13
  583. package/src/openapi/sentence.ts +14 -0
  584. package/src/openapi/source.ts +37 -21
  585. package/src/openapi/spec-dependency-error.ts +15 -0
  586. package/src/reference/asyncapi.ts +83 -0
  587. package/src/reference/graphql.ts +89 -0
  588. package/src/reference/index.ts +40 -0
  589. package/src/reference/openapi.ts +83 -0
  590. package/src/reference/options.ts +201 -0
  591. package/src/reference/scalar.ts +136 -0
  592. package/src/reference/schema.ts +88 -0
  593. package/src/registry/eject.ts +97 -42
  594. package/src/search/adapters/algolia.ts +46 -0
  595. package/src/search/adapters/flexsearch.ts +29 -0
  596. package/src/search/adapters/index.ts +39 -0
  597. package/src/search/adapters/mixedbread.ts +39 -0
  598. package/src/search/adapters/orama-cloud.ts +51 -0
  599. package/src/search/adapters/orama.ts +24 -0
  600. package/src/search/adapters/pagefind.ts +27 -0
  601. package/src/search/adapters/registry.ts +131 -0
  602. package/src/search/adapters/types.ts +46 -0
  603. package/src/search/adapters/typesense.ts +57 -0
  604. package/src/search/build.ts +8 -4
  605. package/src/search/documents.ts +6 -0
  606. package/src/search/sync/algolia.ts +12 -11
  607. package/src/search/sync/index.ts +35 -20
  608. package/src/search/sync/orama-cloud.ts +12 -9
  609. package/src/search/sync/typesense.ts +15 -13
  610. package/src/sources/contentful.ts +63 -0
  611. package/src/sources/custom.ts +38 -0
  612. package/src/sources/filesystem.ts +51 -0
  613. package/src/sources/github-releases.ts +52 -0
  614. package/src/sources/index.ts +60 -0
  615. package/src/sources/mdx-remote.ts +76 -0
  616. package/src/sources/notion.ts +63 -0
  617. package/src/sources/obsidian.ts +38 -0
  618. package/src/sources/payload.ts +60 -0
  619. package/src/sources/registry.ts +182 -0
  620. package/src/sources/sanity.ts +66 -0
  621. package/src/sources/shared.ts +52 -0
  622. package/src/sources/strapi.ts +58 -0
  623. package/src/theme/entry.ts +24 -2
  624. package/src/translate/report.ts +40 -7
  625. package/src/upgrade/upgrade.ts +499 -0
  626. package/dist/cli/chunk-0qymqwzz.js +0 -164
  627. package/dist/cli/chunk-0qymqwzz.js.map +0 -15
  628. package/dist/cli/chunk-1jefwnfs.js.map +0 -48
  629. package/dist/cli/chunk-2mzebbbz.js +0 -69
  630. package/dist/cli/chunk-2z47ypj8.js.map +0 -11
  631. package/dist/cli/chunk-4x36ddpw.js.map +0 -11
  632. package/dist/cli/chunk-5093q3n7.js +0 -68
  633. package/dist/cli/chunk-5093q3n7.js.map +0 -10
  634. package/dist/cli/chunk-5qk08vmp.js.map +0 -11
  635. package/dist/cli/chunk-7s8hm3b6.js +0 -5347
  636. package/dist/cli/chunk-7s8hm3b6.js.map +0 -58
  637. package/dist/cli/chunk-8cjtbafj.js.map +0 -13
  638. package/dist/cli/chunk-97r59kpr.js +0 -381
  639. package/dist/cli/chunk-97r59kpr.js.map +0 -12
  640. package/dist/cli/chunk-ahnw3kxw.js.map +0 -15
  641. package/dist/cli/chunk-b27xqwn9.js.map +0 -10
  642. package/dist/cli/chunk-bf6bt1xt.js +0 -185
  643. package/dist/cli/chunk-bf6bt1xt.js.map +0 -11
  644. package/dist/cli/chunk-cjtn640a.js.map +0 -10
  645. package/dist/cli/chunk-ct47dqpx.js.map +0 -11
  646. package/dist/cli/chunk-esphfr8p.js +0 -107
  647. package/dist/cli/chunk-esphfr8p.js.map +0 -11
  648. package/dist/cli/chunk-ex56aa81.js +0 -1016
  649. package/dist/cli/chunk-ex56aa81.js.map +0 -13
  650. package/dist/cli/chunk-garjf5z9.js +0 -30
  651. package/dist/cli/chunk-garjf5z9.js.map +0 -10
  652. package/dist/cli/chunk-js7saxwm.js +0 -1045
  653. package/dist/cli/chunk-js7saxwm.js.map +0 -22
  654. package/dist/cli/chunk-k79xp7av.js.map +0 -39
  655. package/dist/cli/chunk-ps4m1xh4.js.map +0 -15
  656. package/dist/cli/chunk-q4rae3bg.js +0 -60
  657. package/dist/cli/chunk-q4rae3bg.js.map +0 -10
  658. package/dist/cli/chunk-rz9jmfhz.js +0 -108
  659. package/dist/cli/chunk-rz9jmfhz.js.map +0 -10
  660. package/dist/cli/chunk-vh9w1sgp.js +0 -73
  661. package/dist/cli/chunk-vh9w1sgp.js.map +0 -10
  662. package/dist/cli/chunk-vrfp10qk.js +0 -81
  663. package/dist/cli/chunk-vrfp10qk.js.map +0 -10
  664. package/dist/cli/chunk-yg63d42r.js.map +0 -34
  665. package/docs/advanced/api-reference.mdx +0 -240
  666. package/docs/reference/cli.mdx +0 -197
  667. package/skills/blume-migrate/assets/oxfmt@0.55.0.patch +0 -20
  668. package/src/components/content/changelog-element.ts +0 -69
  669. package/src/search/providers.ts +0 -91
  670. /package/dist/cli/{chunk-e7f42gdj.js.map → chunk-7ez8ny0t.js.map} +0 -0
  671. /package/dist/cli/{chunk-nn13znc2.js.map → chunk-88cpgt6h.js.map} +0 -0
  672. /package/dist/cli/{chunk-vacwm2hv.js.map → chunk-f7t03s3g.js.map} +0 -0
  673. /package/dist/cli/{chunk-dwgcp5sm.js.map → chunk-zxccj738.js.map} +0 -0
package/src/core/links.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { existsSync } from "node:fs";
2
2
 
3
- import { basename, dirname, join, relative, resolve } from "pathe";
3
+ import { basename, dirname, join, normalize, relative, resolve } from "pathe";
4
4
 
5
5
  import { stripBasePath, withBasePath } from "./base-path.ts";
6
6
  import {
@@ -80,6 +80,8 @@ interface LinkContext {
80
80
  /** Servable routes outside the graph (custom pages, generated routes); their
81
81
  * headings are unknown, so anchors there are accepted unchecked. */
82
82
  extraRoutes: Set<string>;
83
+ /** Where content files publish, for file links (`./setup.mdx`). */
84
+ fileRoutes: FileRouteIndex;
83
85
  /** Locale routing, when the site is multi-locale; drives served-route resolution. */
84
86
  i18n: LocaleRouting | null;
85
87
  publicDir: string | null;
@@ -156,6 +158,81 @@ const toRoute = (path: string): string => {
156
158
  return route === "" ? "/" : route;
157
159
  };
158
160
 
161
+ /** The page a relative link is written on, as the resolver sees it. */
162
+ export interface RelativeLinkBase {
163
+ /** Whether the page is its folder's index (its route is its directory). */
164
+ isIndex: boolean;
165
+ /** The page's own route, base path included. */
166
+ route: string;
167
+ }
168
+
169
+ /** A root-relative path, a bare `#fragment` or `?query`, or any scheme. */
170
+ const NOT_RELATIVE = /^(?:[#?/]|[a-z][a-z0-9+.-]*:)/iu;
171
+
172
+ /**
173
+ * Where a relative page link written on `from` lands: a root-relative route
174
+ * with the authored `?query#hash` kept — or `undefined` when `href` isn't a
175
+ * relative page link (a root-relative path, a bare `#fragment` or `?query`,
176
+ * an external URL or other scheme, or a relative asset like `./diagram.png`).
177
+ *
178
+ * The one reading `blume validate` checks links against and the Markdown
179
+ * pipeline rewrites them to, so the built `href`, the link check, and the
180
+ * browser agree. Relative paths resolve file-style: a leaf page's links
181
+ * resolve against its parent directory, while an index page's route already is
182
+ * its directory — `./install` on `guides/index.mdx` (`/guides`) lands on
183
+ * `/guides/install`, where a browser reading the slashless URL would go to
184
+ * `/install`. A `.md`/`.mdx` target is a file link: `resolveFile` maps its
185
+ * decoded path, relative to the linking file, to the route that file
186
+ * publishes at, so a target with its own `slug` or an ordering prefix still
187
+ * lands on its page instead of on its raw Markdown source. Without
188
+ * `resolveFile`, or for a file it doesn't know, the target resolves
189
+ * route-relative with the extension dropped.
190
+ *
191
+ * Any other extension (`./diagram.png`) marks an asset, except where a page
192
+ * publishes at the resolved path: `hasRoute` answers that, so a dotted page
193
+ * name (`./node.js` for `node.js.mdx`, `./v1.2`) is still a page link.
194
+ */
195
+ export const resolveRelativeHref = (
196
+ href: string,
197
+ from: RelativeLinkBase,
198
+ resolveFile?: (path: string) => string | undefined,
199
+ hasRoute?: (route: string) => boolean
200
+ ): string | undefined => {
201
+ if (href === "" || NOT_RELATIVE.test(href)) {
202
+ return undefined;
203
+ }
204
+ const suffixAt = href.search(/[?#]/u);
205
+ const path = suffixAt === -1 ? href : href.slice(0, suffixAt);
206
+ const suffix = suffixAt === -1 ? "" : href.slice(suffixAt);
207
+ if (FILE_EXT.test(path) && !DOC_EXT.test(path)) {
208
+ const route = toRoute(resolveRelative(from.route, path, from.isIndex));
209
+ return hasRoute?.(decodePercent(route)) ? `${route}${suffix}` : undefined;
210
+ }
211
+ const fileRoute = DOC_EXT.test(path)
212
+ ? resolveFile?.(decodePercent(path))
213
+ : undefined;
214
+ return `${fileRoute ?? toRoute(resolveRelative(from.route, path, from.isIndex))}${suffix}`;
215
+ };
216
+
217
+ /**
218
+ * Whether a content file is its folder's index, from its name as written on
219
+ * disk: `index.md(x)` after an ordering prefix, optionally carrying one of
220
+ * `localeTokens` before the extension (`index.fr.mdx` under the `dot` locale
221
+ * parser, `index.$.mdx` for a file shared by every locale).
222
+ */
223
+ export const isIndexFileName = (
224
+ name: string,
225
+ localeTokens: readonly string[] = []
226
+ ): boolean => {
227
+ const stem = basename(name).replace(NUMERIC_PREFIX, "");
228
+ const match = /^index(?:\.(?<token>[^./]+))?\.(?:md|mdx)$/iu.exec(stem);
229
+ if (!match) {
230
+ return false;
231
+ }
232
+ const token = match.groups?.token;
233
+ return token === undefined || localeTokens.includes(token);
234
+ };
235
+
159
236
  /** Build a map of route -> set of heading anchor slugs. */
160
237
  const buildAnchorIndex = (pages: PageRecord[]): Map<string, Set<string>> => {
161
238
  const anchors = new Map<string, Set<string>>();
@@ -329,6 +406,92 @@ const checkExternalLinks = async (
329
406
  return diagnostics;
330
407
  };
331
408
 
409
+ /** Where content files publish, for resolving file links (`./setup.mdx`). */
410
+ export interface FileRouteIndex {
411
+ /**
412
+ * Absolute source path → the route that file publishes at. A file shared by
413
+ * every locale publishes once per locale; the default locale's route stands
414
+ * for it (a link from a localized page moves into that locale afterwards,
415
+ * exactly as the rendered link does). Fallback copies render another
416
+ * locale's file, so they never stand for it.
417
+ */
418
+ bySource: Map<string, string>;
419
+ /**
420
+ * A default-locale page's locale-stripped path (`navPath`) → its route. A
421
+ * page in a locale folder that links a sibling not translated yet
422
+ * (`fr/guides/setup.mdx` missing) means the default tree's file
423
+ * (`guides/setup.mdx`), whose route — its own `slug` included — then moves
424
+ * into the reader's locale onto the fallback copy.
425
+ */
426
+ byDefaultNavPath: Map<string, string>;
427
+ }
428
+
429
+ /** The linking side of a file link: where the page's source lives. */
430
+ export interface FileLinkBase {
431
+ navPath: string;
432
+ sourcePath: string;
433
+ }
434
+
435
+ export const buildFileRouteIndex = (
436
+ pages: readonly PageRecord[],
437
+ i18n: LocaleRouting | null
438
+ ): FileRouteIndex => {
439
+ const bySource = new Map<string, string>();
440
+ const byDefaultNavPath = new Map<string, string>();
441
+ for (const page of pages) {
442
+ const { sourcePath } = page;
443
+ if (!sourcePath || page.fallback) {
444
+ continue;
445
+ }
446
+ const isDefault = page.locale === i18n?.defaultLocale;
447
+ if (!bySource.has(sourcePath) || isDefault) {
448
+ bySource.set(sourcePath, page.route);
449
+ }
450
+ if (isDefault) {
451
+ byDefaultNavPath.set(normalize(page.navPath), page.route);
452
+ }
453
+ }
454
+ return { byDefaultNavPath, bySource };
455
+ };
456
+
457
+ /**
458
+ * The route a file link written on `from` lands on: the linked file's own
459
+ * route, or — when a locale folder doesn't have that file yet — the default
460
+ * tree's file at the same place.
461
+ */
462
+ export const routeOfLinkedFile = (
463
+ index: FileRouteIndex,
464
+ from: FileLinkBase,
465
+ path: string
466
+ ): string | undefined =>
467
+ index.bySource.get(resolve(dirname(from.sourcePath), path)) ??
468
+ index.byDefaultNavPath.get(normalize(join(dirname(from.navPath), path)));
469
+
470
+ /**
471
+ * The path a relative link on `page` resolves to — the same reading the
472
+ * Markdown pipeline rewrites the rendered `href` to (see
473
+ * {@link resolveRelativeHref}), so what's checked is what ships. A relative
474
+ * asset (`./diagram.png`) isn't a page link; it resolves against the route's
475
+ * directory for the public-dir probe.
476
+ */
477
+ const relativeTarget = (
478
+ page: PageRecord,
479
+ rawPath: string,
480
+ ctx: LinkContext
481
+ ): string => {
482
+ const base = { isIndex: isIndexPage(page), route: page.route };
483
+ const { navPath, sourcePath } = page;
484
+ const resolveFile = sourcePath
485
+ ? (path: string) =>
486
+ routeOfLinkedFile(ctx.fileRoutes, { navPath, sourcePath }, path)
487
+ : undefined;
488
+ return (
489
+ resolveRelativeHref(rawPath, base, resolveFile, (route) =>
490
+ ctx.routes.has(route)
491
+ ) ?? resolveRelative(page.route, rawPath, base.isIndex)
492
+ );
493
+ };
494
+
332
495
  /** Classify a single link, queueing external refs via `onExternal`. */
333
496
  const classifyLink = (
334
497
  page: PageRecord,
@@ -382,7 +545,7 @@ const classifyLink = (
382
545
 
383
546
  const resolved = rawPath.startsWith("/")
384
547
  ? rawPath
385
- : resolveRelative(page.route, rawPath, isIndexPage(page));
548
+ : relativeTarget(page, rawPath, ctx);
386
549
  return checkPathLink(resolved, fragment, page, link, site, ctx, via);
387
550
  };
388
551
 
@@ -416,6 +579,7 @@ export const validateLinks = async (
416
579
  anchors: buildAnchorIndex(graph.pages),
417
580
  basePath,
418
581
  extraRoutes: new Set((options.extraRoutes ?? []).map(toRoute)),
582
+ fileRoutes: buildFileRouteIndex(graph.pages, options.i18n ?? null),
419
583
  i18n: options.i18n ?? null,
420
584
  publicDir: options.publicDir,
421
585
  redirects: new Set(
@@ -82,9 +82,14 @@ const buildFallbackRoutes = (
82
82
  if (!fallback) {
83
83
  return [];
84
84
  }
85
+ // A monolingual page (a GitHub release) publishes in one language only, so it
86
+ // gets no copy at every other locale's URL: those would be duplicates of the
87
+ // same text under a translated chrome.
85
88
  const fallbackPages = new Map(
86
89
  graph.pages.flatMap((page) =>
87
- page.locale === fallback ? [[page.translationKey, page] as const] : []
90
+ page.locale === fallback && !page.monolingual
91
+ ? [[page.translationKey, page] as const]
92
+ : []
88
93
  )
89
94
  );
90
95
  const routes: RouteManifestEntry[] = [];
@@ -126,6 +131,7 @@ const buildFallbackRoutes = (
126
131
  indexable: false,
127
132
  lastModified: source.lastModified,
128
133
  locale: code,
134
+ monolingual: source.monolingual,
129
135
  path,
130
136
  source: source.source,
131
137
  sourcePath: source.sourcePath,
@@ -145,7 +151,7 @@ export const buildManifest = (options: {
145
151
  graph: ContentGraph;
146
152
  }): BlumeManifest => {
147
153
  const { context, config, graph } = options;
148
- const searchEnabled = config.search.provider !== "none";
154
+ const searchEnabled = config.search.provider.mode !== "none";
149
155
  const { i18n } = config;
150
156
 
151
157
  // Real translations per logical page, for `hreflang` and the switcher. Built
@@ -189,6 +195,7 @@ export const buildManifest = (options: {
189
195
  indexable: searchEnabled && contentIndexable(page, config),
190
196
  lastModified: page.lastModified,
191
197
  locale: page.locale,
198
+ monolingual: page.monolingual,
192
199
  path: page.route,
193
200
  source: page.source,
194
201
  sourcePath: page.sourcePath,
@@ -234,7 +241,7 @@ export const buildManifest = (options: {
234
241
  return {
235
242
  blumeVersion: getBlumeVersion(),
236
243
  contentRoot: context.contentRoot,
237
- output: config.deployment.output,
244
+ output: config.deployment.options.output,
238
245
  projectRoot: context.root,
239
246
  routes,
240
247
  version: MANIFEST_VERSION,
@@ -43,12 +43,66 @@ export const isUnderPath = (route: string, base: string): boolean =>
43
43
  export const isRootTab = (tab: NavTab, root: string): boolean =>
44
44
  isUnderPath(root, tab.path);
45
45
 
46
+ /**
47
+ * A page's sidebar icon: `sidebar.icon`, or the top-level `icon` shorthand
48
+ * (the Mintlify-style spelling) when the nested key is unset.
49
+ */
50
+ const sidebarIcon = (page: PageRecord): string | undefined =>
51
+ page.meta.sidebar.icon ?? page.meta.icon;
52
+
53
+ /**
54
+ * Words a folder name spells in lowercase that read wrong capitalized: the
55
+ * acronyms and brand casings docs folders are named after (`api-reference`
56
+ * is "API Reference", not "Api Reference").
57
+ */
58
+ const WORD_FORMS = new Map([
59
+ ["ai", "AI"],
60
+ ["api", "API"],
61
+ ["apis", "APIs"],
62
+ ["asyncapi", "AsyncAPI"],
63
+ ["cli", "CLI"],
64
+ ["css", "CSS"],
65
+ ["faq", "FAQ"],
66
+ ["faqs", "FAQs"],
67
+ ["graphql", "GraphQL"],
68
+ ["html", "HTML"],
69
+ ["http", "HTTP"],
70
+ ["https", "HTTPS"],
71
+ ["id", "ID"],
72
+ ["ids", "IDs"],
73
+ ["ios", "iOS"],
74
+ ["js", "JS"],
75
+ ["json", "JSON"],
76
+ ["jwt", "JWT"],
77
+ ["llm", "LLM"],
78
+ ["llms", "LLMs"],
79
+ ["macos", "macOS"],
80
+ ["mcp", "MCP"],
81
+ ["oauth", "OAuth"],
82
+ ["openapi", "OpenAPI"],
83
+ ["rss", "RSS"],
84
+ ["sdk", "SDK"],
85
+ ["sdks", "SDKs"],
86
+ ["seo", "SEO"],
87
+ ["sql", "SQL"],
88
+ ["sso", "SSO"],
89
+ ["ts", "TS"],
90
+ ["ui", "UI"],
91
+ ["url", "URL"],
92
+ ["urls", "URLs"],
93
+ ["xml", "XML"],
94
+ ["yaml", "YAML"],
95
+ ]);
96
+
46
97
  const humanize = (segment: string): string =>
47
98
  segment
48
99
  .replace(NUMERIC_PREFIX, "")
49
100
  .split(WORD_SPLIT)
50
101
  .filter(Boolean)
51
- .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
102
+ .map(
103
+ (word) =>
104
+ WORD_FORMS.get(word) ?? word.charAt(0).toUpperCase() + word.slice(1)
105
+ )
52
106
  .join(" ");
53
107
 
54
108
  const numericOrder = (segment: string): number => {
@@ -276,6 +330,12 @@ const applyFolderMeta = (
276
330
  * update the folder's `meta.ts` and forget the index page's own frontmatter
277
331
  * (or vice versa), and a correct-looking sidebar hides the mismatch.
278
332
  *
333
+ * Only fires when the index page's own sidebar row is hidden
334
+ * (`sidebar.hidden`), so the linked group header is the only sidebar label the
335
+ * page has. A visible index row shows the page's own label beneath the header,
336
+ * so the sidebar already carries both titles and a divergence there is a
337
+ * deliberate pairing ("CLI" over "Overview"), not drift.
338
+ *
279
339
  * Only fires when the page has an explicit frontmatter `title` of its own:
280
340
  * when it's absent, `page.title` is derived from the first heading or the
281
341
  * filename, so it almost never coincidentally matches a custom folder title —
@@ -298,7 +358,11 @@ const indexTitleMismatchDiagnostic = (
298
358
  metaPrefix: string,
299
359
  sharedMetaPrefix: string
300
360
  ): Diagnostic | undefined => {
301
- if (!page.meta.title || page.fallback || folderPath === "") {
361
+ if (
362
+ !(page.meta.title && page.meta.sidebar.hidden) ||
363
+ page.fallback ||
364
+ folderPath === ""
365
+ ) {
302
366
  return undefined;
303
367
  }
304
368
  const meta =
@@ -552,8 +616,9 @@ const buildFileSystemSidebar = (
552
616
  const stem = stemOf(filename);
553
617
  const dirs = parts.slice(0, -1);
554
618
 
555
- // Checked before the hidden filter: a sidebar-hidden index page still
556
- // renders with its own <title>, so title drift matters there just the same.
619
+ // Checked before the hidden filter: the title check fires only for a
620
+ // sidebar-hidden index page, whose own <title> the header label stands in
621
+ // for, so it must see the pages the filter drops.
557
622
  if (isIndexStem(stem)) {
558
623
  const diagnostic = indexTitleMismatchDiagnostic(
559
624
  page,
@@ -612,7 +677,7 @@ const buildFileSystemSidebar = (
612
677
  deprecated: page.meta.deprecated || undefined,
613
678
  description: page.description,
614
679
  file: page.sourcePath,
615
- icon: page.meta.sidebar.icon,
680
+ icon: sidebarIcon(page),
616
681
  key: segmentKey(stem),
617
682
  kind: "page",
618
683
  label: page.meta.sidebar.label ?? page.title,
@@ -691,7 +756,7 @@ const configItemToNode = (
691
756
  badge: page.meta.sidebar.badge,
692
757
  deprecated: page.meta.deprecated || undefined,
693
758
  description: page.description,
694
- icon: page.meta.sidebar.icon,
759
+ icon: sidebarIcon(page),
695
760
  kind: "page",
696
761
  label: page.meta.sidebar.label ?? page.title,
697
762
  pageId: page.id,
@@ -760,10 +825,26 @@ const buildConfigSidebar = (
760
825
  /**
761
826
  * Resolve a tab's clickable target. A tab's `path` scopes its sidebar section
762
827
  * but need not be a real route — a section with no index page would 404 if the
763
- * tab linked straight to it. Prefer an exact page/group at the path; otherwise
764
- * fall back to the first linkable route in the section (sidebar order).
828
+ * tab linked straight to it. Prefer the path itself when it's served outside
829
+ * the content tree (the generated changelog index) or is a page/group in the
830
+ * tree; otherwise fall back to the first linkable route in the section
831
+ * (sidebar order).
832
+ *
833
+ * Routes outside the content tree are served at their own path, not under
834
+ * `basePath` (`pages/changelog.astro` answers `/changelog` on a `/docs`-based
835
+ * site), while tab paths arrive based (`/docs/changelog`). So when the based
836
+ * path is neither an outside route nor a tree node, the base-less path is
837
+ * checked against the outside routes before the section's first page wins.
765
838
  */
766
- const resolveTabHref = (sidebar: NavNode[], path: string): string => {
839
+ const resolveTabHref = (
840
+ sidebar: NavNode[],
841
+ path: string,
842
+ extraRoutes: ReadonlySet<string>,
843
+ basePath: string
844
+ ): string => {
845
+ if (extraRoutes.has(path)) {
846
+ return path;
847
+ }
767
848
  let first: string | undefined;
768
849
  const walk = (nodes: NavNode[]): boolean => {
769
850
  for (const node of nodes) {
@@ -784,19 +865,30 @@ const resolveTabHref = (sidebar: NavNode[], path: string): string => {
784
865
  }
785
866
  return false;
786
867
  };
787
- return walk(sidebar) ? path : (first ?? path);
868
+ if (walk(sidebar)) {
869
+ return path;
870
+ }
871
+ const bare = stripBasePath(basePath, path);
872
+ return extraRoutes.has(bare) ? bare : (first ?? path);
788
873
  };
789
874
 
790
875
  /**
791
876
  * Attach a resolved `href` to each tab whose section has no index page. An
792
877
  * author-declared `href` is the tab's stated target, so it's kept as-is —
793
878
  * resolution only fills in the tabs that didn't declare one. That's what lets a
794
- * tab point at a route outside the content tree (a generated `/changelog`
795
- * index, a custom `.astro` page), which resolution can't see.
879
+ * tab point at a route outside the content tree that resolution can't see (a
880
+ * custom `.astro` page); the generated changelog index arrives in
881
+ * `extraRoutes`, so a `/changelog` tab needs no `href`.
796
882
  */
797
- const withTabHrefs = (tabs: NavTab[], sidebar: NavNode[]): NavTab[] =>
883
+ const withTabHrefs = (
884
+ tabs: NavTab[],
885
+ sidebar: NavNode[],
886
+ extraRoutes: ReadonlySet<string>,
887
+ basePath: string
888
+ ): NavTab[] =>
798
889
  tabs.map((tab) => {
799
- const href = tab.href ?? resolveTabHref(sidebar, tab.path);
890
+ const href =
891
+ tab.href ?? resolveTabHref(sidebar, tab.path, extraRoutes, basePath);
800
892
  return href === tab.path ? tab : { ...tab, href };
801
893
  });
802
894
 
@@ -913,6 +1005,12 @@ export const buildNavigation = (
913
1005
  * into in place; omit to discard.
914
1006
  */
915
1007
  diagnostics?: Diagnostic[];
1008
+ /**
1009
+ * Routes served outside the content tree that a tab `path` may name
1010
+ * directly (the generated changelog index), so the tab links there instead
1011
+ * of falling back to its section's first page.
1012
+ */
1013
+ extraRoutes?: ReadonlySet<string>;
916
1014
  }
917
1015
  ): Navigation => {
918
1016
  const basePath = options.basePath ?? "";
@@ -920,6 +1018,7 @@ export const buildNavigation = (
920
1018
  const metaPrefix = options.metaPrefix ?? "";
921
1019
  const sharedMetaPrefix = options.sharedMetaPrefix ?? "";
922
1020
  const sharedFolderMeta = options.sharedFolderMeta ?? new Map();
1021
+ const extraRoutes = options.extraRoutes ?? new Set<string>();
923
1022
 
924
1023
  // Content-derived sidebar routes are already based via `page.route`; the
925
1024
  // based tab paths also feed tab-scoping below, so they must agree with the
@@ -981,7 +1080,7 @@ export const buildNavigation = (
981
1080
  root: rootTabPath,
982
1081
  selectors,
983
1082
  sidebar,
984
- tabs: withTabHrefs(tabs, sidebar),
1083
+ tabs: withTabHrefs(tabs, sidebar, extraRoutes, basePath),
985
1084
  };
986
1085
  }
987
1086
 
@@ -1004,6 +1103,6 @@ export const buildNavigation = (
1004
1103
  root: rootTabPath,
1005
1104
  selectors,
1006
1105
  sidebar,
1007
- tabs: withTabHrefs(tabs, sidebar),
1106
+ tabs: withTabHrefs(tabs, sidebar, extraRoutes, basePath),
1008
1107
  };
1009
1108
  };
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Links that open in a new tab. Every such link Blume renders — chrome (header
3
+ * actions, featured sidebar links, page actions), content components (Card,
4
+ * Tile, Tooltip), and Markdown links under `markdown.externalLinks` — points
5
+ * `aria-describedby` at one hidden, localized "Opens in a new tab" element each
6
+ * layout renders once per page, so screen-reader users hear the same warning
7
+ * sighted readers get from the arrow icon or the new tab itself.
8
+ */
9
+
10
+ import { isExternalUrl } from "./base-path.ts";
11
+
12
+ /** The id of the layout's hidden "Opens in a new tab" description. */
13
+ export const NEW_TAB_HINT_ID = "blume-new-tab-hint";
14
+
15
+ /** The attributes a new-tab link carries (spread onto an `<a>`). */
16
+ export interface NewTabAttrs {
17
+ "aria-describedby"?: string;
18
+ rel?: string;
19
+ target?: string;
20
+ }
21
+
22
+ /** A link that always opens in a new tab (a repo link, an edit link). */
23
+ export const NEW_TAB_ATTRS: NewTabAttrs = {
24
+ "aria-describedby": NEW_TAB_HINT_ID,
25
+ rel: "noreferrer",
26
+ target: "_blank",
27
+ };
28
+
29
+ /**
30
+ * {@link NEW_TAB_ATTRS} for an external href (the shared `isExternalUrl`
31
+ * predicate), and nothing for a site route, fragment, or `mailto:` link, which
32
+ * stay in the current tab.
33
+ */
34
+ export const newTabAttrs = (href?: string): NewTabAttrs =>
35
+ href !== undefined && isExternalUrl(href) ? NEW_TAB_ATTRS : {};
@@ -0,0 +1,21 @@
1
+ import { createRequire } from "node:module";
2
+
3
+ /**
4
+ * Node's own `require`, for the SDKs and heavy modules Blume loads only when a
5
+ * feature needs them. Resolution starts from this file, inside the installed
6
+ * package, exactly like the `import()` it stands in for, and Node 22.12+ loads
7
+ * ES modules through it too.
8
+ *
9
+ * Never `import()` in their place. When Node can't import the generated Astro
10
+ * config itself — it refuses to strip types from TypeScript under
11
+ * `node_modules`, which is where an installed Blume's runtime source lives —
12
+ * Astro evaluates the config, and every Blume module it reaches, in a Vite
13
+ * module runner that it closes as soon as the config has loaded. A dynamic
14
+ * `import()` in one of those modules resolves through that runner, so a call
15
+ * made any later (an `astro:build:done` hook, a Markdown plugin) throws "Vite
16
+ * module runner has been closed". `require` is Node's, so it works however the
17
+ * calling module was evaluated. The repo itself never sees the failure: its
18
+ * workspace link puts Blume outside `node_modules`, so Node imports the config
19
+ * natively.
20
+ */
21
+ export const nodeRequire = createRequire(import.meta.url);
@@ -1,7 +1,8 @@
1
1
  import { isAbsolute, relative } from "pathe";
2
2
 
3
+ import { CHANGELOG_INDEX_ROUTE, hasChangelogIndex } from "./changelog-index.ts";
3
4
  import { loadConfig } from "./config.ts";
4
- import { applyDeploymentEnv } from "./deployment-env.ts";
5
+ import { customStaticRoutes, discoverPages } from "./custom-pages.ts";
5
6
  import { buildContentGraph } from "./graph.ts";
6
7
  import { i18nDiagnostics } from "./i18n.ts";
7
8
  import { expandIncludes, hasIncludeStatements } from "./includes.ts";
@@ -36,12 +37,6 @@ export type BuildMode = "dev" | "build";
36
37
  export interface ConfigOverrides {
37
38
  /** Override `content.root` (`blume dev --content-dir`). */
38
39
  contentRoot?: string;
39
- /** Override `deployment.adapter` (`blume build --adapter`). */
40
- adapter?: ResolvedConfig["deployment"]["adapter"];
41
- /** Override `deployment.base` (`blume build --base`). */
42
- base?: string;
43
- /** Override `deployment.output` (`blume build --output`). */
44
- output?: ResolvedConfig["deployment"]["output"];
45
40
  }
46
41
 
47
42
  /**
@@ -61,18 +56,20 @@ const applyConfigOverrides = (
61
56
  if (!overrides) {
62
57
  return config;
63
58
  }
59
+ // `--content-dir` re-roots every filesystem source: with one (the
60
+ // zero-config case) it is exactly the old `content.root` override, and
61
+ // several must share a root anyway (see `resolveDocsCollection`).
62
+ const { contentRoot } = overrides;
63
+ const sources = contentRoot
64
+ ? config.content.sources.map((source) =>
65
+ source.kind === "filesystem"
66
+ ? { ...source, options: { ...source.options, root: contentRoot } }
67
+ : source
68
+ )
69
+ : config.content.sources;
64
70
  return {
65
71
  ...config,
66
- content: {
67
- ...config.content,
68
- root: overrides.contentRoot ?? config.content.root,
69
- },
70
- deployment: {
71
- ...config.deployment,
72
- adapter: overrides.adapter ?? config.deployment.adapter,
73
- base: overrides.base ?? config.deployment.base,
74
- output: overrides.output ?? config.deployment.output,
75
- },
72
+ content: { ...config.content, sources },
76
73
  };
77
74
  };
78
75
 
@@ -138,7 +135,7 @@ const entryIdDiagnostics = (
138
135
  message: `Content source "${page.source.name}" is rooted outside the docs collection base, so ${page.route} resolves entry id "${entryId}" but the collection would generate "${expected}" — the page would 404 at runtime.`,
139
136
  severity: "error",
140
137
  suggestion:
141
- "Use a single filesystem source (the docs collection roots at it), or root every filesystem source at content.root and partition them with include globs — a root at a subdirectory of content.root still mismatches.",
138
+ "Use a single filesystem() source (the docs collection roots at it), or give every filesystem() source the same root and partition them with include globs — a root at a subdirectory of the first source's root still mismatches.",
142
139
  });
143
140
  }
144
141
  }
@@ -185,6 +182,7 @@ const normalizeLoadedEntries = (
185
182
  frontmatterExtend,
186
183
  i18n: config.i18n,
187
184
  source: {
185
+ monolingual: source.monolingual,
188
186
  name: source.name,
189
187
  prefix: source.prefix,
190
188
  staged: source.staged,
@@ -208,6 +206,15 @@ const normalizeLoadedEntries = (
208
206
  * Collects all diagnostics without throwing on content-level problems so
209
207
  * callers can decide how strict to be.
210
208
  */
209
+ /** Narrows the logo config's image shorthand from its object form. */
210
+ const isLogoShorthand = (
211
+ logo: NonNullable<ResolvedConfig["logo"]>
212
+ ): logo is string => typeof logo === "string";
213
+
214
+ /** The configured brand link: `logo.href`, else the site root. */
215
+ const logoHref = (logo: ResolvedConfig["logo"]): string =>
216
+ logo === undefined || isLogoShorthand(logo) ? "/" : (logo.href ?? "/");
217
+
211
218
  export const scanProject = async (
212
219
  root: string,
213
220
  options: {
@@ -226,13 +233,7 @@ export const scanProject = async (
226
233
  const configResult = await loadConfig(root, {
227
234
  devServerUrl: options.devServerUrl,
228
235
  });
229
- // Re-run platform detection after CLI overrides: `loadConfig` already ran it,
230
- // but adapter inference keys off `deployment.output`, which `--output server`
231
- // only sets here. Idempotent for already-resolved fields — `deployment.site`
232
- // keeps loadConfig's explicit > platform env > devServerUrl precedence.
233
- const config = applyDeploymentEnv(
234
- applyConfigOverrides(configResult.config, options.overrides)
235
- );
236
+ const config = applyConfigOverrides(configResult.config, options.overrides);
236
237
  const context = resolveProjectContext(root, config, {
237
238
  runtimeDir: options.runtimeDir,
238
239
  });
@@ -373,8 +374,20 @@ export const scanProject = async (
373
374
  );
374
375
  }
375
376
 
377
+ // Custom `.astro` pages and the generated changelog index aren't content
378
+ // pages, so name them for navigation: a `/changelog` tab should open the
379
+ // timeline, not the newest entry, and a tab or brand link to a custom page
380
+ // must stay on its route in every locale.
381
+ const customPages = context.pagesRoot
382
+ ? await discoverPages(context.pagesRoot)
383
+ : [];
376
384
  const graph = buildContentGraph(pages, {
377
385
  basePath: config.basePath,
386
+ brandHref: logoHref(config.logo),
387
+ extraRoutes: new Set([
388
+ ...customStaticRoutes(customPages),
389
+ ...(hasChangelogIndex(pages, config) ? [CHANGELOG_INDEX_ROUTE] : []),
390
+ ]),
378
391
  folderMeta: folderMeta.meta,
379
392
  i18n: config.i18n,
380
393
  navigation: config.navigation,
@@ -397,7 +410,10 @@ export const scanProject = async (
397
410
  ...contentDiagnostics,
398
411
  ...includeDiagnostics,
399
412
  ...folderMeta.diagnostics,
400
- ...entryIdDiagnostics(pages, resolveDocsCollection(config, context).base),
413
+ ...entryIdDiagnostics(
414
+ pages,
415
+ resolveDocsCollection(config, context.root).base
416
+ ),
401
417
  ...graph.diagnostics,
402
418
  ...i18nWarnings,
403
419
  ...versionWarnings,