blume 2.1.3 → 2.2.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 (377) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +126 -0
  3. package/README.md +4 -4
  4. package/dist/cli/{chunk-74wfhv79.js → chunk-05f21r8h.js} +1 -1
  5. package/dist/cli/{chunk-9qzgg6mj.js → chunk-1cm0h819.js} +3 -3
  6. package/dist/cli/{chunk-nbyvgnnm.js → chunk-1rkxzg3n.js} +104 -47
  7. package/dist/cli/chunk-1rkxzg3n.js.map +36 -0
  8. package/dist/cli/{chunk-desvm72z.js → chunk-2nr29y95.js} +22 -26
  9. package/dist/cli/chunk-2nr29y95.js.map +13 -0
  10. package/dist/cli/{chunk-32khwcb6.js → chunk-49r370mb.js} +42 -69
  11. package/dist/cli/{chunk-32khwcb6.js.map → chunk-49r370mb.js.map} +9 -10
  12. package/dist/cli/{chunk-ms9z66nz.js → chunk-4rftmttv.js} +2 -2
  13. package/dist/cli/{chunk-s0bdnc7v.js → chunk-79bzzkx4.js} +5 -4
  14. package/dist/cli/chunk-79bzzkx4.js.map +10 -0
  15. package/dist/cli/{chunk-hvcxng5f.js → chunk-7tekknev.js} +6 -6
  16. package/dist/cli/{chunk-dv2z0rzf.js → chunk-8b489v1b.js} +38 -13
  17. package/dist/cli/{chunk-dv2z0rzf.js.map → chunk-8b489v1b.js.map} +5 -4
  18. package/dist/cli/{chunk-0pmb4tr4.js → chunk-8f8xw4x2.js} +1195 -222
  19. package/dist/cli/chunk-8f8xw4x2.js.map +44 -0
  20. package/dist/cli/chunk-96snx8yy.js +329 -0
  21. package/dist/cli/chunk-96snx8yy.js.map +13 -0
  22. package/dist/cli/{chunk-ckrcm08h.js → chunk-a3ydrqny.js} +48 -62
  23. package/dist/cli/{chunk-ckrcm08h.js.map → chunk-a3ydrqny.js.map} +4 -5
  24. package/dist/cli/{chunk-bxwhtvw8.js → chunk-ahc5gc9h.js} +5 -5
  25. package/dist/cli/{chunk-dpy8qdjf.js → chunk-as09pafp.js} +73 -182
  26. package/dist/cli/chunk-as09pafp.js.map +18 -0
  27. package/dist/cli/{chunk-tfycddbt.js → chunk-bjk964fp.js} +2 -2
  28. package/dist/cli/{chunk-gfc5fqvf.js → chunk-br0n8n3e.js} +18 -18
  29. package/dist/cli/{chunk-gfc5fqvf.js.map → chunk-br0n8n3e.js.map} +1 -1
  30. package/dist/cli/{chunk-297ajdcv.js → chunk-c69tw29j.js} +6 -6
  31. package/dist/cli/{chunk-297ajdcv.js.map → chunk-c69tw29j.js.map} +3 -3
  32. package/dist/cli/chunk-cn3dy3hz.js +146 -0
  33. package/dist/cli/chunk-cn3dy3hz.js.map +11 -0
  34. package/dist/cli/{chunk-124fvmzq.js → chunk-dm4mg9bj.js} +11064 -8742
  35. package/dist/cli/{chunk-124fvmzq.js.map → chunk-dm4mg9bj.js.map} +78 -64
  36. package/dist/cli/{chunk-dqhg54mr.js → chunk-dmx6cezx.js} +1 -1
  37. package/dist/cli/{chunk-60hz265r.js → chunk-dpcahcrb.js} +24 -11
  38. package/dist/cli/chunk-dpcahcrb.js.map +10 -0
  39. package/dist/cli/{chunk-a57yh0h9.js → chunk-dyk3r7jw.js} +7 -7
  40. package/dist/cli/{chunk-a57yh0h9.js.map → chunk-dyk3r7jw.js.map} +1 -1
  41. package/dist/cli/{chunk-twy7psdz.js → chunk-enk76qd5.js} +14 -10
  42. package/dist/cli/chunk-enk76qd5.js.map +10 -0
  43. package/dist/cli/{chunk-k3gr1nm9.js → chunk-fqy4n3q2.js} +13 -16
  44. package/dist/cli/{chunk-k3gr1nm9.js.map → chunk-fqy4n3q2.js.map} +3 -3
  45. package/dist/cli/{chunk-sc93apfs.js → chunk-frk2wdq1.js} +17 -17
  46. package/dist/cli/{chunk-sc93apfs.js.map → chunk-frk2wdq1.js.map} +2 -2
  47. package/dist/cli/{chunk-kdpzw38a.js → chunk-gprwej7h.js} +95 -7
  48. package/dist/cli/chunk-gprwej7h.js.map +11 -0
  49. package/dist/cli/chunk-kkrteb15.js +157 -0
  50. package/dist/cli/chunk-kkrteb15.js.map +10 -0
  51. package/dist/cli/{chunk-ave1wcbr.js → chunk-m9bp2hc1.js} +1 -1
  52. package/dist/cli/{chunk-58bt3hag.js → chunk-mrvh4c88.js} +4 -4
  53. package/dist/cli/{chunk-5cmts6dv.js → chunk-p8zg3a2h.js} +7 -7
  54. package/dist/cli/{chunk-5cmts6dv.js.map → chunk-p8zg3a2h.js.map} +1 -1
  55. package/dist/cli/{chunk-0w6hs479.js → chunk-rkqae7kh.js} +30 -18
  56. package/dist/cli/chunk-rkqae7kh.js.map +10 -0
  57. package/dist/cli/{chunk-bqxaqn5p.js → chunk-t5xx74rd.js} +8 -8
  58. package/dist/cli/{chunk-bqxaqn5p.js.map → chunk-t5xx74rd.js.map} +1 -1
  59. package/dist/cli/{chunk-1f882900.js → chunk-th0pgaj5.js} +2 -2
  60. package/dist/cli/{chunk-dhwtqnnd.js → chunk-vnbvhgmd.js} +2 -2
  61. package/dist/cli/{chunk-gxtg6ed2.js → chunk-vzy9rh6y.js} +21 -21
  62. package/dist/cli/{chunk-gxtg6ed2.js.map → chunk-vzy9rh6y.js.map} +3 -3
  63. package/dist/cli/{chunk-j1x92mx9.js → chunk-w6e455pz.js} +1 -1
  64. package/dist/cli/chunk-wehv26ry.js +155 -0
  65. package/dist/cli/chunk-wehv26ry.js.map +11 -0
  66. package/dist/cli/{chunk-y4msrwm9.js → chunk-xgq4eytj.js} +15 -15
  67. package/dist/cli/{chunk-36bxs9c7.js → chunk-xt0qvz1t.js} +27 -23
  68. package/dist/cli/chunk-xt0qvz1t.js.map +10 -0
  69. package/dist/cli/index.js +78 -42
  70. package/dist/cli/index.js.map +7 -6
  71. package/dist/types/ai/changelog-components.d.ts +18 -0
  72. package/dist/types/ai/changelog-markdown.d.ts +12 -0
  73. package/dist/types/ai/openapi-components.d.ts +7 -7
  74. package/dist/types/ai/operation-details.d.ts +39 -0
  75. package/dist/types/ai/relative-links.d.ts +3 -1
  76. package/dist/types/ai/serializers.d.ts +6 -4
  77. package/dist/types/analytics/head.d.ts +4 -2
  78. package/dist/types/analytics/script.d.ts +8 -2
  79. package/dist/types/components/content/youtube.d.ts +20 -0
  80. package/dist/types/components/layout/nav-utils.d.ts +8 -0
  81. package/dist/types/components/openapi/example-panels.d.ts +33 -0
  82. package/dist/types/components/openapi/helpers.d.ts +53 -11
  83. package/dist/types/components/openapi/snippets.d.ts +7 -1
  84. package/dist/types/core/alert-diagnostics.d.ts +10 -0
  85. package/dist/types/core/builtin-tags.d.ts +16 -0
  86. package/dist/types/core/changelog-list.d.ts +48 -0
  87. package/dist/types/core/closest-match.d.ts +6 -0
  88. package/dist/types/core/code-fence-diagnostics.d.ts +18 -0
  89. package/dist/types/core/code-fences.d.ts +8 -1
  90. package/dist/types/core/config-input.d.ts +1 -1
  91. package/dist/types/core/content-assets.d.ts +16 -18
  92. package/dist/types/core/diagnostics.d.ts +14 -0
  93. package/dist/types/core/directive-diagnostics.d.ts +22 -8
  94. package/dist/types/core/former-routes.d.ts +14 -0
  95. package/dist/types/core/github.d.ts +5 -4
  96. package/dist/types/core/heading-markers.d.ts +65 -2
  97. package/dist/types/core/hidden-pages.d.ts +3 -0
  98. package/dist/types/core/includes.d.ts +23 -0
  99. package/dist/types/core/last-modified.d.ts +18 -6
  100. package/dist/types/core/links.d.ts +42 -2
  101. package/dist/types/core/load-module.d.ts +19 -4
  102. package/dist/types/core/manifest.d.ts +14 -1
  103. package/dist/types/core/markdown-mirrors.d.ts +22 -0
  104. package/dist/types/core/mdx-elements.d.ts +32 -0
  105. package/dist/types/core/mdx-syntax.d.ts +100 -0
  106. package/dist/types/core/meta-diagnostics.d.ts +7 -0
  107. package/dist/types/core/meta.d.ts +32 -5
  108. package/dist/types/core/nav-diagnostics.d.ts +9 -2
  109. package/dist/types/core/navigation.d.ts +13 -0
  110. package/dist/types/core/ordering-prefix.d.ts +6 -0
  111. package/dist/types/core/pinned-ids.d.ts +23 -0
  112. package/dist/types/core/project-graph.d.ts +9 -1
  113. package/dist/types/core/redirect-patterns.d.ts +18 -0
  114. package/dist/types/core/relative-files.d.ts +31 -0
  115. package/dist/types/core/schema.d.ts +267 -7
  116. package/dist/types/core/sources/normalize.d.ts +101 -9
  117. package/dist/types/core/sources/types.d.ts +16 -0
  118. package/dist/types/core/static-files.d.ts +10 -0
  119. package/dist/types/core/static-host.d.ts +42 -0
  120. package/dist/types/core/svg-images.d.ts +26 -0
  121. package/dist/types/core/syntax-diagnostics.d.ts +14 -0
  122. package/dist/types/core/types.d.ts +42 -1
  123. package/dist/types/deploy/generated-files.d.ts +41 -0
  124. package/dist/types/deploy/redirects.d.ts +21 -7
  125. package/dist/types/markdown/code-title.d.ts +9 -5
  126. package/dist/types/markdown/directives.d.ts +8 -0
  127. package/dist/types/markdown/fence-language.d.ts +51 -0
  128. package/dist/types/markdown/fence-meta.d.ts +5 -0
  129. package/dist/types/markdown/github-alerts.d.ts +35 -0
  130. package/dist/types/openapi/asyncapi.d.ts +6 -5
  131. package/dist/types/openapi/checks.d.ts +10 -5
  132. package/dist/types/openapi/model.d.ts +55 -8
  133. package/dist/types/openapi/parse.d.ts +4 -1
  134. package/dist/types/sources/filesystem.d.ts +14 -4
  135. package/dist/types/sources/mdx-remote.d.ts +6 -3
  136. package/dist/types/sources/registry.d.ts +3 -3
  137. package/dist/types/theme/color.d.ts +33 -0
  138. package/dist/types/theme/palette.d.ts +122 -0
  139. package/docs/01-quickstart.mdx +1 -1
  140. package/docs/02-deployment.mdx +23 -8
  141. package/docs/04-migrating.mdx +16 -4
  142. package/docs/advanced/changelog.mdx +27 -1
  143. package/docs/advanced/skills.mdx +1 -1
  144. package/docs/cli/audit.mdx +1 -1
  145. package/docs/cli/doctor.mdx +2 -2
  146. package/docs/cli/evals.mdx +1 -1
  147. package/docs/cli/index.mdx +4 -2
  148. package/docs/cli/validate.mdx +10 -7
  149. package/docs/configuration/analytics.mdx +2 -0
  150. package/docs/configuration/customization.mdx +3 -1
  151. package/docs/configuration/index.mdx +2 -2
  152. package/docs/configuration/theming.mdx +5 -1
  153. package/docs/content/components.mdx +26 -6
  154. package/docs/content/frontmatter.mdx +1 -1
  155. package/docs/content/includes.mdx +6 -2
  156. package/docs/content/index.mdx +11 -1
  157. package/docs/content/islands.mdx +2 -0
  158. package/docs/content/meta.mdx +9 -3
  159. package/docs/content/navigation.mdx +14 -6
  160. package/docs/content/sources/obsidian.mdx +1 -1
  161. package/docs/content/sources/remote-mdx.mdx +3 -1
  162. package/docs/content/syntax.mdx +76 -8
  163. package/docs/content/variables.mdx +3 -1
  164. package/docs/discoverability/agent-discovery.mdx +1 -1
  165. package/docs/discoverability/json-api.mdx +1 -1
  166. package/docs/discoverability/llms-txt.mdx +1 -1
  167. package/docs/discoverability/markdown.mdx +2 -2
  168. package/docs/discoverability/open-graph.mdx +1 -1
  169. package/docs/discoverability/sitemap-and-robots.mdx +1 -1
  170. package/docs/references/graphql.mdx +2 -0
  171. package/docs/references/openapi.mdx +45 -7
  172. package/package.json +4 -4
  173. package/skills/blume/SKILL.md +2 -2
  174. package/skills/blume-migrate/SKILL.md +49 -27
  175. package/skills/blume-migrate/references/docsify.md +262 -0
  176. package/skills/blume-migrate/references/docus.md +298 -0
  177. package/skills/blume-migrate/references/docusaurus.md +3 -3
  178. package/skills/blume-migrate/references/fern.md +327 -0
  179. package/skills/blume-migrate/references/fumadocs.md +1 -1
  180. package/skills/blume-migrate/references/gitbook.md +267 -0
  181. package/skills/blume-migrate/references/github-wiki.md +259 -0
  182. package/skills/blume-migrate/references/jekyll.md +253 -0
  183. package/skills/blume-migrate/references/mdbook.md +256 -0
  184. package/skills/blume-migrate/references/mintlify.md +5 -5
  185. package/skills/blume-migrate/references/mkdocs.md +236 -0
  186. package/skills/blume-migrate/references/monorepo.md +20 -8
  187. package/skills/blume-migrate/references/nextra.md +2 -2
  188. package/skills/blume-migrate/references/readme.md +257 -0
  189. package/skills/blume-migrate/references/redocly.md +198 -0
  190. package/skills/blume-migrate/references/starlight.md +3 -3
  191. package/skills/blume-migrate/references/vitepress.md +204 -0
  192. package/skills/blume-migrate/references/vuepress.md +271 -0
  193. package/skills/blume-migrate/scripts/docsify-codemod.mjs +2387 -0
  194. package/skills/blume-migrate/scripts/docus-codemod.mjs +2542 -0
  195. package/skills/blume-migrate/scripts/fern-codemod.mjs +2633 -0
  196. package/skills/blume-migrate/scripts/github-wiki-codemod.mjs +2808 -0
  197. package/skills/blume-migrate/scripts/include-excerpts.mjs +649 -0
  198. package/skills/blume-migrate/scripts/jekyll-codemod.mjs +3649 -0
  199. package/skills/blume-migrate/scripts/mintlify-codemod.mjs +17 -13
  200. package/skills/blume-migrate/scripts/mkdocs-codemod.mjs +2526 -0
  201. package/skills/blume-migrate/scripts/operation-routes.mjs +508 -0
  202. package/skills/blume-migrate/scripts/pin-heading-ids.mjs +1049 -0
  203. package/skills/blume-migrate/scripts/readme-codemod.mjs +4139 -0
  204. package/skills/blume-migrate/scripts/vitepress-codemod.mjs +1511 -0
  205. package/src/ai/changelog-components.ts +24 -0
  206. package/src/ai/changelog-markdown.ts +45 -41
  207. package/src/ai/component-markdown.ts +22 -9
  208. package/src/ai/llms.ts +36 -5
  209. package/src/ai/markdown.ts +2 -2
  210. package/src/ai/openapi-components.ts +25 -9
  211. package/src/ai/operation-details.ts +208 -0
  212. package/src/ai/relative-links.ts +48 -8
  213. package/src/ai/serializers.ts +7 -6
  214. package/src/ai/site-skill.ts +6 -2
  215. package/src/analytics/head.ts +11 -5
  216. package/src/analytics/script.ts +13 -3
  217. package/src/astro/generate.ts +32 -6
  218. package/src/astro/index.ts +1 -0
  219. package/src/astro/integration.ts +27 -1
  220. package/src/astro/mdx-source-errors.ts +46 -0
  221. package/src/astro/templates.ts +89 -185
  222. package/src/audit/checks/redirects.ts +5 -1
  223. package/src/audit/redirects.ts +36 -19
  224. package/src/audit/report.ts +22 -9
  225. package/src/audit/run.ts +12 -28
  226. package/src/audit/terms.ts +23 -0
  227. package/src/audit/url.ts +2 -2
  228. package/src/cli/build-failure.ts +256 -7
  229. package/src/cli/command-meta.ts +1 -1
  230. package/src/cli/commands/audit.ts +1 -1
  231. package/src/cli/commands/build.ts +12 -3
  232. package/src/cli/commands/check.ts +13 -2
  233. package/src/cli/commands/dev.ts +1 -1
  234. package/src/cli/commands/doctor.ts +4 -2
  235. package/src/cli/commands/init.ts +3 -3
  236. package/src/cli/commands/preview.ts +17 -1
  237. package/src/cli/commands/validate.ts +22 -10
  238. package/src/cli/prepare.ts +1 -1
  239. package/src/cli/preview-pages.ts +161 -0
  240. package/src/cli/unknown-flags.ts +1 -42
  241. package/src/components/code-copy.ts +51 -0
  242. package/src/components/code-language.ts +75 -0
  243. package/src/components/content/AccordionItem.astro +16 -1
  244. package/src/components/content/Callout.astro +11 -1
  245. package/src/components/content/Changelog.astro +189 -0
  246. package/src/components/content/CodeBlock.astro +8 -11
  247. package/src/components/content/Tabs.astro +16 -2
  248. package/src/components/content/YouTube.astro +6 -4
  249. package/src/components/content/code-block.ts +33 -0
  250. package/src/components/content/content-strings.ts +5 -1
  251. package/src/components/content/inline-markdown.ts +35 -8
  252. package/src/components/content/youtube.ts +49 -7
  253. package/src/components/layout/Analytics.astro +5 -1
  254. package/src/components/layout/NavTree.astro +8 -3
  255. package/src/components/layout/RootLayout.astro +18 -31
  256. package/src/components/layout/nav-utils.ts +14 -0
  257. package/src/components/layout/search/pagefind.ts +2 -1
  258. package/src/components/openapi/Operation.astro +4 -0
  259. package/src/components/openapi/PanelTabs.astro +9 -3
  260. package/src/components/openapi/RequestBody.astro +42 -8
  261. package/src/components/openapi/RequestPanel.astro +34 -53
  262. package/src/components/openapi/example-panels.ts +110 -0
  263. package/src/components/openapi/helpers.ts +195 -20
  264. package/src/components/openapi/operation-model.ts +63 -26
  265. package/src/components/openapi/request.ts +13 -0
  266. package/src/components/openapi/snippets.ts +14 -2
  267. package/src/core/alert-diagnostics.ts +89 -0
  268. package/src/core/builtin-tags.ts +76 -0
  269. package/src/core/changelog-list.ts +87 -0
  270. package/src/core/closest-match.ts +41 -0
  271. package/src/core/code-fence-diagnostics.ts +267 -0
  272. package/src/core/code-fences.ts +43 -7
  273. package/src/core/component-diagnostics.ts +65 -12
  274. package/src/core/config-input.ts +1 -1
  275. package/src/core/config.ts +46 -1
  276. package/src/core/content-assets.ts +64 -144
  277. package/src/core/diagnostics.ts +36 -3
  278. package/src/core/directive-diagnostics.ts +265 -47
  279. package/src/core/former-routes.ts +46 -0
  280. package/src/core/git-ignored.ts +53 -0
  281. package/src/core/github.ts +5 -4
  282. package/src/core/heading-markers.ts +156 -2
  283. package/src/core/hidden-pages.ts +64 -0
  284. package/src/core/includes.ts +181 -41
  285. package/src/core/last-modified.ts +47 -10
  286. package/src/core/links.ts +438 -83
  287. package/src/core/load-module.ts +98 -9
  288. package/src/core/manifest.ts +12 -9
  289. package/src/core/markdown-mirrors.ts +52 -0
  290. package/src/core/mdx-elements.ts +153 -0
  291. package/src/core/mdx-syntax.ts +377 -0
  292. package/src/core/meta-diagnostics.ts +134 -0
  293. package/src/core/meta.ts +154 -55
  294. package/src/core/nav-diagnostics.ts +16 -4
  295. package/src/core/navigation.ts +70 -10
  296. package/src/core/ordering-prefix.ts +22 -2
  297. package/src/core/pinned-ids.ts +141 -0
  298. package/src/core/project-graph.ts +221 -29
  299. package/src/core/redirect-patterns.ts +41 -0
  300. package/src/core/relative-files.ts +145 -0
  301. package/src/core/schema.ts +17 -3
  302. package/src/core/sources/mdx-remote.ts +75 -14
  303. package/src/core/sources/normalize.ts +885 -179
  304. package/src/core/sources/notion.ts +5 -4
  305. package/src/core/sources/obsidian.ts +2 -5
  306. package/src/core/sources/types.ts +17 -0
  307. package/src/core/static-files.ts +40 -0
  308. package/src/core/static-host.ts +125 -0
  309. package/src/core/svg-images.ts +98 -0
  310. package/src/core/syntax-diagnostics.ts +491 -0
  311. package/src/core/types.ts +43 -1
  312. package/src/deploy/artifacts.ts +2 -28
  313. package/src/deploy/generated-files.ts +128 -0
  314. package/src/deploy/platforms/vercel.ts +25 -1
  315. package/src/deploy/redirects.ts +43 -16
  316. package/src/deploy/sitemap.ts +2 -1
  317. package/src/markdown/base-links.ts +87 -6
  318. package/src/markdown/code-title.ts +29 -13
  319. package/src/markdown/content-assets.ts +210 -0
  320. package/src/markdown/directives.ts +69 -5
  321. package/src/markdown/fence-language.ts +106 -0
  322. package/src/markdown/fence-meta.ts +42 -0
  323. package/src/markdown/github-alerts.ts +172 -0
  324. package/src/markdown/heading-anchors.ts +265 -56
  325. package/src/markdown/include.ts +30 -3
  326. package/src/markdown/index.ts +145 -40
  327. package/src/markdown/prompt-text.ts +95 -0
  328. package/src/markdown/relative-links.ts +37 -2
  329. package/src/markdown/route-snapshot.ts +22 -12
  330. package/src/markdown/svg-fallback.ts +51 -0
  331. package/src/markdown/title-heading.ts +134 -0
  332. package/src/migrate/migrate.ts +127 -2
  333. package/src/og/card.ts +14 -4
  334. package/src/openapi/asyncapi.ts +14 -6
  335. package/src/openapi/checks.ts +160 -15
  336. package/src/openapi/model.ts +172 -33
  337. package/src/openapi/parse.ts +13 -4
  338. package/src/openapi/render-mdx.ts +55 -9
  339. package/src/openapi/source.ts +87 -6
  340. package/src/registry/eject.ts +0 -1
  341. package/src/search/documents.ts +10 -2
  342. package/src/sources/filesystem.ts +28 -3
  343. package/src/sources/mdx-remote.ts +7 -15
  344. package/src/theme/color.ts +14 -0
  345. package/src/theme/entry.ts +22 -1
  346. package/src/theme/palette.ts +74 -1
  347. package/dist/cli/chunk-0pmb4tr4.js.map +0 -35
  348. package/dist/cli/chunk-0w6hs479.js.map +0 -10
  349. package/dist/cli/chunk-150jk7km.js +0 -52
  350. package/dist/cli/chunk-150jk7km.js.map +0 -10
  351. package/dist/cli/chunk-36bxs9c7.js.map +0 -10
  352. package/dist/cli/chunk-53jqw8z0.js +0 -89
  353. package/dist/cli/chunk-53jqw8z0.js.map +0 -10
  354. package/dist/cli/chunk-60hz265r.js.map +0 -10
  355. package/dist/cli/chunk-desvm72z.js.map +0 -14
  356. package/dist/cli/chunk-dpy8qdjf.js.map +0 -17
  357. package/dist/cli/chunk-es8h0v3j.js +0 -441
  358. package/dist/cli/chunk-es8h0v3j.js.map +0 -11
  359. package/dist/cli/chunk-kdpzw38a.js.map +0 -11
  360. package/dist/cli/chunk-nbyvgnnm.js.map +0 -36
  361. package/dist/cli/chunk-s0bdnc7v.js.map +0 -10
  362. package/dist/cli/chunk-twy7psdz.js.map +0 -10
  363. package/dist/cli/chunk-ydp3065p.js +0 -64
  364. package/dist/cli/chunk-ydp3065p.js.map +0 -10
  365. /package/dist/cli/{chunk-74wfhv79.js.map → chunk-05f21r8h.js.map} +0 -0
  366. /package/dist/cli/{chunk-9qzgg6mj.js.map → chunk-1cm0h819.js.map} +0 -0
  367. /package/dist/cli/{chunk-ms9z66nz.js.map → chunk-4rftmttv.js.map} +0 -0
  368. /package/dist/cli/{chunk-hvcxng5f.js.map → chunk-7tekknev.js.map} +0 -0
  369. /package/dist/cli/{chunk-bxwhtvw8.js.map → chunk-ahc5gc9h.js.map} +0 -0
  370. /package/dist/cli/{chunk-tfycddbt.js.map → chunk-bjk964fp.js.map} +0 -0
  371. /package/dist/cli/{chunk-dqhg54mr.js.map → chunk-dmx6cezx.js.map} +0 -0
  372. /package/dist/cli/{chunk-ave1wcbr.js.map → chunk-m9bp2hc1.js.map} +0 -0
  373. /package/dist/cli/{chunk-58bt3hag.js.map → chunk-mrvh4c88.js.map} +0 -0
  374. /package/dist/cli/{chunk-1f882900.js.map → chunk-th0pgaj5.js.map} +0 -0
  375. /package/dist/cli/{chunk-dhwtqnnd.js.map → chunk-vnbvhgmd.js.map} +0 -0
  376. /package/dist/cli/{chunk-j1x92mx9.js.map → chunk-w6e455pz.js.map} +0 -0
  377. /package/dist/cli/{chunk-y4msrwm9.js.map → chunk-xgq4eytj.js.map} +0 -0
@@ -0,0 +1,267 @@
1
+ import type { Nodes } from "mdast";
2
+ import { markdownToMdast, mdxToMdast } from "satteri";
3
+
4
+ import {
5
+ MARKDOWN_BODY_FEATURES,
6
+ MDX_BODY_FEATURES,
7
+ } from "../markdown/features.ts";
8
+ import { isKnownLanguage, normalizeFence } from "../markdown/fence-language.ts";
9
+ import {
10
+ metaTokens,
11
+ RUST_LANGUAGES,
12
+ RUST_META_KEYWORDS,
13
+ } from "../markdown/fence-meta.ts";
14
+ import { strippedLineOffset } from "./sources/normalize.ts";
15
+ import type { SourceEntry } from "./sources/types.ts";
16
+ import type { Diagnostic } from "./types.ts";
17
+
18
+ type Code = Extract<Nodes, { type: "code" }>;
19
+
20
+ /**
21
+ * Fence languages that render without a Shiki grammar on purpose: `math`,
22
+ * which Astro never highlights, and `package-install`, which `.mdx` turns
23
+ * into install tabs (and the docs describe as a plain block in `.md`).
24
+ */
25
+ const BLUME_LANGUAGES: ReadonlySet<string> = new Set([
26
+ "math",
27
+ "package-install",
28
+ ]);
29
+
30
+ /** A fence problem at a line of the parsed text, before it's placed in a file. */
31
+ interface Finding {
32
+ code: string;
33
+ line: number;
34
+ message: string;
35
+ suggestion: string;
36
+ }
37
+
38
+ /** A fence option another docs tool reads and Blume doesn't. */
39
+ interface ForeignOption {
40
+ /** Matches the option's token in the fence's info string. */
41
+ pattern: RegExp;
42
+ /** The fence languages the option belongs to; any language when unset. */
43
+ languages?: ReadonlySet<string>;
44
+ /** What comes of the option, since Blume ignores it. */
45
+ effect: string;
46
+ /** The Blume spelling, given the option's quoted value. */
47
+ instead: (value: string) => string;
48
+ }
49
+
50
+ /** `hl_lines="2 4-5"` → `{2,4-5}`: MkDocs separates lines with spaces. */
51
+ const lineRange = (value: string): string =>
52
+ `{${value
53
+ .trim()
54
+ .split(/[\s,]+/u)
55
+ .join(",")}}`;
56
+
57
+ const FOREIGN_OPTIONS: readonly ForeignOption[] = [
58
+ {
59
+ effect: "the block highlights no lines",
60
+ instead: (value) =>
61
+ `Put the lines in braces after the language instead: \`${lineRange(value)}\`.`,
62
+ pattern: /^hl_lines=["'](?<value>[^"']*)["']$/u,
63
+ },
64
+ {
65
+ effect: "the block shows no line numbers",
66
+ instead: (value) =>
67
+ `Write \`lineNumbers\` after the language instead.${
68
+ value.trim() === "1" ? "" : " Blume numbers every block from 1."
69
+ }`,
70
+ pattern: /^linenums=["'](?<value>[^"']*)["']$/u,
71
+ },
72
+ {
73
+ effect: "the block shows no line numbers",
74
+ instead: () => "Write `lineNumbers` after the language instead.",
75
+ pattern: /^showLineNumbers(?:\{\d*\})?$/u,
76
+ },
77
+ {
78
+ // Mintlify's. Like `showLineNumbers` and `wordWrap`, it never joins the
79
+ // title (see `markdown/fence-meta.ts`).
80
+ effect: "the block shows no line numbers",
81
+ instead: () =>
82
+ 'Write `lineNumbers` after the language instead. To keep the word in the block\'s title, set the title with `title="…"`.',
83
+ pattern: /^lines$/u,
84
+ },
85
+ {
86
+ effect: "the block doesn't wrap its long lines",
87
+ instead: () => "Write `wrap` after the language instead.",
88
+ pattern: /^wordWrap$/u,
89
+ },
90
+ {
91
+ effect: "the block has no title",
92
+ instead: (value) => `Write \`title="${value}"\` instead.`,
93
+ pattern: /^filename=["'](?<value>[^"']*)["']$/u,
94
+ },
95
+ {
96
+ // rustdoc's and mdBook's, on a Rust fence. They never join the title.
97
+ effect:
98
+ "it does nothing: Blume shows a code block as static text, and never compiles, tests, or runs it",
99
+ instead: () => "Remove it.",
100
+ languages: RUST_LANGUAGES,
101
+ pattern: new RegExp(`^(?:${[...RUST_META_KEYWORDS].join("|")})$`, "u"),
102
+ },
103
+ ];
104
+
105
+ /** The option `token` is in a fence whose language is `lang`, if any. */
106
+ const foreignOption = (
107
+ token: string,
108
+ lang: string
109
+ ): { option: ForeignOption; value: string } | null => {
110
+ for (const option of FOREIGN_OPTIONS) {
111
+ const match = option.pattern.exec(token);
112
+ if (match && (option.languages?.has(lang) ?? true)) {
113
+ return { option, value: match.groups?.value ?? "" };
114
+ }
115
+ }
116
+ return null;
117
+ };
118
+
119
+ /** Whether a fence's language renders as the author meant: highlighted, or plain on purpose. */
120
+ const rendersLanguage = (lang: string): boolean =>
121
+ isKnownLanguage(lang) || BLUME_LANGUAGES.has(lang);
122
+
123
+ /** A fence's language and option tokens, as the renderer reads them. */
124
+ const fenceParts = (
125
+ lang: string | null | undefined,
126
+ meta: string | null | undefined
127
+ ) => {
128
+ const fence = normalizeFence(lang, meta) ?? {
129
+ lang: lang ?? "",
130
+ meta: meta ?? null,
131
+ };
132
+ return { lang: fence.lang, meta: fence.meta, tokens: metaTokens(fence.meta) };
133
+ };
134
+
135
+ const unknownLanguage = (node: Code, line: number): Finding[] => {
136
+ const { lang } = fenceParts(node.lang, node.meta);
137
+ if (!lang || rendersLanguage(lang)) {
138
+ return [];
139
+ }
140
+ return [
141
+ {
142
+ code: "BLUME_UNKNOWN_CODE_LANGUAGE",
143
+ line,
144
+ message: `\`${lang}\` isn't a language Blume can highlight, so the code block renders as plain text.`,
145
+ suggestion:
146
+ 'Use a Shiki language id or alias (https://shiki.style/languages), or `text` for plain text. Options like a title go after the language, as in `text title="notes.txt"`.',
147
+ },
148
+ ];
149
+ };
150
+
151
+ const foreignOptions = (node: Code, line: number): Finding[] => {
152
+ const { lang, tokens } = fenceParts(node.lang, node.meta);
153
+ // A fence with options and no language reads its first option as the
154
+ // language, so that token counts too.
155
+ return [node.lang ?? "", ...tokens].flatMap((token) => {
156
+ const found = foreignOption(token, lang);
157
+ if (!found) {
158
+ return [];
159
+ }
160
+ return [
161
+ {
162
+ code: "BLUME_CODE_FENCE_OPTION",
163
+ line,
164
+ message: `\`${token}\` isn't a Blume code block option, so ${found.option.effect}.`,
165
+ suggestion: found.option.instead(found.value),
166
+ },
167
+ ];
168
+ });
169
+ };
170
+
171
+ // A fence opener's info string: the text after a run of three or more
172
+ // backticks or tildes. Matched anywhere in a line, so it also sees fences in
173
+ // list items and quotes (and fence-like inline code, which the parse below
174
+ // sorts out).
175
+ const FENCE_INFO = /(?:`{3,}|~{3,})[\t ]*(?<info>[^`\n]*)/gu;
176
+
177
+ /** Whether a fence's info string holds anything these checks report. */
178
+ const suspectInfo = (info: string): boolean => {
179
+ const [lang, ...rest] = info.trim().split(/\s+/u);
180
+ if (!lang) {
181
+ return false;
182
+ }
183
+ const parts = fenceParts(lang, rest.join(" ") || null);
184
+ return (
185
+ !rendersLanguage(parts.lang) ||
186
+ [lang, ...parts.tokens].some(
187
+ (token) => foreignOption(token, parts.lang) !== null
188
+ )
189
+ );
190
+ };
191
+
192
+ const parse = (text: string, format: "md" | "mdx"): Nodes | null => {
193
+ try {
194
+ return format === "mdx"
195
+ ? mdxToMdast(text, { features: MDX_BODY_FEATURES })
196
+ : markdownToMdast(text, { features: MARKDOWN_BODY_FEATURES });
197
+ } catch {
198
+ return null;
199
+ }
200
+ };
201
+
202
+ /**
203
+ * The fence problems in a page body, in source order. The body is parsed as
204
+ * the renderer reads it, so a fence shown inside another code block never
205
+ * counts. A page is parsed only when an info string looks like one of these
206
+ * problems, so pages without one cost a scan. A body MDX can't parse fails to
207
+ * render anyway, with its own error.
208
+ */
209
+ const fenceFindings = (text: string, format: "md" | "mdx"): Finding[] => {
210
+ const suspect = [...text.matchAll(FENCE_INFO)].some((match) =>
211
+ suspectInfo(match.groups?.info ?? "")
212
+ );
213
+ const tree = suspect ? parse(text, format) : null;
214
+ if (!tree) {
215
+ return [];
216
+ }
217
+ const found: Finding[] = [];
218
+ const walk = (node: Nodes): void => {
219
+ if (node.type === "code") {
220
+ const line = node.position?.start.line ?? 1;
221
+ found.push(...unknownLanguage(node, line), ...foreignOptions(node, line));
222
+ }
223
+ if ("children" in node) {
224
+ for (const child of node.children) {
225
+ walk(child);
226
+ }
227
+ }
228
+ };
229
+ walk(tree);
230
+ return found;
231
+ };
232
+
233
+ /**
234
+ * Warn about code fences that don't render as their author meant:
235
+ *
236
+ * - `BLUME_UNKNOWN_CODE_LANGUAGE`: a language Shiki doesn't know, so the block
237
+ * renders as plain text. Shiki itself only logs a console line naming the
238
+ * language, with no page or line.
239
+ * - `BLUME_CODE_FENCE_OPTION`: an option another docs tool reads, like MkDocs'
240
+ * `hl_lines="2 3"`, Docusaurus' `showLineNumbers`, Mintlify's `lines`, or
241
+ * rustdoc's `no_run`, which Blume ignores. None of them becomes part of the
242
+ * block's title. The suggestion gives the Blume spelling, or says to remove
243
+ * an option Blume has no use for.
244
+ *
245
+ * Lines point into the file the author wrote, a partial's own file for a
246
+ * fence an `<include>` brought in.
247
+ */
248
+ export const codeFenceDiagnostics = (
249
+ entry: SourceEntry,
250
+ sourceName: string
251
+ ): Diagnostic[] => {
252
+ const page = entry.sourcePath ?? `${sourceName}:${entry.ref}`;
253
+ const offset =
254
+ entry.bodyLineOffset ?? strippedLineOffset(entry.raw, entry.body.text);
255
+ return fenceFindings(
256
+ entry.expanded?.text ?? entry.body.text,
257
+ entry.body.format
258
+ ).map(({ line, ...finding }) => {
259
+ const origin = entry.expanded?.origins[line - 1];
260
+ return {
261
+ ...finding,
262
+ file: origin?.file ?? page,
263
+ line: origin?.line ?? line + offset,
264
+ severity: "warning",
265
+ };
266
+ });
267
+ };
@@ -12,37 +12,73 @@
12
12
  */
13
13
  const CODE_FENCE = /^(?<run>`{3,}|~{3,})/u;
14
14
 
15
- /** The open fence's delimiter char and run length, or null outside one. */
16
- export type FenceState = { delimiter: "`" | "~"; length: number } | null;
15
+ // One block-quote marker. A fence inside a quote (`> ```ts`) is written after
16
+ // one marker per level of nesting, and so is every line of its code.
17
+ const QUOTE_MARKER = /^[ \t]*>/u;
18
+
19
+ /**
20
+ * The open fence's delimiter char, its run length, and the block-quote depth
21
+ * it opened at, or null outside one.
22
+ */
23
+ export type FenceState = {
24
+ delimiter: "`" | "~";
25
+ length: number;
26
+ quotes: number;
27
+ } | null;
28
+
29
+ /** Strip up to `max` leading block-quote markers from `line`. */
30
+ const stripQuotes = (line: string, max: number) => {
31
+ let rest = line;
32
+ let depth = 0;
33
+ while (depth < max) {
34
+ const marker = QUOTE_MARKER.exec(rest);
35
+ if (!marker) {
36
+ break;
37
+ }
38
+ rest = rest.slice(marker[0].length);
39
+ depth += 1;
40
+ }
41
+ return { depth, rest };
42
+ };
17
43
 
18
44
  /**
19
45
  * Advance the fenced-code state for one line: an opening fence records its
20
46
  * delimiter and run length, only a bare run of the same character at least as
21
47
  * long closes it (CommonMark), and any other line leaves the state untouched.
48
+ * A fence inside block quotes is read past the quote markers it opened under,
49
+ * and a line without them ends the quote, and the fence with it: code takes
50
+ * no lazy continuation lines (CommonMark).
22
51
  */
23
52
  export const nextFenceState = (line: string, fence: FenceState): FenceState => {
24
- const trimmed = line.trimStart();
53
+ const { depth, rest } = stripQuotes(
54
+ line,
55
+ fence === null ? Number.POSITIVE_INFINITY : fence.quotes
56
+ );
57
+ if (fence !== null && depth < fence.quotes) {
58
+ return nextFenceState(line, null);
59
+ }
60
+ const trimmed = rest.trimStart();
25
61
  const run = trimmed.match(CODE_FENCE)?.groups?.run;
26
62
  if (run === undefined) {
27
63
  return fence;
28
64
  }
29
- const rest = trimmed.slice(run.length);
65
+ const info = trimmed.slice(run.length);
30
66
  const delimiter = run.startsWith("`") ? ("`" as const) : ("~" as const);
31
67
  if (fence === null) {
32
68
  // A backtick fence's info string cannot itself contain a backtick
33
69
  // (CommonMark) — a line-leading ```inline``` span is a paragraph, and
34
70
  // opening a phantom fence on it would swallow every heading and link
35
71
  // after it. Tilde fences carry no such rule.
36
- if (delimiter === "`" && rest.includes("`")) {
72
+ if (delimiter === "`" && info.includes("`")) {
37
73
  return fence;
38
74
  }
39
- return { delimiter, length: run.length };
75
+ return { delimiter, length: run.length, quotes: depth };
40
76
  }
41
77
  // A closing fence may be followed only by whitespace: a same-length
42
78
  // ```js line inside an open ``` block is content, not a closer.
43
79
  return fence.delimiter === delimiter &&
44
80
  run.length >= fence.length &&
45
- rest.trim() === ""
81
+ info.trim() === ""
46
82
  ? null
47
83
  : fence;
48
84
  };
@@ -1,6 +1,6 @@
1
1
  import { relative } from "pathe";
2
2
 
3
- import { BUILTIN_MDX_TAGS } from "./builtin-tags.ts";
3
+ import { BUILTIN_CHILD_PROPS, BUILTIN_MDX_TAGS } from "./builtin-tags.ts";
4
4
  import type { Diagnostic, PageRecord } from "./types.ts";
5
5
 
6
6
  /** `CardGroup` → `card-group`, matching registry item names. */
@@ -9,36 +9,89 @@ const toKebab = (tag: string): string =>
9
9
  .replaceAll(/(?<lower>[a-z0-9])(?<upper>[A-Z])/gu, "$<lower>-$<upper>")
10
10
  .toLowerCase();
11
11
 
12
+ const LIST = new Intl.ListFormat("en", { type: "conjunction" });
13
+
12
14
  /**
13
15
  * Warn when an `.mdx` page uses a `<Component>` tag that resolves to nothing —
14
16
  * a built-in, an island, or a `components.ts` override — so a typo surfaces as a
15
17
  * friendly diagnostic (with a `blume add` hint where one exists) instead of a raw
16
18
  * MDX "X is not defined" build error. `extraTags` are the project's own known
17
19
  * components (islands + overrides); `registryNames` gates the install hint.
20
+ * One warning per tag names every page that uses it, anchored to the first.
18
21
  */
19
22
  export const validateUsedComponents = (
20
23
  pages: PageRecord[],
21
24
  extraTags: Set<string>,
22
25
  registryNames: Set<string>
26
+ ): Diagnostic[] => {
27
+ const users = new Map<string, PageRecord[]>();
28
+ for (const page of pages) {
29
+ for (const tag of page.componentsUsed ?? []) {
30
+ if (!(BUILTIN_MDX_TAGS.has(tag) || extraTags.has(tag))) {
31
+ users.set(tag, [...(users.get(tag) ?? []), page]);
32
+ }
33
+ }
34
+ }
35
+ return [...users].map(([tag, using]) => {
36
+ const name = toKebab(tag);
37
+ const suggestion = registryNames.has(name)
38
+ ? `Run \`blume add ${name}\` to install it, or register <${tag}> in components.ts (mdx).`
39
+ : `Register <${tag}> in components.ts (mdx), or add an islands/${tag}.tsx component.`;
40
+ const routes = using.map((page) => page.route);
41
+ const [first] = using;
42
+ return {
43
+ code: "BLUME_UNKNOWN_COMPONENT",
44
+ file: first?.sourcePath ?? first?.id,
45
+ message:
46
+ routes.length === 1
47
+ ? `<${tag}> is used in ${LIST.format(routes)} but isn't a known component.`
48
+ : `<${tag}> is used on ${routes.length} pages but isn't a known component: ${LIST.format(routes)}.`,
49
+ severity: "warning",
50
+ suggestion,
51
+ };
52
+ });
53
+ };
54
+
55
+ const EITHER = new Intl.ListFormat("en", { type: "disjunction" });
56
+ const quoted = (names: readonly string[]): string[] =>
57
+ names.map((name) => `\`${name}\``);
58
+
59
+ /**
60
+ * Warn about each childless built-in in an `.mdx` page given props it doesn't
61
+ * take (`<Badge type="tip" text="beta" />`, VitePress's Badge). Blume's
62
+ * components ignore unknown props, so the element renders without the
63
+ * content those props carried. `extraTags` are the project's own components
64
+ * (islands + overrides): one that replaces a built-in takes its own props,
65
+ * so its uses aren't checked. A file shared by several locales is reported
66
+ * once.
67
+ */
68
+ export const unknownPropDiagnostics = (
69
+ pages: readonly PageRecord[],
70
+ extraTags: ReadonlySet<string>
23
71
  ): Diagnostic[] => {
24
72
  const diagnostics: Diagnostic[] = [];
25
73
  const seen = new Set<string>();
26
74
  for (const page of pages) {
27
- for (const tag of page.componentsUsed ?? []) {
28
- if (BUILTIN_MDX_TAGS.has(tag) || extraTags.has(tag) || seen.has(tag)) {
75
+ for (const use of page.unknownProps ?? []) {
76
+ const file = use.file ?? page.sourcePath ?? page.id;
77
+ const key = `${file}:${use.line}:${use.column}`;
78
+ if (extraTags.has(use.tag) || seen.has(key)) {
29
79
  continue;
30
80
  }
31
- seen.add(tag);
32
- const name = toKebab(tag);
33
- const suggestion = registryNames.has(name)
34
- ? `Run \`blume add ${name}\` to install it, or register <${tag}> in components.ts (mdx).`
35
- : `Register <${tag}> in components.ts (mdx), or add an islands/${tag}.tsx component.`;
81
+ seen.add(key);
82
+ const accepted = BUILTIN_CHILD_PROPS.get(use.tag) ?? [];
83
+ const takes =
84
+ accepted.length > 0
85
+ ? `, and use the props ${use.tag} takes: ${LIST.format(quoted(accepted))}`
86
+ : `; ${use.tag} takes no props`;
36
87
  diagnostics.push({
37
- code: "BLUME_UNKNOWN_COMPONENT",
38
- file: page.sourcePath ?? page.id,
39
- message: `<${tag}> is used in ${page.route} but isn't a known component.`,
88
+ code: "BLUME_UNKNOWN_PROP",
89
+ column: use.column,
90
+ file,
91
+ line: use.line,
92
+ message: `<${use.tag}> in ${page.route} has no children, and ${use.tag} doesn't take ${EITHER.format(quoted(use.props))}, so it renders without what ${use.props.length === 1 ? "that prop" : "those props"} meant to show.`,
40
93
  severity: "warning",
41
- suggestion,
94
+ suggestion: `Put the content between the tags (<${use.tag}>…</${use.tag}>)${takes}.`,
42
95
  });
43
96
  }
44
97
  }
@@ -181,7 +181,7 @@ export interface ChangelogConfig {
181
181
  export interface ContentConfig {
182
182
  /** Default page `type` for content that sets none. Defaults to `doc`. */
183
183
  defaultType?: string;
184
- /** Glob patterns to ignore. Defaults to `["**\/_*", "**\/.*"]`. Shorthand; not allowed beside `sources`. */
184
+ /** Glob patterns to ignore, on top of `["**\/_*", "**\/.*"]`; a `!pattern` entry stops ignoring that pattern, a default included. Shorthand; not allowed beside `sources`. */
185
185
  exclude?: string[];
186
186
  /** Glob patterns to include. Defaults to `["**\/*.{md,mdx}"]`. Shorthand; not allowed beside `sources`. */
187
187
  include?: string[];
@@ -3,6 +3,7 @@ import { existsSync, readFileSync } from "node:fs";
3
3
  import { basename } from "pathe";
4
4
  import { z } from "zod";
5
5
 
6
+ import { ACCENTS, invalidColorSettings } from "../theme/palette.ts";
6
7
  import type { BlumeConfig } from "./config-input.ts";
7
8
  import { applyDeploymentEnv } from "./deployment-env.ts";
8
9
  import {
@@ -261,6 +262,50 @@ const configIssues = (
261
262
  );
262
263
  };
263
264
 
265
+ const COLOR_FORMS =
266
+ "a hex value like #6340ac, rgb(), hsl(), oklch(), or a color name like rebeccapurple";
267
+
268
+ /**
269
+ * A warning for each configured color that isn't a CSS color, at the line
270
+ * that sets it. The schema has always taken any string, so this warns rather
271
+ * than failing a build that used to pass.
272
+ */
273
+ const colorDiagnostics = (
274
+ config: ResolvedConfig,
275
+ configFile: string | null
276
+ ): Diagnostic[] => {
277
+ const invalid = invalidColorSettings(config);
278
+ if (invalid.length === 0) {
279
+ return [];
280
+ }
281
+ const source =
282
+ configFile && existsSync(configFile)
283
+ ? readFileSync(configFile, "utf-8")
284
+ : undefined;
285
+ const located = diagnosticsFromIssues(
286
+ invalid.map((setting) => ({
287
+ message: `${JSON.stringify(setting.value)} isn't a CSS color, so ${
288
+ setting.path[0] === "seo"
289
+ ? "the social card renderer rejects it"
290
+ : "browsers ignore the styles that use it"
291
+ }.`,
292
+ path: setting.path,
293
+ })),
294
+ {
295
+ code: "BLUME_THEME_COLOR_INVALID",
296
+ file: configFile ?? undefined,
297
+ source,
298
+ }
299
+ );
300
+ return located.map((diagnostic, index) => ({
301
+ ...diagnostic,
302
+ severity: "warning",
303
+ suggestion: invalid[index]?.presets
304
+ ? `Use a preset (${Object.keys(ACCENTS).join(", ")}) or a CSS color: ${COLOR_FORMS}.`
305
+ : `Use a CSS color: ${COLOR_FORMS}.`,
306
+ }));
307
+ };
308
+
264
309
  /**
265
310
  * Load and validate the project config. When no config file exists, schema
266
311
  * defaults produce a fully resolved config so the zero-boilerplate path works.
@@ -364,7 +409,7 @@ export const loadConfig = async (
364
409
  seo: { ...config.seo, og: { ...config.seo.og, enabled: ogEnabled } },
365
410
  },
366
411
  configFile,
367
- diagnostics: [],
412
+ diagnostics: colorDiagnostics(config, configFile),
368
413
  themeFontsConfigured,
369
414
  };
370
415
  };