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
@@ -11,11 +11,14 @@
11
11
  * the shared store fails to count (logged, not blocked). A shared store
12
12
  * without its secrets or binding falls back to counting in memory.
13
13
  */
14
+ import { z } from "zod";
15
+
14
16
  import { clientAddressOf } from "../core/client-address.ts";
15
17
  import type { ClientContext } from "../core/client-address.ts";
16
18
  import { RATE_LIMIT_BINDING } from "./cloudflare.ts";
17
19
  import { DEFAULT_REQUESTS, DEFAULT_WINDOW } from "./memory.ts";
18
20
  import type { RateLimitAdapter } from "./schema.ts";
21
+ import { UNKEY_MAX_WINDOW, UNKEY_NAMESPACE, unkeySecrets } from "./unkey.ts";
19
22
  import { upstashSecrets } from "./upstash.ts";
20
23
 
21
24
  /** What a limiter says about one request. */
@@ -152,6 +155,68 @@ export const upstashLimiter = (
152
155
  };
153
156
  };
154
157
 
158
+ /** Unkey's rate limit endpoint. */
159
+ const UNKEY_LIMIT_URL = "https://api.unkey.com/v2/ratelimit.limit";
160
+
161
+ /**
162
+ * How long Unkey gets to count a request, in milliseconds, before the
163
+ * request is let through without it.
164
+ */
165
+ export const UNKEY_TIMEOUT = 2000;
166
+
167
+ /** The part of Unkey's reply the limiter reads. */
168
+ const unkeyReplySchema = z.object({
169
+ data: z.object({
170
+ overrideId: z.string().optional(),
171
+ reset: z.number(),
172
+ success: z.boolean(),
173
+ }),
174
+ });
175
+
176
+ /**
177
+ * Count with Unkey's rate limit API. Unkey takes identifiers of letters,
178
+ * digits, and `_.:/-`, so anything else in the key (an IPv6 host's brackets,
179
+ * an address's `%` zone) becomes `_`. Its `reset` is a timestamp on Unkey's
180
+ * clock, which this server's may not match, so `retryAfter` is kept between
181
+ * one second and the window: the configured one, or Unkey's longest when an
182
+ * override set in Unkey replaced it.
183
+ */
184
+ export const unkeyLimiter =
185
+ (
186
+ requests: number,
187
+ window: number,
188
+ namespace: string,
189
+ rootKey: string,
190
+ { fetch: fetchImpl = fetch, now = Date.now }: LimiterRuntime = {}
191
+ ): Limiter =>
192
+ async (key) => {
193
+ const response = await fetchImpl(UNKEY_LIMIT_URL, {
194
+ body: JSON.stringify({
195
+ duration: window * 1000,
196
+ identifier: key.replaceAll(/[^\w.:/-]/gu, "_"),
197
+ limit: requests,
198
+ namespace,
199
+ }),
200
+ headers: {
201
+ authorization: `Bearer ${rootKey}`,
202
+ "content-type": "application/json",
203
+ },
204
+ method: "POST",
205
+ signal: AbortSignal.timeout(UNKEY_TIMEOUT),
206
+ });
207
+ if (!response.ok) {
208
+ await response.body?.cancel();
209
+ throw new Error(`Unkey answered ${response.status}.`);
210
+ }
211
+ const { data } = unkeyReplySchema.parse(await response.json());
212
+ const seconds = Math.ceil((data.reset - now()) / 1000);
213
+ const longest = data.overrideId ? UNKEY_MAX_WINDOW : window;
214
+ return {
215
+ allowed: data.success,
216
+ retryAfter: Math.min(longest, Math.max(1, seconds)),
217
+ };
218
+ };
219
+
155
220
  /** Count with a Workers rate limiting binding. */
156
221
  export const bindingLimiter =
157
222
  (binding: RateLimitBinding, window: number): Limiter =>
@@ -185,6 +250,22 @@ export const createLimiter = (
185
250
  return null;
186
251
  }
187
252
  const { requests, window } = limitOf(adapter);
253
+ if (adapter.kind === "unkey") {
254
+ const [secret] = unkeySecrets(adapter.options);
255
+ const rootKey = runtime.secret?.(secret);
256
+ if (rootKey) {
257
+ return unkeyLimiter(
258
+ requests,
259
+ window,
260
+ adapter.options.namespace ?? UNKEY_NAMESPACE,
261
+ rootKey,
262
+ runtime
263
+ );
264
+ }
265
+ console.warn(
266
+ `Rate limiting counts in memory: set ${secret} to share the count through Unkey.`
267
+ );
268
+ }
188
269
  if (adapter.kind === "cloudflare") {
189
270
  if (runtime.binding) {
190
271
  return bindingLimiter(runtime.binding, window);
@@ -2,6 +2,7 @@ import { z } from "zod";
2
2
 
3
3
  import { cloudflareRateLimitAdapterSchema } from "./cloudflare.ts";
4
4
  import { memory, memoryAdapterSchema } from "./memory.ts";
5
+ import { unkeyAdapterSchema } from "./unkey.ts";
5
6
  import { upstashAdapterSchema } from "./upstash.ts";
6
7
 
7
8
  const ADAPTER_HINT =
@@ -12,6 +13,7 @@ export const rateLimitAdapterSchema = z.discriminatedUnion("kind", [
12
13
  cloudflareRateLimitAdapterSchema,
13
14
  memoryAdapterSchema,
14
15
  upstashAdapterSchema,
16
+ unkeyAdapterSchema,
15
17
  ]);
16
18
 
17
19
  export type RateLimitAdapter = z.output<typeof rateLimitAdapterSchema>;
@@ -0,0 +1,61 @@
1
+ import { z } from "zod";
2
+
3
+ import type { AdapterDescriptor } from "../core/adapter.ts";
4
+ import { adapterDescriptorSchema } from "../core/adapter.ts";
5
+ import { rateLimitOptionsSchema } from "./memory.ts";
6
+ import type { RateLimitOptions } from "./memory.ts";
7
+
8
+ /** The env var holding the Unkey root key, unless set. */
9
+ const UNKEY_ROOT_KEY_ENV = "UNKEY_ROOT_KEY";
10
+
11
+ /** The Unkey rate limit namespace the count is kept in, unless set. */
12
+ export const UNKEY_NAMESPACE = "docs";
13
+
14
+ /** The longest window Unkey takes, in seconds (30 days). */
15
+ export const UNKEY_MAX_WINDOW = 2_592_000;
16
+
17
+ /** Options for {@link unkey}. */
18
+ export interface UnkeyOptions extends RateLimitOptions {
19
+ /**
20
+ * The Unkey rate limit namespace, up to 512 characters. Defaults to `docs`;
21
+ * Unkey creates it on the first request.
22
+ */
23
+ namespace?: string;
24
+ /** Name of the env var holding the root key. Defaults to `UNKEY_ROOT_KEY`. */
25
+ rootKeyEnv?: string;
26
+ /** The window, in seconds: Unkey takes up to 30 days. */
27
+ window?: number;
28
+ }
29
+
30
+ export const unkeyOptionsSchema = rateLimitOptionsSchema.extend({
31
+ namespace: z.string().min(1).max(512).optional(),
32
+ rootKeyEnv: z.string().min(1).optional(),
33
+ window: z.number().int().positive().max(UNKEY_MAX_WINDOW).optional(),
34
+ });
35
+
36
+ export type UnkeyAdapter = AdapterDescriptor<"unkey", UnkeyOptions>;
37
+
38
+ export const unkeyAdapterSchema = adapterDescriptorSchema(
39
+ "unkey",
40
+ unkeyOptionsSchema
41
+ );
42
+
43
+ /** The env var the root key is read from. */
44
+ export const unkeySecrets = (options: UnkeyOptions): [string] => [
45
+ options.rootKeyEnv ?? UNKEY_ROOT_KEY_ENV,
46
+ ];
47
+
48
+ /**
49
+ * Counts with Unkey's rate limiting, which every instance on every host
50
+ * shares. Unkey counts in each region and shares the counts between them
51
+ * within moments, so a short burst split across regions can pass more than
52
+ * the limit. It talks to Unkey's REST API, so there is nothing to install;
53
+ * the root key comes from `UNKEY_ROOT_KEY`, or the env var `rootKeyEnv`
54
+ * names.
55
+ */
56
+ export const unkey = (options: UnkeyOptions = {}): UnkeyAdapter => ({
57
+ kind: "unkey",
58
+ options,
59
+ requiredSecrets: unkeySecrets(options),
60
+ runtimeDeps: [],
61
+ });
@@ -813,7 +813,6 @@ export const eject = async (
813
813
  exportEpub,
814
814
  exportPdf,
815
815
  needsReact,
816
- staged: hasStaged,
817
816
  });
818
817
 
819
818
  if (config.seo.og.enabled) {
@@ -264,7 +264,7 @@ export const registry: RegistryItem[] = [
264
264
  }),
265
265
  layoutComponent({
266
266
  description:
267
- "The site footer: a row of links, social icons, the theme toggle, and the Cookie settings link.",
267
+ "The site footer: a copyright line, a row of links, social icons, and the Cookie settings link.",
268
268
  file: "SiteFooter.astro",
269
269
  name: "footer",
270
270
  slot: "Footer",
@@ -29,6 +29,7 @@ import {
29
29
  MARKDOWN_BODY_FEATURES,
30
30
  MDX_BODY_FEATURES,
31
31
  } from "../markdown/features.ts";
32
+ import { normalizeFence } from "../markdown/fence-language.ts";
32
33
  import { pageFacets } from "./facets.ts";
33
34
 
34
35
  /** A document indexed by the client-side search providers (Orama, FlexSearch). */
@@ -177,7 +178,13 @@ const collectCode = (
177
178
  if (!walk.includeCodeBlocks) {
178
179
  return;
179
180
  }
180
- const title = parseCodeTitle(node.meta ?? undefined);
181
+ // The fence as the page renders it (`[file.ts]{2}` splits, `JSON`
182
+ // lowercases), so the indexed title is the one the page shows.
183
+ const fence = normalizeFence(node.lang, node.meta) ?? node;
184
+ const title = parseCodeTitle(
185
+ fence.meta ?? undefined,
186
+ fence.lang ?? undefined
187
+ );
181
188
  if (title) {
182
189
  out.push(title, " ");
183
190
  }
@@ -504,7 +511,8 @@ export const buildSearchDocuments = async (
504
511
  return false;
505
512
  }
506
513
  const page = pageById.get(route.id);
507
- return page ? contentIndexable(page, project.config) : false;
514
+ const { config, graph } = project;
515
+ return page ? contentIndexable(page, config, graph) : false;
508
516
  });
509
517
 
510
518
  // Built once — `downlevelComponents` rebuilds its registry per call
@@ -7,12 +7,37 @@ import { DEFAULT_CONTENT_GLOB, sharedSourceOptionsSchema } from "./shared.ts";
7
7
 
8
8
  /** What `include` defaults to: every Markdown/MDX file under the root. */
9
9
  export const DEFAULT_CONTENT_INCLUDE = [DEFAULT_CONTENT_GLOB];
10
- /** What `exclude` defaults to: underscore- and dot-prefixed paths. */
10
+ /** What `exclude` always holds: underscore- and dot-prefixed paths. */
11
11
  export const DEFAULT_CONTENT_EXCLUDE = ["**/_*", "**/.*"];
12
12
 
13
+ /**
14
+ * A source's `exclude` as the scan applies it: the defaults, plus the
15
+ * author's patterns. A `!pattern` entry takes that exact pattern back out, a
16
+ * default included, so `["!**\/_*"]` publishes `_`-prefixed files. The glob
17
+ * library can't negate an ignore pattern itself.
18
+ */
19
+ export const resolveContentExclude = (
20
+ patterns: readonly string[]
21
+ ): string[] => {
22
+ const kept = new Set(
23
+ patterns.flatMap((pattern) =>
24
+ pattern.startsWith("!") ? [pattern.slice(1)] : []
25
+ )
26
+ );
27
+ return [
28
+ ...new Set([
29
+ ...DEFAULT_CONTENT_EXCLUDE,
30
+ ...patterns.filter((pattern) => !pattern.startsWith("!")),
31
+ ]),
32
+ ].filter((pattern) => !kept.has(pattern));
33
+ };
34
+
13
35
  /** Options for {@link filesystem}. */
14
36
  export interface FilesystemOptions extends SharedSourceOptions {
15
- /** Glob patterns to ignore. Defaults to `["**\/_*", "**\/.*"]`. */
37
+ /**
38
+ * Glob patterns to ignore, on top of `["**\/_*", "**\/.*"]`. A `!pattern`
39
+ * entry stops ignoring that pattern, a default included.
40
+ */
16
41
  exclude?: string[];
17
42
  /** Glob patterns to include. Defaults to `["**\/*.{md,mdx}"]`. */
18
43
  include?: string[];
@@ -21,7 +46,7 @@ export interface FilesystemOptions extends SharedSourceOptions {
21
46
  }
22
47
 
23
48
  export const filesystemOptionsSchema = sharedSourceOptionsSchema.extend({
24
- exclude: z.array(z.string()).default(DEFAULT_CONTENT_EXCLUDE),
49
+ exclude: z.array(z.string()).default([]).transform(resolveContentExclude),
25
50
  include: z.array(z.string()).default(DEFAULT_CONTENT_INCLUDE),
26
51
  root: z.string().default("docs"),
27
52
  });
@@ -50,27 +50,19 @@ export const mdxRemoteAdapterSchema = adapterDescriptorSchema(
50
50
  mdxRemoteOptionsSchema
51
51
  );
52
52
 
53
- // The hosts the source ever sends `GITHUB_TOKEN` to (the same set the fetch
54
- // in core/sources/mdx-remote.ts checks), so only an adapter reading from
55
- // GitHub declares the variable.
56
- const GITHUB_HOSTS = new Set(["api.github.com", "raw.githubusercontent.com"]);
57
-
58
- const readsFromGithub = (options: MdxRemoteOptions): boolean =>
59
- options.github !== undefined ||
60
- (options.url !== undefined &&
61
- URL.canParse(options.url) &&
62
- GITHUB_HOSTS.has(new URL(options.url).hostname));
63
-
64
53
  /**
65
54
  * Remote Markdown/MDX fetched over HTTP. Enumerate files explicitly against a
66
55
  * raw `url` base, or from a GitHub repo subtree via `github`. A private repo's
67
- * token comes from `GITHUB_TOKEN` — never inline it here. The adapter declares
68
- * the variable only when it reads from GitHub, since a `url` on any other host
69
- * is never sent the token.
56
+ * token comes from `GITHUB_TOKEN` — never inline it here. Only `github`
57
+ * declares the variable: it lists files through GitHub's API, whose
58
+ * unauthenticated rate limit a build can run into even for a public
59
+ * repository. A raw `url` needs no token for a public repository, so a fetch
60
+ * that fails for want of one names the variable instead (see
61
+ * core/sources/mdx-remote.ts).
70
62
  */
71
63
  export const mdxRemote = (options: MdxRemoteOptions): MdxRemoteAdapter => ({
72
64
  kind: "mdx-remote",
73
65
  options,
74
- requiredSecrets: readsFromGithub(options) ? ["GITHUB_TOKEN"] : [],
66
+ requiredSecrets: options.github ? ["GITHUB_TOKEN"] : [],
75
67
  runtimeDeps: [],
76
68
  });
@@ -470,6 +470,20 @@ export const parseColor = (value: string): Rgba | null => {
470
470
  : null;
471
471
  };
472
472
 
473
+ // Color syntax `parseColor` leaves unread but a browser takes: a custom
474
+ // property, `color-mix()`, `color()`, `light-dark()`, a relative color
475
+ // (`oklch(from var(--x) l c h)`), and the `currentcolor` keyword.
476
+ const UNREAD_COLOR =
477
+ /^(?:currentcolor|(?:color|color-mix|hsla?|hwb|lab|lch|light-dark|oklab|oklch|rgba?|var)\(.*\))$/isu;
478
+
479
+ /**
480
+ * Whether `value` is a CSS color: one `parseColor` reads, or a color function
481
+ * or keyword it leaves unread. Words that name no color (`deep purple`), and
482
+ * hex values with a wrong digit count or no `#`, are not.
483
+ */
484
+ export const isCssColor = (value: string): boolean =>
485
+ parseColor(value) !== null || UNREAD_COLOR.test(value.trim());
486
+
473
487
  /** `color` painted over an opaque `backdrop`. */
474
488
  export const composite = (color: Rgba, backdrop: Rgba): Rgba => {
475
489
  const mix = (top: number, bottom: number): number =>
@@ -63,13 +63,18 @@ const TOKEN_DEFAULTS = `:root {
63
63
  --blume-action: var(--blume-accent);
64
64
  --blume-action-foreground: var(--blume-accent-foreground);
65
65
  --blume-code-background: oklch(0.99 0 0);
66
- /* Shiki notation transformers: line/word highlight, diff add/remove. */
66
+ /* Shiki notation transformers: line/word highlight, diff add/remove, and
67
+ error/warning lines. */
67
68
  --blume-code-highlight: oklch(0.55 0.16 255 / 0.1);
68
69
  --blume-code-highlight-border: oklch(0.55 0.16 255 / 0.55);
69
70
  --blume-code-add: oklch(0.72 0.16 150 / 0.16);
70
71
  --blume-code-add-border: oklch(0.52 0.15 150 / 0.7);
71
72
  --blume-code-remove: oklch(0.66 0.21 22 / 0.16);
72
73
  --blume-code-remove-border: oklch(0.55 0.2 22 / 0.7);
74
+ --blume-code-error: oklch(0.66 0.21 22 / 0.16);
75
+ --blume-code-error-border: oklch(0.55 0.2 22 / 0.7);
76
+ --blume-code-warning: oklch(0.8 0.16 80 / 0.2);
77
+ --blume-code-warning-border: oklch(0.66 0.15 70 / 0.75);
73
78
  --blume-code-word: oklch(0.55 0.16 255 / 0.16);
74
79
  --blume-code-word-border: oklch(0.55 0.16 255 / 0.5);
75
80
  --blume-radius: 0.75rem;
@@ -129,6 +134,10 @@ const TOKEN_DEFAULTS = `:root {
129
134
  --blume-code-add-border: oklch(0.72 0.16 150 / 0.7);
130
135
  --blume-code-remove: oklch(0.72 0.21 22 / 0.22);
131
136
  --blume-code-remove-border: oklch(0.7 0.2 22 / 0.7);
137
+ --blume-code-error: oklch(0.72 0.21 22 / 0.22);
138
+ --blume-code-error-border: oklch(0.7 0.2 22 / 0.7);
139
+ --blume-code-warning: oklch(0.82 0.16 80 / 0.18);
140
+ --blume-code-warning-border: oklch(0.82 0.16 80 / 0.7);
132
141
  --blume-code-word: oklch(0.7 0.14 255 / 0.22);
133
142
  --blume-code-word-border: oklch(0.7 0.14 255 / 0.55);
134
143
  }`;
@@ -866,6 +875,18 @@ pre[data-line-numbers] .line::before {
866
875
  box-shadow: inset 2px 0 0 0 var(--blume-code-remove-border);
867
876
  }
868
877
 
878
+ /* Error and warning lines (\`// [!code error]\`, \`// [!code warning]\`) are
879
+ highlighted lines with a level class, tinted red and amber. */
880
+ .line.highlighted.error {
881
+ background-color: var(--blume-code-error) !important;
882
+ box-shadow: inset 2px 0 0 0 var(--blume-code-error-border);
883
+ }
884
+
885
+ .line.highlighted.warning {
886
+ background-color: var(--blume-code-warning) !important;
887
+ box-shadow: inset 2px 0 0 0 var(--blume-code-warning-border);
888
+ }
889
+
869
890
  /* Word highlight (\`// [!code word:x]\`) wraps matches in an inline span. The
870
891
  element selector keeps it ahead of the transparent token-span rule. */
871
892
  span.highlighted-word {
@@ -1,5 +1,5 @@
1
1
  import type { ResolvedConfig } from "../core/schema.ts";
2
- import { composite, contrastRatio, parseColor } from "./color.ts";
2
+ import { composite, contrastRatio, isCssColor, parseColor } from "./color.ts";
3
3
  import type { Rgba } from "./color.ts";
4
4
 
5
5
  /** Per-mode CSS colors. */
@@ -243,3 +243,76 @@ ${root}
243
243
  }
244
244
  ${dark}`;
245
245
  };
246
+
247
+ /** A configured color: where the config sets it, and its value. */
248
+ export interface ColorSetting {
249
+ /** The config path, e.g. `["theme", "accent", "dark"]`. */
250
+ path: string[];
251
+ /** Whether the field also takes an accent preset name (`teal`). */
252
+ presets: boolean;
253
+ value: string;
254
+ }
255
+
256
+ const MODES = ["light", "dark"] as const;
257
+
258
+ /**
259
+ * A per-mode field's settings. Both modes holding one value (a string, which
260
+ * the schema copies into each) is one setting at the field itself, so a
261
+ * mistake is reported once, at the key that set it.
262
+ */
263
+ const modeSettings = (
264
+ path: string[],
265
+ value: { dark?: string; light?: string } | undefined,
266
+ presets: boolean
267
+ ): ColorSetting[] => {
268
+ if (value?.light !== undefined && value.light === value.dark) {
269
+ return [{ path, presets, value: value.light }];
270
+ }
271
+ return MODES.flatMap((mode) => {
272
+ const color = value?.[mode];
273
+ return color === undefined
274
+ ? []
275
+ : [{ path: [...path, mode], presets, value: color }];
276
+ });
277
+ };
278
+
279
+ const OG_PALETTE_KEYS = [
280
+ "accent",
281
+ "background",
282
+ "border",
283
+ "foreground",
284
+ "muted",
285
+ ] as const;
286
+
287
+ /**
288
+ * The configured colors that aren't CSS colors (`"deep purple"`, a hex value
289
+ * missing its `#`): `theme.accent`, `theme.action`, and `theme.background`,
290
+ * and the OG card's `seo.og.palette`. The schema takes any string, so a
291
+ * mistake would otherwise reach the stylesheet, where browsers drop it, or
292
+ * the card renderer, which rejects it. Preset names are CSS color names too.
293
+ */
294
+ export const invalidColorSettings = (
295
+ config: ResolvedConfig
296
+ ): ColorSetting[] => {
297
+ const { theme } = config;
298
+ const palette = config.seo.og.palette ?? {};
299
+ return [
300
+ ...modeSettings(["theme", "accent"], theme.accent, true),
301
+ ...(theme.action === undefined
302
+ ? []
303
+ : [{ path: ["theme", "action"], presets: true, value: theme.action }]),
304
+ ...modeSettings(["theme", "background"], theme.background, false),
305
+ ...OG_PALETTE_KEYS.flatMap((key) => {
306
+ const value = palette[key];
307
+ return value === undefined
308
+ ? []
309
+ : [
310
+ {
311
+ path: ["seo", "og", "palette", key],
312
+ presets: key === "accent",
313
+ value,
314
+ },
315
+ ];
316
+ }),
317
+ ].filter((setting) => !isCssColor(setting.value));
318
+ };