blume 2.1.3 → 2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (397) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +134 -0
  3. package/README.md +4 -4
  4. package/dist/cli/{chunk-y4msrwm9.js → chunk-080mfch6.js} +15 -15
  5. package/dist/cli/{chunk-gfc5fqvf.js → chunk-1ch3ct3r.js} +18 -18
  6. package/dist/cli/{chunk-gfc5fqvf.js.map → chunk-1ch3ct3r.js.map} +1 -1
  7. package/dist/cli/{chunk-32khwcb6.js → chunk-2ssh60qh.js} +42 -69
  8. package/dist/cli/{chunk-32khwcb6.js.map → chunk-2ssh60qh.js.map} +9 -10
  9. package/dist/cli/{chunk-9qzgg6mj.js → chunk-34wap3ed.js} +3 -3
  10. package/dist/cli/{chunk-k3gr1nm9.js → chunk-40vk8kgf.js} +13 -16
  11. package/dist/cli/{chunk-k3gr1nm9.js.map → chunk-40vk8kgf.js.map} +3 -3
  12. package/dist/cli/chunk-487b95qy.js +329 -0
  13. package/dist/cli/chunk-487b95qy.js.map +13 -0
  14. package/dist/cli/chunk-4zxh25hc.js +157 -0
  15. package/dist/cli/chunk-4zxh25hc.js.map +10 -0
  16. package/dist/cli/chunk-69rjf786.js +146 -0
  17. package/dist/cli/chunk-69rjf786.js.map +11 -0
  18. package/dist/cli/{chunk-dqhg54mr.js → chunk-9h8mh2tk.js} +3 -3
  19. package/dist/cli/{chunk-dqhg54mr.js.map → chunk-9h8mh2tk.js.map} +2 -2
  20. package/dist/cli/{chunk-bqxaqn5p.js → chunk-9jqmq5hv.js} +8 -8
  21. package/dist/cli/{chunk-bqxaqn5p.js.map → chunk-9jqmq5hv.js.map} +1 -1
  22. package/dist/cli/{chunk-dv2z0rzf.js → chunk-aafhwx74.js} +38 -13
  23. package/dist/cli/{chunk-dv2z0rzf.js.map → chunk-aafhwx74.js.map} +5 -4
  24. package/dist/cli/{chunk-desvm72z.js → chunk-b680sv30.js} +22 -26
  25. package/dist/cli/chunk-b680sv30.js.map +13 -0
  26. package/dist/cli/{chunk-0w6hs479.js → chunk-beb0z983.js} +30 -18
  27. package/dist/cli/chunk-beb0z983.js.map +10 -0
  28. package/dist/cli/{chunk-1f882900.js → chunk-c5bjxq8v.js} +2 -2
  29. package/dist/cli/chunk-e1n41w9m.js +155 -0
  30. package/dist/cli/chunk-e1n41w9m.js.map +11 -0
  31. package/dist/cli/{chunk-sc93apfs.js → chunk-etckvbrh.js} +17 -17
  32. package/dist/cli/{chunk-sc93apfs.js.map → chunk-etckvbrh.js.map} +2 -2
  33. package/dist/cli/{chunk-bxwhtvw8.js → chunk-f260jdrn.js} +5 -5
  34. package/dist/cli/{chunk-74wfhv79.js → chunk-fnxgtjeq.js} +1 -1
  35. package/dist/cli/{chunk-ms9z66nz.js → chunk-gwy55cx3.js} +2 -2
  36. package/dist/cli/{chunk-ms9z66nz.js.map → chunk-gwy55cx3.js.map} +2 -2
  37. package/dist/cli/{chunk-dhwtqnnd.js → chunk-gxhcf2hs.js} +2 -2
  38. package/dist/cli/{chunk-tfycddbt.js → chunk-h0qwdbem.js} +2 -2
  39. package/dist/cli/{chunk-nbyvgnnm.js → chunk-hktq4j5r.js} +109 -49
  40. package/dist/cli/chunk-hktq4j5r.js.map +36 -0
  41. package/dist/cli/{chunk-twy7psdz.js → chunk-hn3g2b89.js} +14 -10
  42. package/dist/cli/chunk-hn3g2b89.js.map +10 -0
  43. package/dist/cli/{chunk-j1x92mx9.js → chunk-j4nfgn2j.js} +1 -1
  44. package/dist/cli/{chunk-dpy8qdjf.js → chunk-k45sq16q.js} +74 -183
  45. package/dist/cli/chunk-k45sq16q.js.map +18 -0
  46. package/dist/cli/{chunk-5cmts6dv.js → chunk-kmswjxj9.js} +7 -7
  47. package/dist/cli/{chunk-5cmts6dv.js.map → chunk-kmswjxj9.js.map} +1 -1
  48. package/dist/cli/{chunk-36bxs9c7.js → chunk-nqna8dzp.js} +27 -23
  49. package/dist/cli/chunk-nqna8dzp.js.map +10 -0
  50. package/dist/cli/{chunk-a57yh0h9.js → chunk-p834rkcn.js} +7 -7
  51. package/dist/cli/{chunk-a57yh0h9.js.map → chunk-p834rkcn.js.map} +1 -1
  52. package/dist/cli/{chunk-60hz265r.js → chunk-ppa6mg4d.js} +24 -11
  53. package/dist/cli/chunk-ppa6mg4d.js.map +10 -0
  54. package/dist/cli/{chunk-s0bdnc7v.js → chunk-q1wwrxzw.js} +5 -4
  55. package/dist/cli/chunk-q1wwrxzw.js.map +10 -0
  56. package/dist/cli/{chunk-ave1wcbr.js → chunk-qcyxkj44.js} +1 -1
  57. package/dist/cli/{chunk-124fvmzq.js → chunk-r5nm9w0c.js} +11183 -8806
  58. package/dist/cli/{chunk-124fvmzq.js.map → chunk-r5nm9w0c.js.map} +82 -66
  59. package/dist/cli/{chunk-ckrcm08h.js → chunk-rtvgmg2j.js} +48 -62
  60. package/dist/cli/{chunk-ckrcm08h.js.map → chunk-rtvgmg2j.js.map} +4 -5
  61. package/dist/cli/{chunk-297ajdcv.js → chunk-t2h8pwy1.js} +6 -6
  62. package/dist/cli/{chunk-297ajdcv.js.map → chunk-t2h8pwy1.js.map} +3 -3
  63. package/dist/cli/{chunk-gxtg6ed2.js → chunk-x60e15st.js} +21 -21
  64. package/dist/cli/{chunk-gxtg6ed2.js.map → chunk-x60e15st.js.map} +3 -3
  65. package/dist/cli/{chunk-58bt3hag.js → chunk-x7bh6d9j.js} +4 -4
  66. package/dist/cli/{chunk-hvcxng5f.js → chunk-ydqzkhjn.js} +6 -6
  67. package/dist/cli/{chunk-kdpzw38a.js → chunk-yg8ckc3g.js} +95 -7
  68. package/dist/cli/chunk-yg8ckc3g.js.map +11 -0
  69. package/dist/cli/{chunk-0pmb4tr4.js → chunk-zxd5jrgx.js} +1205 -222
  70. package/dist/cli/chunk-zxd5jrgx.js.map +44 -0
  71. package/dist/cli/index.js +78 -42
  72. package/dist/cli/index.js.map +7 -6
  73. package/dist/types/ai/changelog-components.d.ts +18 -0
  74. package/dist/types/ai/changelog-markdown.d.ts +12 -0
  75. package/dist/types/ai/openapi-components.d.ts +7 -7
  76. package/dist/types/ai/operation-details.d.ts +39 -0
  77. package/dist/types/ai/relative-links.d.ts +3 -1
  78. package/dist/types/ai/serializers.d.ts +6 -4
  79. package/dist/types/analytics/head.d.ts +4 -2
  80. package/dist/types/analytics/script.d.ts +8 -2
  81. package/dist/types/components/content/youtube.d.ts +20 -0
  82. package/dist/types/components/layout/nav-utils.d.ts +8 -0
  83. package/dist/types/components/openapi/example-panels.d.ts +33 -0
  84. package/dist/types/components/openapi/helpers.d.ts +53 -11
  85. package/dist/types/components/openapi/snippets.d.ts +7 -1
  86. package/dist/types/core/alert-diagnostics.d.ts +10 -0
  87. package/dist/types/core/builtin-tags.d.ts +16 -0
  88. package/dist/types/core/changelog-list.d.ts +48 -0
  89. package/dist/types/core/closest-match.d.ts +6 -0
  90. package/dist/types/core/code-fence-diagnostics.d.ts +18 -0
  91. package/dist/types/core/code-fences.d.ts +8 -1
  92. package/dist/types/core/config-input.d.ts +38 -6
  93. package/dist/types/core/content-assets.d.ts +16 -18
  94. package/dist/types/core/data.d.ts +6 -2
  95. package/dist/types/core/diagnostics.d.ts +14 -0
  96. package/dist/types/core/directive-diagnostics.d.ts +22 -8
  97. package/dist/types/core/former-routes.d.ts +14 -0
  98. package/dist/types/core/github.d.ts +5 -4
  99. package/dist/types/core/heading-markers.d.ts +65 -2
  100. package/dist/types/core/hidden-pages.d.ts +3 -0
  101. package/dist/types/core/includes.d.ts +23 -0
  102. package/dist/types/core/last-modified.d.ts +18 -6
  103. package/dist/types/core/links.d.ts +42 -2
  104. package/dist/types/core/load-module.d.ts +19 -4
  105. package/dist/types/core/manifest.d.ts +14 -1
  106. package/dist/types/core/markdown-mirrors.d.ts +22 -0
  107. package/dist/types/core/mcp-clients.d.ts +47 -0
  108. package/dist/types/core/mdx-elements.d.ts +32 -0
  109. package/dist/types/core/mdx-syntax.d.ts +100 -0
  110. package/dist/types/core/meta-diagnostics.d.ts +7 -0
  111. package/dist/types/core/meta.d.ts +32 -5
  112. package/dist/types/core/nav-diagnostics.d.ts +16 -2
  113. package/dist/types/core/navigation.d.ts +13 -0
  114. package/dist/types/core/ordering-prefix.d.ts +6 -0
  115. package/dist/types/core/pinned-ids.d.ts +23 -0
  116. package/dist/types/core/project-graph.d.ts +9 -1
  117. package/dist/types/core/redirect-patterns.d.ts +18 -0
  118. package/dist/types/core/relative-files.d.ts +31 -0
  119. package/dist/types/core/schema.d.ts +322 -11
  120. package/dist/types/core/sources/normalize.d.ts +101 -9
  121. package/dist/types/core/sources/types.d.ts +16 -0
  122. package/dist/types/core/static-files.d.ts +10 -0
  123. package/dist/types/core/static-host.d.ts +42 -0
  124. package/dist/types/core/svg-images.d.ts +26 -0
  125. package/dist/types/core/syntax-diagnostics.d.ts +14 -0
  126. package/dist/types/core/types.d.ts +42 -1
  127. package/dist/types/deploy/generated-files.d.ts +41 -0
  128. package/dist/types/deploy/redirects.d.ts +21 -7
  129. package/dist/types/markdown/code-title.d.ts +9 -5
  130. package/dist/types/markdown/directives.d.ts +8 -0
  131. package/dist/types/markdown/fence-language.d.ts +51 -0
  132. package/dist/types/markdown/fence-meta.d.ts +5 -0
  133. package/dist/types/markdown/github-alerts.d.ts +35 -0
  134. package/dist/types/openapi/asyncapi.d.ts +6 -5
  135. package/dist/types/openapi/checks.d.ts +10 -5
  136. package/dist/types/openapi/model.d.ts +55 -8
  137. package/dist/types/openapi/parse.d.ts +4 -1
  138. package/dist/types/ratelimit/index.d.ts +2 -0
  139. package/dist/types/ratelimit/memory.d.ts +1 -1
  140. package/dist/types/ratelimit/schema.d.ts +40 -0
  141. package/dist/types/ratelimit/unkey.d.ts +48 -0
  142. package/dist/types/sources/filesystem.d.ts +14 -4
  143. package/dist/types/sources/mdx-remote.d.ts +6 -3
  144. package/dist/types/sources/registry.d.ts +3 -3
  145. package/dist/types/theme/color.d.ts +33 -0
  146. package/dist/types/theme/palette.d.ts +122 -0
  147. package/docs/01-quickstart.mdx +1 -1
  148. package/docs/02-deployment.mdx +24 -8
  149. package/docs/04-migrating.mdx +16 -4
  150. package/docs/advanced/changelog.mdx +27 -1
  151. package/docs/advanced/skills.mdx +1 -1
  152. package/docs/cli/audit.mdx +1 -1
  153. package/docs/cli/doctor.mdx +2 -2
  154. package/docs/cli/evals.mdx +1 -1
  155. package/docs/cli/index.mdx +4 -2
  156. package/docs/cli/validate.mdx +10 -7
  157. package/docs/configuration/analytics.mdx +2 -0
  158. package/docs/configuration/customization.mdx +3 -1
  159. package/docs/configuration/index.mdx +7 -6
  160. package/docs/configuration/rate-limiting.mdx +20 -1
  161. package/docs/configuration/theming.mdx +5 -1
  162. package/docs/content/components.mdx +26 -6
  163. package/docs/content/frontmatter.mdx +1 -1
  164. package/docs/content/i18n.mdx +1 -1
  165. package/docs/content/includes.mdx +6 -2
  166. package/docs/content/index.mdx +11 -1
  167. package/docs/content/islands.mdx +2 -0
  168. package/docs/content/meta.mdx +9 -3
  169. package/docs/content/navigation.mdx +14 -6
  170. package/docs/content/sources/obsidian.mdx +1 -1
  171. package/docs/content/sources/remote-mdx.mdx +3 -1
  172. package/docs/content/syntax.mdx +76 -8
  173. package/docs/content/variables.mdx +3 -1
  174. package/docs/discoverability/agent-discovery.mdx +1 -1
  175. package/docs/discoverability/json-api.mdx +1 -1
  176. package/docs/discoverability/llms-txt.mdx +1 -1
  177. package/docs/discoverability/markdown.mdx +2 -2
  178. package/docs/discoverability/mcp.mdx +40 -8
  179. package/docs/discoverability/open-graph.mdx +1 -1
  180. package/docs/discoverability/sitemap-and-robots.mdx +1 -1
  181. package/docs/references/graphql.mdx +2 -0
  182. package/docs/references/openapi.mdx +45 -7
  183. package/package.json +4 -4
  184. package/skills/blume/SKILL.md +2 -2
  185. package/skills/blume-migrate/SKILL.md +51 -29
  186. package/skills/blume-migrate/references/docsify.md +262 -0
  187. package/skills/blume-migrate/references/docus.md +298 -0
  188. package/skills/blume-migrate/references/docusaurus.md +5 -5
  189. package/skills/blume-migrate/references/fern.md +327 -0
  190. package/skills/blume-migrate/references/fumadocs.md +1 -1
  191. package/skills/blume-migrate/references/gitbook.md +267 -0
  192. package/skills/blume-migrate/references/github-wiki.md +259 -0
  193. package/skills/blume-migrate/references/jekyll.md +253 -0
  194. package/skills/blume-migrate/references/mdbook.md +256 -0
  195. package/skills/blume-migrate/references/mintlify.md +5 -5
  196. package/skills/blume-migrate/references/mkdocs.md +236 -0
  197. package/skills/blume-migrate/references/monorepo.md +20 -8
  198. package/skills/blume-migrate/references/nextra.md +5 -5
  199. package/skills/blume-migrate/references/readme.md +257 -0
  200. package/skills/blume-migrate/references/redocly.md +198 -0
  201. package/skills/blume-migrate/references/starlight.md +3 -3
  202. package/skills/blume-migrate/references/vitepress.md +204 -0
  203. package/skills/blume-migrate/references/vuepress.md +271 -0
  204. package/skills/blume-migrate/scripts/docsify-codemod.mjs +2387 -0
  205. package/skills/blume-migrate/scripts/docus-codemod.mjs +2542 -0
  206. package/skills/blume-migrate/scripts/fern-codemod.mjs +2633 -0
  207. package/skills/blume-migrate/scripts/github-wiki-codemod.mjs +2808 -0
  208. package/skills/blume-migrate/scripts/include-excerpts.mjs +649 -0
  209. package/skills/blume-migrate/scripts/jekyll-codemod.mjs +3649 -0
  210. package/skills/blume-migrate/scripts/mintlify-codemod.mjs +17 -13
  211. package/skills/blume-migrate/scripts/mkdocs-codemod.mjs +2526 -0
  212. package/skills/blume-migrate/scripts/operation-routes.mjs +508 -0
  213. package/skills/blume-migrate/scripts/pin-heading-ids.mjs +1049 -0
  214. package/skills/blume-migrate/scripts/readme-codemod.mjs +4139 -0
  215. package/skills/blume-migrate/scripts/vitepress-codemod.mjs +1511 -0
  216. package/src/ai/changelog-components.ts +24 -0
  217. package/src/ai/changelog-markdown.ts +45 -41
  218. package/src/ai/component-markdown.ts +22 -9
  219. package/src/ai/llms.ts +36 -5
  220. package/src/ai/markdown.ts +2 -2
  221. package/src/ai/openapi-components.ts +25 -9
  222. package/src/ai/operation-details.ts +208 -0
  223. package/src/ai/relative-links.ts +48 -8
  224. package/src/ai/serializers.ts +7 -6
  225. package/src/ai/site-skill.ts +6 -2
  226. package/src/analytics/head.ts +11 -5
  227. package/src/analytics/script.ts +13 -3
  228. package/src/astro/generate.ts +47 -11
  229. package/src/astro/index.ts +1 -0
  230. package/src/astro/integration.ts +27 -1
  231. package/src/astro/mdx-source-errors.ts +46 -0
  232. package/src/astro/templates.ts +93 -189
  233. package/src/audit/checks/redirects.ts +5 -1
  234. package/src/audit/redirects.ts +36 -19
  235. package/src/audit/report.ts +22 -9
  236. package/src/audit/run.ts +12 -28
  237. package/src/audit/terms.ts +23 -0
  238. package/src/audit/url.ts +2 -2
  239. package/src/cli/build-failure.ts +256 -7
  240. package/src/cli/command-meta.ts +1 -1
  241. package/src/cli/commands/audit.ts +1 -1
  242. package/src/cli/commands/build.ts +12 -3
  243. package/src/cli/commands/check.ts +13 -2
  244. package/src/cli/commands/dev.ts +1 -1
  245. package/src/cli/commands/doctor.ts +4 -2
  246. package/src/cli/commands/init.ts +3 -3
  247. package/src/cli/commands/preview.ts +17 -1
  248. package/src/cli/commands/validate.ts +22 -10
  249. package/src/cli/prepare.ts +1 -1
  250. package/src/cli/preview-pages.ts +161 -0
  251. package/src/cli/required-secrets.ts +0 -1
  252. package/src/cli/unknown-flags.ts +1 -42
  253. package/src/components/code-copy.ts +51 -0
  254. package/src/components/code-language.ts +75 -0
  255. package/src/components/content/AccordionItem.astro +16 -1
  256. package/src/components/content/Callout.astro +11 -1
  257. package/src/components/content/Changelog.astro +189 -0
  258. package/src/components/content/CodeBlock.astro +8 -11
  259. package/src/components/content/Tabs.astro +16 -2
  260. package/src/components/content/YouTube.astro +6 -4
  261. package/src/components/content/code-block.ts +33 -0
  262. package/src/components/content/content-strings.ts +5 -1
  263. package/src/components/content/inline-markdown.ts +35 -8
  264. package/src/components/content/youtube.ts +49 -7
  265. package/src/components/layout/Analytics.astro +5 -1
  266. package/src/components/layout/NavTree.astro +8 -3
  267. package/src/components/layout/PageActions.astro +81 -23
  268. package/src/components/layout/RootLayout.astro +35 -32
  269. package/src/components/layout/SiteFooter.astro +31 -8
  270. package/src/components/layout/nav-utils.ts +14 -0
  271. package/src/components/layout/search/pagefind.ts +2 -1
  272. package/src/components/openapi/Operation.astro +4 -0
  273. package/src/components/openapi/PanelTabs.astro +9 -3
  274. package/src/components/openapi/RequestBody.astro +42 -8
  275. package/src/components/openapi/RequestPanel.astro +34 -53
  276. package/src/components/openapi/example-panels.ts +110 -0
  277. package/src/components/openapi/helpers.ts +195 -20
  278. package/src/components/openapi/operation-model.ts +63 -26
  279. package/src/components/openapi/request.ts +13 -0
  280. package/src/components/openapi/snippets.ts +14 -2
  281. package/src/core/alert-diagnostics.ts +89 -0
  282. package/src/core/builtin-tags.ts +76 -0
  283. package/src/core/changelog-list.ts +87 -0
  284. package/src/core/closest-match.ts +41 -0
  285. package/src/core/code-fence-diagnostics.ts +267 -0
  286. package/src/core/code-fences.ts +43 -7
  287. package/src/core/component-diagnostics.ts +65 -12
  288. package/src/core/config-input.ts +39 -5
  289. package/src/core/config.ts +46 -1
  290. package/src/core/content-assets.ts +64 -144
  291. package/src/core/data.ts +10 -3
  292. package/src/core/diagnostics.ts +36 -3
  293. package/src/core/directive-diagnostics.ts +265 -47
  294. package/src/core/former-routes.ts +46 -0
  295. package/src/core/git-ignored.ts +53 -0
  296. package/src/core/github.ts +5 -4
  297. package/src/core/heading-markers.ts +156 -2
  298. package/src/core/hidden-pages.ts +64 -0
  299. package/src/core/includes.ts +181 -41
  300. package/src/core/last-modified.ts +47 -10
  301. package/src/core/links.ts +438 -83
  302. package/src/core/load-module.ts +98 -9
  303. package/src/core/manifest.ts +12 -9
  304. package/src/core/markdown-mirrors.ts +52 -0
  305. package/src/core/mcp-clients.ts +87 -0
  306. package/src/core/mdx-elements.ts +153 -0
  307. package/src/core/mdx-syntax.ts +377 -0
  308. package/src/core/meta-diagnostics.ts +134 -0
  309. package/src/core/meta.ts +154 -55
  310. package/src/core/nav-diagnostics.ts +41 -4
  311. package/src/core/navigation.ts +70 -10
  312. package/src/core/ordering-prefix.ts +22 -2
  313. package/src/core/pinned-ids.ts +141 -0
  314. package/src/core/project-graph.ts +221 -29
  315. package/src/core/redirect-patterns.ts +41 -0
  316. package/src/core/relative-files.ts +145 -0
  317. package/src/core/schema.ts +110 -6
  318. package/src/core/sources/mdx-remote.ts +75 -14
  319. package/src/core/sources/normalize.ts +885 -179
  320. package/src/core/sources/notion.ts +5 -4
  321. package/src/core/sources/obsidian.ts +2 -5
  322. package/src/core/sources/types.ts +17 -0
  323. package/src/core/static-files.ts +40 -0
  324. package/src/core/static-host.ts +125 -0
  325. package/src/core/svg-images.ts +98 -0
  326. package/src/core/syntax-diagnostics.ts +491 -0
  327. package/src/core/types.ts +43 -1
  328. package/src/deploy/artifacts.ts +2 -28
  329. package/src/deploy/generated-files.ts +128 -0
  330. package/src/deploy/platforms/vercel.ts +25 -1
  331. package/src/deploy/redirects.ts +43 -16
  332. package/src/deploy/sitemap.ts +2 -1
  333. package/src/markdown/base-links.ts +87 -6
  334. package/src/markdown/code-title.ts +29 -13
  335. package/src/markdown/content-assets.ts +210 -0
  336. package/src/markdown/directives.ts +69 -5
  337. package/src/markdown/fence-language.ts +106 -0
  338. package/src/markdown/fence-meta.ts +42 -0
  339. package/src/markdown/github-alerts.ts +172 -0
  340. package/src/markdown/heading-anchors.ts +265 -56
  341. package/src/markdown/include.ts +30 -3
  342. package/src/markdown/index.ts +145 -40
  343. package/src/markdown/prompt-text.ts +95 -0
  344. package/src/markdown/relative-links.ts +37 -2
  345. package/src/markdown/route-snapshot.ts +22 -12
  346. package/src/markdown/svg-fallback.ts +51 -0
  347. package/src/markdown/title-heading.ts +134 -0
  348. package/src/migrate/migrate.ts +127 -2
  349. package/src/og/card.ts +14 -4
  350. package/src/openapi/asyncapi.ts +14 -6
  351. package/src/openapi/checks.ts +160 -15
  352. package/src/openapi/model.ts +172 -33
  353. package/src/openapi/parse.ts +13 -4
  354. package/src/openapi/render-mdx.ts +55 -9
  355. package/src/openapi/source.ts +87 -6
  356. package/src/ratelimit/index.ts +2 -0
  357. package/src/ratelimit/memory.ts +1 -1
  358. package/src/ratelimit/runtime.ts +81 -0
  359. package/src/ratelimit/schema.ts +2 -0
  360. package/src/ratelimit/unkey.ts +61 -0
  361. package/src/registry/eject.ts +0 -1
  362. package/src/registry/registry.ts +1 -1
  363. package/src/search/documents.ts +10 -2
  364. package/src/sources/filesystem.ts +28 -3
  365. package/src/sources/mdx-remote.ts +7 -15
  366. package/src/theme/color.ts +14 -0
  367. package/src/theme/entry.ts +22 -1
  368. package/src/theme/palette.ts +74 -1
  369. package/dist/cli/chunk-0pmb4tr4.js.map +0 -35
  370. package/dist/cli/chunk-0w6hs479.js.map +0 -10
  371. package/dist/cli/chunk-150jk7km.js +0 -52
  372. package/dist/cli/chunk-150jk7km.js.map +0 -10
  373. package/dist/cli/chunk-36bxs9c7.js.map +0 -10
  374. package/dist/cli/chunk-53jqw8z0.js +0 -89
  375. package/dist/cli/chunk-53jqw8z0.js.map +0 -10
  376. package/dist/cli/chunk-60hz265r.js.map +0 -10
  377. package/dist/cli/chunk-desvm72z.js.map +0 -14
  378. package/dist/cli/chunk-dpy8qdjf.js.map +0 -17
  379. package/dist/cli/chunk-es8h0v3j.js +0 -441
  380. package/dist/cli/chunk-es8h0v3j.js.map +0 -11
  381. package/dist/cli/chunk-kdpzw38a.js.map +0 -11
  382. package/dist/cli/chunk-nbyvgnnm.js.map +0 -36
  383. package/dist/cli/chunk-s0bdnc7v.js.map +0 -10
  384. package/dist/cli/chunk-twy7psdz.js.map +0 -10
  385. package/dist/cli/chunk-ydp3065p.js +0 -64
  386. package/dist/cli/chunk-ydp3065p.js.map +0 -10
  387. /package/dist/cli/{chunk-y4msrwm9.js.map → chunk-080mfch6.js.map} +0 -0
  388. /package/dist/cli/{chunk-9qzgg6mj.js.map → chunk-34wap3ed.js.map} +0 -0
  389. /package/dist/cli/{chunk-1f882900.js.map → chunk-c5bjxq8v.js.map} +0 -0
  390. /package/dist/cli/{chunk-bxwhtvw8.js.map → chunk-f260jdrn.js.map} +0 -0
  391. /package/dist/cli/{chunk-74wfhv79.js.map → chunk-fnxgtjeq.js.map} +0 -0
  392. /package/dist/cli/{chunk-dhwtqnnd.js.map → chunk-gxhcf2hs.js.map} +0 -0
  393. /package/dist/cli/{chunk-tfycddbt.js.map → chunk-h0qwdbem.js.map} +0 -0
  394. /package/dist/cli/{chunk-j1x92mx9.js.map → chunk-j4nfgn2j.js.map} +0 -0
  395. /package/dist/cli/{chunk-ave1wcbr.js.map → chunk-qcyxkj44.js.map} +0 -0
  396. /package/dist/cli/{chunk-58bt3hag.js.map → chunk-x7bh6d9j.js.map} +0 -0
  397. /package/dist/cli/{chunk-hvcxng5f.js.map → chunk-ydqzkhjn.js.map} +0 -0
package/src/audit/run.ts CHANGED
@@ -8,7 +8,6 @@ import type { BlumeProject } from "../core/project-graph.ts";
8
8
  import type { Diagnostic } from "../core/types.ts";
9
9
  import { deployStaticDir } from "../deploy/adapter-output.ts";
10
10
  import { applyBaseToAstroRedirects } from "../deploy/redirects.ts";
11
- import { CHECKS } from "./catalog.ts";
12
11
  import type { CheckId } from "./catalog.ts";
13
12
  import { assetChecks } from "./checks/assets.ts";
14
13
  import { contentChecks } from "./checks/content.ts";
@@ -32,7 +31,7 @@ import {
32
31
  import { crawlStaticDir } from "./crawl.ts";
33
32
  import { buildGraph } from "./graph.ts";
34
33
  import { resolveRedirects } from "./redirects.ts";
35
- import { shortId } from "./terms.ts";
34
+ import { checkSelected } from "./terms.ts";
36
35
  import { DEFAULT_THRESHOLDS } from "./types.ts";
37
36
  import type {
38
37
  AuditContext,
@@ -84,6 +83,10 @@ export interface AuditResult {
84
83
  origin: string | null;
85
84
  /** Which tiers actually ran. A skipped tier is reported, never hidden. */
86
85
  tiers: Record<AuditTier, boolean>;
86
+ /** The `--only` terms the findings were filtered to, if any. */
87
+ only?: string[];
88
+ /** The `--skip` terms the findings were filtered by, if any. */
89
+ skip?: string[];
87
90
  }
88
91
 
89
92
  /** Thrown when there's no build to audit. */
@@ -123,18 +126,6 @@ const readSources = async (
123
126
  return new Map(entries.filter((entry) => entry !== null));
124
127
  };
125
128
 
126
- /** Does a check id or its category match one of the user's `--only`/`--skip` terms? */
127
- const matches = (id: CheckId, terms: string[]): boolean => {
128
- const meta = CHECKS.find((check) => check.id === id);
129
- const short = shortId(id);
130
- return terms.some((raw) => {
131
- const term = raw.trim().toLowerCase();
132
- return (
133
- term === short || term === id.toLowerCase() || term === meta?.category
134
- );
135
- });
136
- };
137
-
138
129
  /** Audit a built site. */
139
130
  export const runAudit = async (options: AuditOptions): Promise<AuditResult> => {
140
131
  const { project } = options;
@@ -203,25 +194,18 @@ export const runAudit = async (options: AuditOptions): Promise<AuditResult> => {
203
194
  )
204
195
  );
205
196
 
206
- let diagnostics = results.flat();
207
- if (options.only?.length) {
208
- // SAFETY: audit diagnostics are created through `finding()`, whose codes
209
- // all come from the check catalog's `CheckId` set.
210
- diagnostics = diagnostics.filter((d) =>
211
- matches(d.code as CheckId, options.only ?? [])
212
- );
213
- }
214
- if (options.skip?.length) {
215
- // SAFETY: same invariant — every audit diagnostic code is a catalog `CheckId`.
216
- diagnostics = diagnostics.filter(
217
- (d) => !matches(d.code as CheckId, options.skip ?? [])
218
- );
219
- }
197
+ // SAFETY: audit diagnostics are created through `finding()`, whose codes
198
+ // all come from the check catalog's `CheckId` set.
199
+ const diagnostics = results
200
+ .flat()
201
+ .filter((d) => checkSelected(d.code as CheckId, options));
220
202
 
221
203
  return {
222
204
  diagnostics,
205
+ only: options.only,
223
206
  origin,
224
207
  pages: crawl.pages.length,
208
+ skip: options.skip,
225
209
  staticDir,
226
210
  tiers,
227
211
  };
@@ -5,6 +5,29 @@ import type { CheckId } from "./catalog.ts";
5
5
  export const shortId = (id: CheckId): string =>
6
6
  id.replace("BLUME_AUDIT_", "").toLowerCase();
7
7
 
8
+ /** Does a check id or its category match one of the user's `--only`/`--skip` terms? */
9
+ const matchesTerms = (id: CheckId, terms: readonly string[]): boolean => {
10
+ const meta = CHECKS.find((check) => check.id === id);
11
+ const short = shortId(id);
12
+ return terms.some((raw) => {
13
+ const term = raw.trim().toLowerCase();
14
+ return (
15
+ term === short || term === id.toLowerCase() || term === meta?.category
16
+ );
17
+ });
18
+ };
19
+
20
+ /**
21
+ * Whether `--only` and `--skip` leave a check in: it matches an `--only`
22
+ * term, when any are given, and no `--skip` term.
23
+ */
24
+ export const checkSelected = (
25
+ id: CheckId,
26
+ filter: { only?: readonly string[]; skip?: readonly string[] }
27
+ ): boolean =>
28
+ (!filter.only?.length || matchesTerms(id, filter.only)) &&
29
+ !matchesTerms(id, filter.skip ?? []);
30
+
8
31
  /**
9
32
  * Every term `--only`/`--skip` accept, as the audit's filter compares them:
10
33
  * each check's short id and each category. The full `BLUME_AUDIT_…` id is
package/src/audit/url.ts CHANGED
@@ -158,8 +158,8 @@ export const resolveHref = (
158
158
  // A relative href resolves the way a browser resolves it from the page's
159
159
  // canonical URL. `URL` needs an origin to do that, so borrow a placeholder
160
160
  // one and keep only the path. The canonical URL is slashless — the generated
161
- // config sets `trailingSlash: "never"`, Vercel redirects `/docs/api/` onto
162
- // `/docs/api`, and the dev server 404s the slashed form — so `./auth` there
161
+ // config sets `trailingSlash: "never"`, and Vercel and the dev server
162
+ // redirect `/docs/api/` onto `/docs/api` — so `./auth` there
163
163
  // means `/docs/auth`, exactly where a reader's click lands. Content links
164
164
  // never hit this: the Markdown pipeline rewrites them root-relative (see
165
165
  // `resolveRelativeHref`); what's left is hand-written HTML, which a browser
@@ -1,5 +1,12 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+
3
+ import { basename, dirname, isAbsolute, join, relative, resolve } from "pathe";
4
+ import ts from "typescript";
1
5
  import { z } from "zod";
2
6
 
7
+ import { locateMdxFailure } from "../core/mdx-syntax.ts";
8
+ import type { MdxFailure, MdxFailureSite } from "../core/mdx-syntax.ts";
9
+ import type { BlumeProject } from "../core/project-graph.ts";
3
10
  import type { Diagnostic } from "../core/types.ts";
4
11
 
5
12
  /**
@@ -39,22 +46,231 @@ const blumeErrorSchema = z.looseObject({
39
46
  }),
40
47
  });
41
48
 
49
+ /**
50
+ * Finds where in an `.mdx` page's files a failure Astro reported with no line
51
+ * happened (see `locateMdxFailure`).
52
+ */
53
+ export type MdxFailureLocator = (
54
+ file: string,
55
+ failure: MdxFailure
56
+ ) => Promise<MdxFailureSite | null>;
57
+
58
+ /** What the report reads besides the error. */
59
+ export interface BuildFailureContext {
60
+ locate?: MdxFailureLocator;
61
+ /** The project root, where the build reads `tsconfig.json`. */
62
+ root?: string;
63
+ }
64
+
65
+ // The message for a name nothing defines, in V8 (Node) and JavaScriptCore (Bun).
66
+ const UNDEFINED_NAME = [
67
+ /^(?<name>[\w$]+) is not defined$/u,
68
+ /^Can't find variable: (?<name>[\w$]+)$/u,
69
+ ];
70
+ // A compiler message that opens with where it failed.
71
+ const POSITIONED = /^\d+:\d+: /u;
72
+
73
+ /**
74
+ * A `{…}` expression in an `.mdx` page that reads a name nothing defines: the
75
+ * page compiles, then throws while it renders. Placed at the expression when
76
+ * the page (or a partial it includes) shows where.
77
+ */
78
+ const undefinedNameDiagnostic = (
79
+ name: string,
80
+ file: string,
81
+ site: MdxFailureSite | null
82
+ ): Diagnostic => {
83
+ const escape =
84
+ "show the braces as text: escape each opening brace (`\\{`) or put it in inline code";
85
+ // `{{name}}` is a Blume variable the site doesn't define.
86
+ const variable = site?.expression === `{{${name}}}`;
87
+ return {
88
+ code: "BLUME_MDX_UNDEFINED_NAME",
89
+ column: site?.column,
90
+ file: site?.file ?? file,
91
+ line: site?.line,
92
+ message: site?.expression
93
+ ? `\`${site.expression}\` reads \`${name}\`, which isn't defined, so the page fails to render. In .mdx, \`{…}\` is a JavaScript expression.`
94
+ : `\`${name}\` isn't defined, so the page fails to render. In .mdx, \`{…}\` is a JavaScript expression, and one reads it.`,
95
+ severity: "error",
96
+ suggestion: variable
97
+ ? `Define \`${name}\` under \`variables\` in blume.config.ts, or ${escape}.`
98
+ : `To ${escape}. For a value every page shares, define a variable in blume.config.ts and write \`{{name}}\`.`,
99
+ };
100
+ };
101
+
102
+ // Vite's message for an `extends` or `references` entry it can't resolve. It
103
+ // names an `extends` package as written, and a path resolved.
104
+ const TSCONFIG_NOT_FOUND = /Tsconfig not found (?<target>\S+?)\.?$/mu;
105
+
106
+ const tsconfigLinksSchema = z.object({
107
+ extends: z.union([z.string(), z.array(z.string())]).optional(),
108
+ references: z.array(z.object({ path: z.string() })).optional(),
109
+ });
110
+
111
+ /** An entry in a tsconfig's `extends` or `references`, as written. */
112
+ interface TsconfigLink {
113
+ key: "extends" | "references";
114
+ written: string;
115
+ }
116
+
117
+ /**
118
+ * The `extends` or `references` entry in the tsconfig at `path` that names
119
+ * `target`: written as is, or resolved from the tsconfig's folder (a
120
+ * reference can name a folder, which means its `tsconfig.json`).
121
+ */
122
+ const linkTo = (path: string, target: string): TsconfigLink | undefined => {
123
+ const parsed = tsconfigLinksSchema.safeParse(
124
+ ts.readConfigFile(path, ts.sys.readFile).config
125
+ );
126
+ if (!parsed.success) {
127
+ return undefined;
128
+ }
129
+ const names = (written: string): boolean => {
130
+ const resolved = resolve(dirname(path), written);
131
+ return (
132
+ written === target ||
133
+ resolved === target ||
134
+ join(resolved, "tsconfig.json") === target
135
+ );
136
+ };
137
+ const extended = [parsed.data.extends ?? []].flat().find(names);
138
+ if (extended !== undefined) {
139
+ return { key: "extends", written: extended };
140
+ }
141
+ const referenced = (parsed.data.references ?? [])
142
+ .map((reference) => reference.path)
143
+ .find(names);
144
+ return referenced === undefined
145
+ ? undefined
146
+ : { key: "references", written: referenced };
147
+ };
148
+
149
+ /** How to fix a tsconfig entry that doesn't resolve. */
150
+ const tsconfigFix = ({ key, written }: TsconfigLink, name: string): string => {
151
+ if (key === "references") {
152
+ return `Create \`${written}\` (a framework's generated tsconfig appears once its prepare or build step runs), or remove it from \`references\` in ${name}.`;
153
+ }
154
+ if (written.startsWith(".") || isAbsolute(written)) {
155
+ return `Restore \`${written}\`, or remove it from \`extends\` in ${name}.`;
156
+ }
157
+ const packageName = written
158
+ .split("/")
159
+ .slice(0, written.startsWith("@") ? 2 : 1)
160
+ .join("/");
161
+ return `Install the package that provides it (\`${packageName}\`), or remove it from \`extends\` in ${name}.`;
162
+ };
163
+
164
+ /**
165
+ * An `extends` or `references` entry in the project's `tsconfig.json` that
166
+ * doesn't resolve: Vite reads that file while it builds the site, so the
167
+ * build fails in `astro sync` before any page compiles. Placed at the entry's
168
+ * line when the project's own tsconfig names the target.
169
+ */
170
+ const tsconfigExtendsDiagnostic = (
171
+ target: string,
172
+ root: string | undefined
173
+ ): Diagnostic => {
174
+ const found = root
175
+ ? ["tsconfig.json", "jsconfig.json"]
176
+ .map((name) => join(root, name))
177
+ .filter((path) => existsSync(path))
178
+ .map((path) => ({ link: linkTo(path, target), path }))
179
+ .find(({ link }) => link !== undefined)
180
+ : undefined;
181
+ const file = found?.path;
182
+ const { key, written } = found?.link ?? { key: "extends", written: target };
183
+ const line = file
184
+ ? readFileSync(file, "utf-8")
185
+ .split("\n")
186
+ .findIndex((text) => text.includes(JSON.stringify(written))) + 1
187
+ : 0;
188
+ const name = file ? basename(file) : "tsconfig.json";
189
+ return {
190
+ code: "BLUME_TSCONFIG_EXTENDS",
191
+ file,
192
+ line: line > 0 ? line : undefined,
193
+ message: `${file ? name : "A tsconfig.json in the project"} ${key} \`${written}\`, which can't be found. The build reads the project's ${name} to resolve imports, so it stops here.`,
194
+ severity: "error",
195
+ suggestion: tsconfigFix({ key, written }, name),
196
+ };
197
+ };
198
+
199
+ /** The `extends` target an unresolvable tsconfig names, if `error` is that. */
200
+ const missingTsconfig = (error: Error): string | undefined =>
201
+ (
202
+ TSCONFIG_NOT_FOUND.exec(error.message) ??
203
+ // `astro sync` wraps the resolver's error in its own.
204
+ (error.cause instanceof Error
205
+ ? TSCONFIG_NOT_FOUND.exec(error.cause.message)
206
+ : null)
207
+ )?.groups?.target;
208
+
209
+ /** The name a render error says nothing defines, if it says that. */
210
+ const undefinedName = (error: Error): string | undefined =>
211
+ error.name === "ReferenceError"
212
+ ? UNDEFINED_NAME.map((pattern) => pattern.exec(error.message)).find(Boolean)
213
+ ?.groups?.name
214
+ : undefined;
215
+
216
+ /**
217
+ * Where in an `.mdx` page's files a failure the compiler didn't place
218
+ * happened: a render error the page threw, or a compile error in a partial it
219
+ * includes, which the compiler pins on the page with no `line:column:` in its
220
+ * message. (Bun stamps a `line` of its own on every error, so `loc` can't
221
+ * tell.)
222
+ */
223
+ const locateFailure = async (
224
+ error: Error,
225
+ page: string,
226
+ name: string | undefined,
227
+ locate: MdxFailureLocator | undefined
228
+ ): Promise<MdxFailureSite | null> => {
229
+ if (!locate) {
230
+ return null;
231
+ }
232
+ if (name) {
233
+ return await locate(page, { kind: "undefined-name", name });
234
+ }
235
+ return error.name === "MDXError" && !POSITIONED.test(error.message)
236
+ ? await locate(page, { kind: "syntax" })
237
+ : null;
238
+ };
239
+
42
240
  /** One failure as a diagnostic at the file (and position) it names. */
43
- const failureDiagnostic = (error: Error): Diagnostic => {
241
+ const failureDiagnostic = async (
242
+ error: Error,
243
+ context: BuildFailureContext
244
+ ): Promise<Diagnostic> => {
44
245
  const raised = blumeErrorSchema.safeParse(error);
45
246
  if (raised.success) {
46
247
  return raised.data.diagnostic;
47
248
  }
249
+ const tsconfig = missingTsconfig(error);
250
+ if (tsconfig) {
251
+ return tsconfigExtendsDiagnostic(tsconfig, context.root);
252
+ }
48
253
  const parsed = buildErrorSchema.safeParse(error);
49
254
  const { id, loc } = parsed.success ? parsed.data : {};
255
+ const file = loc?.file ?? id;
256
+ const name = undefinedName(error);
257
+ const site = file?.endsWith(".mdx")
258
+ ? await locateFailure(error, file, name, context.locate)
259
+ : null;
260
+ if (file?.endsWith(".mdx") && name) {
261
+ return undefinedNameDiagnostic(name, file, site);
262
+ }
263
+ if (site?.diagnostic) {
264
+ return site.diagnostic;
265
+ }
50
266
  // A named error (`MDXError`, `CompilerError`) says which step failed.
51
- const name = error.name === "Error" ? "" : `${error.name}: `;
267
+ const label = error.name === "Error" ? "" : `${error.name}: `;
52
268
  return {
53
269
  code: "BLUME_BUILD_FAILED",
54
270
  column: loc?.column,
55
- file: loc?.file ?? id,
271
+ file,
56
272
  line: loc?.line,
57
- message: `${name}${error.message}`,
273
+ message: `${label}${error.message}`,
58
274
  severity: "error",
59
275
  };
60
276
  };
@@ -66,8 +282,41 @@ const failureDiagnostic = (error: Error): Diagnostic => {
66
282
  * is a problem in the site, reported at its file, rather than an internal
67
283
  * error blaming Blume.
68
284
  */
69
- export const astroBuildDiagnostics = (error: Error): Diagnostic[] => {
70
- const parsed = buildErrorSchema.safeParse(error);
285
+ export const astroBuildDiagnostics = async (
286
+ rejection: Error,
287
+ context: BuildFailureContext = {}
288
+ ): Promise<Diagnostic[]> => {
289
+ const parsed = buildErrorSchema.safeParse(rejection);
71
290
  const failures = parsed.success ? (parsed.data.errors ?? []) : [];
72
- return (failures.length > 0 ? failures : [error]).map(failureDiagnostic);
291
+ return await Promise.all(
292
+ (failures.length > 0 ? failures : [rejection]).map(
293
+ async (failure) => await failureDiagnostic(failure, context)
294
+ )
295
+ );
73
296
  };
297
+
298
+ /**
299
+ * The locator a build uses: the failing page's source, read with the includes
300
+ * and variables the scan resolved for it.
301
+ */
302
+ export const buildFailureLocator =
303
+ (
304
+ project: Pick<BlumeProject, "config" | "context" | "graph" | "sources">
305
+ ): MdxFailureLocator =>
306
+ (file, failure) => {
307
+ const page = project.graph.pages.find(
308
+ (candidate) => candidate.sourcePath === file
309
+ );
310
+ const source = project.sources.find(
311
+ (candidate) => candidate.name === page?.source.name
312
+ );
313
+ return locateMdxFailure(
314
+ {
315
+ contentRoot: source?.staged ? undefined : source?.contentRoot,
316
+ name: relative(project.context.root, file),
317
+ sourcePath: file,
318
+ variables: project.config.variables,
319
+ },
320
+ failure
321
+ );
322
+ };
@@ -55,7 +55,7 @@ export const commandMeta = {
55
55
  },
56
56
  migrate: {
57
57
  description:
58
- "Migrate a docs site from Mintlify, Fumadocs, Docusaurus, Starlight, or Nextra with a coding agent.",
58
+ "Migrate a docs site from Mintlify, Docusaurus, GitBook, MkDocs, ReadMe, or another framework with a coding agent.",
59
59
  name: "migrate",
60
60
  },
61
61
  preview: {
@@ -12,6 +12,7 @@ import { NoBuildError, runAudit } from "../../audit/run.ts";
12
12
  import type { AuditResult } from "../../audit/run.ts";
13
13
  import { checkTerms, unknownCheckTerms } from "../../audit/terms.ts";
14
14
  import { isHttpUrl } from "../../audit/url.ts";
15
+ import { closestMatch } from "../../core/closest-match.ts";
15
16
  import { BlumeError } from "../../core/diagnostics.ts";
16
17
  import { scanProject } from "../../core/project-graph.ts";
17
18
  import type { DiagnosticSeverity } from "../../core/types.ts";
@@ -19,7 +20,6 @@ import { parseIgnoreFlag } from "../args.ts";
19
20
  import { commandMeta } from "../command-meta.ts";
20
21
  import { reportInternalError } from "../internal-error.ts";
21
22
  import { flushStdout, logger, reportDiagnostics } from "../log.ts";
22
- import { closestMatch } from "../unknown-flags.ts";
23
23
 
24
24
  const SEVERITIES: DiagnosticSeverity[] = ["error", "warning", "info"];
25
25
 
@@ -22,7 +22,10 @@ import {
22
22
  removedBuildFlags,
23
23
  removedBuildFlagsAdvice,
24
24
  } from "../../upgrade/upgrade.ts";
25
- import { astroBuildDiagnostics } from "../build-failure.ts";
25
+ import {
26
+ astroBuildDiagnostics,
27
+ buildFailureLocator,
28
+ } from "../build-failure.ts";
26
29
  import { commandMeta } from "../command-meta.ts";
27
30
  import { refuseIfDevRunning } from "../dev-lock.ts";
28
31
  import { refuseIfEjected } from "../eject-scripts.ts";
@@ -248,7 +251,7 @@ const reportBuild = async (
248
251
  // count next to the success banner so it can't scroll away unseen.
249
252
  if (project.droppedPages > 0) {
250
253
  logger.warn(
251
- `${project.droppedPages} page(s) failed frontmatter validation and are missing from this build.`
254
+ `${project.droppedPages} page(s) failed validation and are missing from this build.`
252
255
  );
253
256
  }
254
257
  logger.success(`Built to ${distDir}`);
@@ -348,7 +351,13 @@ export const buildCommand = defineCommand({
348
351
  if (error instanceof BlumeError || !(error instanceof Error)) {
349
352
  throw error;
350
353
  }
351
- reportDiagnostics(astroBuildDiagnostics(error), root);
354
+ reportDiagnostics(
355
+ await astroBuildDiagnostics(error, {
356
+ locate: buildFailureLocator(project),
357
+ root,
358
+ }),
359
+ root
360
+ );
352
361
  process.exit(1);
353
362
  }
354
363
 
@@ -6,10 +6,11 @@ import { defineCommand } from "citty";
6
6
  import { join } from "pathe";
7
7
 
8
8
  import { ensureGitignore } from "../../core/gitignore.ts";
9
+ import { astroBuildDiagnostics } from "../build-failure.ts";
9
10
  import { commandMeta } from "../command-meta.ts";
10
11
  import { refuseIfDevRunning } from "../dev-lock.ts";
11
12
  import { refuseIfEjected } from "../eject-scripts.ts";
12
- import { logger } from "../log.ts";
13
+ import { logger, reportDiagnostics } from "../log.ts";
13
14
  import { prepareProject } from "../prepare.ts";
14
15
 
15
16
  export const checkCommand = defineCommand({
@@ -57,7 +58,17 @@ export const checkCommand = defineCommand({
57
58
 
58
59
  // Generate Astro's content/collection and font types into `.blume/.astro`
59
60
  // so `astro:*` virtual modules resolve during the check.
60
- await sync({ logLevel: "warn", root: outDir });
61
+ try {
62
+ await sync({ logLevel: "warn", root: outDir });
63
+ } catch (error) {
64
+ // A sync that rejects on the site (a tsconfig `extends` that doesn't
65
+ // resolve, a page Astro can't load) is reported as `blume build` does.
66
+ if (!(error instanceof Error)) {
67
+ throw error;
68
+ }
69
+ reportDiagnostics(await astroBuildDiagnostics(error, { root }), root);
70
+ process.exit(1);
71
+ }
61
72
 
62
73
  // The project-root tsconfig is what covers the authored `pages/` and config;
63
74
  // without it astro check only sees the generated `.blume` project. Falls back
@@ -152,7 +152,7 @@ export const devCommand = defineCommand({
152
152
  if (error instanceof BlumeError || !(error instanceof Error)) {
153
153
  throw error;
154
154
  }
155
- reportDiagnostics(astroBuildDiagnostics(error), root);
155
+ reportDiagnostics(await astroBuildDiagnostics(error, { root }), root);
156
156
  process.exit(1);
157
157
  }
158
158
 
@@ -22,6 +22,7 @@ import type { ResolvedConfig } from "../../core/schema.ts";
22
22
  import { serverFeatures } from "../../core/server-features.ts";
23
23
  import type { Diagnostic } from "../../core/types.ts";
24
24
  import { unregisteredSnapshotDiagnostics } from "../../core/version-cut.ts";
25
+ import { servedFiles } from "../../deploy/generated-files.ts";
25
26
  import { commandMeta } from "../command-meta.ts";
26
27
  import { loadEnvFiles } from "../env.ts";
27
28
  import { reportInternalError } from "../internal-error.ts";
@@ -33,7 +34,7 @@ import {
33
34
  } from "../log.ts";
34
35
  import { checkRequiredSecrets } from "../required-secrets.ts";
35
36
 
36
- const FALLBACK_NODE_RANGE = ">=22.12.0";
37
+ const FALLBACK_NODE_RANGE = ">=22.19.0";
37
38
 
38
39
  /**
39
40
  * Plan `components.ts` the way `blume dev`/`build` do, reporting each override
@@ -142,7 +143,8 @@ export const doctorCommand = defineCommand({
142
143
  ...unregisteredSnapshotDiagnostics(project),
143
144
  ...validateNavTargets(
144
145
  project.graph.navigation,
145
- navTargetRoutes(project, userPages)
146
+ navTargetRoutes(project, userPages),
147
+ servedFiles(project)
146
148
  )
147
149
  );
148
150
 
@@ -75,9 +75,9 @@ const installScaffold = async (
75
75
  /**
76
76
  * Eject the freshly scaffolded project, or print the install-then-eject path.
77
77
  * Eject jiti-loads the scaffolded blume.config.ts, whose `import { defineConfig }
78
- * from "blume"` only resolves once dependencies are installed (or blume is
79
- * hoisted from an ancestor node_modules, as in a monorepo) — so the fallback
80
- * below is the common path when the install step was skipped.
78
+ * from "blume"` resolves to the running Blume package even before an install
79
+ * (see `blumeModuleAliases`). When eject still fails after a skipped install,
80
+ * the fallback below names the install as the likely fix.
81
81
  */
82
82
  const ejectScaffold = async (
83
83
  root: string,
@@ -4,15 +4,19 @@ import { preview } from "astro";
4
4
  import { defineCommand } from "citty";
5
5
  import { join } from "pathe";
6
6
 
7
+ import { normalizeBasePath } from "../../core/base-path.ts";
7
8
  import { loadConfig } from "../../core/config.ts";
8
9
  import { resolveProjectContext } from "../../core/project.ts";
10
+ import { publicFolder } from "../../core/static-host.ts";
9
11
  import { deployOutputDir } from "../../deploy/adapter-output.ts";
10
12
  import { deployPlatform } from "../../deploy/platforms/index.ts";
13
+ import { distDir } from "../../deploy/platforms/paths.ts";
11
14
  import { parsePort } from "../args.ts";
12
15
  import { commandMeta } from "../command-meta.ts";
13
16
  import { refuseIfEjected } from "../eject-scripts.ts";
14
17
  import { normalizeHost } from "../host-args.ts";
15
18
  import { logger } from "../log.ts";
19
+ import { previewRedirects, servePreview } from "../preview-pages.ts";
16
20
 
17
21
  export const previewCommand = defineCommand({
18
22
  args: {
@@ -48,7 +52,7 @@ export const previewCommand = defineCommand({
48
52
  process.exit(1);
49
53
  }
50
54
 
51
- await preview({
55
+ const server = await preview({
52
56
  logLevel: "info",
53
57
  root: context.outDir,
54
58
  server: {
@@ -58,5 +62,17 @@ export const previewCommand = defineCommand({
58
62
  port: parsePort(args.port),
59
63
  },
60
64
  });
65
+ // A static build is served by Vite, which knows nothing of the host's
66
+ // redirect rules or trailing-slash handling, and whose HTML fallback
67
+ // would answer a page with the redirect page of a redirect from its
68
+ // `.html` URL.
69
+ if (deployment.options.output === "static") {
70
+ servePreview(server, {
71
+ base: normalizeBasePath(deployment.options.base),
72
+ distDir: distDir(context),
73
+ isPublicFolder: publicFolder(join(context.root, "public")),
74
+ redirects: previewRedirects(config, context.outDir),
75
+ });
76
+ }
61
77
  },
62
78
  });