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
@@ -1,6 +1,12 @@
1
1
  ---
2
2
  import { EN_UI } from "../../core/i18n-ui.ts";
3
3
  import type { UIStrings } from "../../core/i18n-ui.ts";
4
+ import {
5
+ isBuiltInMcpClient,
6
+ mcpClients,
7
+ mcpMenuRows,
8
+ } from "../../core/mcp-clients.ts";
9
+ import type { McpMenuEntry } from "../../core/mcp-clients.ts";
4
10
  import { NEW_TAB_ATTRS } from "../../core/new-tab.ts";
5
11
  import { openInChatProviders } from "../../core/open-in-chat.ts";
6
12
  import type { OpenInChatProvider } from "../../core/open-in-chat.ts";
@@ -18,6 +24,11 @@ interface Props {
18
24
  mcpUrl?: string | null;
19
25
  /** Display name of the MCP server (defaults to the site title). */
20
26
  mcpName?: string | null;
27
+ /**
28
+ * "Connect to MCP" clients to list after "Copy server URL", in order, with
29
+ * custom labels resolved; omitting the prop lists every built-in client.
30
+ */
31
+ mcpMenu?: readonly McpMenuEntry[];
21
32
  /**
22
33
  * "Open in chat" providers to list, in order; an empty list hides the
23
34
  * action, and omitting the prop shows every provider.
@@ -40,6 +51,7 @@ const {
40
51
  exportEpub,
41
52
  mcpUrl,
42
53
  mcpName,
54
+ mcpMenu,
43
55
  openInChat,
44
56
  strings,
45
57
  divider = true,
@@ -89,6 +101,10 @@ const providers = (openInChat ?? openInChatProviders).map((key) => ({
89
101
  // keep that separator only while a configured list preserves v0 first.
90
102
  const chatSeparator = providers.length > 1 && providers[0]?.key === "v0";
91
103
 
104
+ // `agents.mcp.clients` picks and orders the clients after "Copy server URL",
105
+ // with a rule between the commands to copy and the install links.
106
+ const mcpRows = mcpMenuRows(mcpMenu ?? mcpClients);
107
+
92
108
  // The block renders once, for the right rail. Below `xl` the script moves it
93
109
  // into the mobile "On this page" dropdown, and the global styles below the
94
110
  // markup restyle it there from that container.
@@ -230,29 +246,57 @@ const panelClass =
230
246
  {a.copyServerUrl}
231
247
  </span>
232
248
  </button>
233
- <button class={menuRowClass} data-mcp-copy-claude type="button">
234
- <span set:html={LOGOS.claude} />
235
- <span class="flex-1" data-mcp-claude-label>
236
- {a.copyClaudeCode}
237
- </span>
238
- </button>
239
- <button class={menuRowClass} data-mcp-copy-codex type="button">
240
- <span set:html={LOGOS.codex} />
241
- <span class="flex-1" data-mcp-codex-label>
242
- {a.copyCodex}
243
- </span>
244
- </button>
245
- <hr class="my-1 border-border border-t" />
246
- <a class={menuRowClass} data-mcp-cursor rel="noreferrer">
247
- <span set:html={LOGOS.cursor} />
248
- <span class="flex-1">{a.addToCursor}</span>
249
- <Icon name="external-link" size={13} />
250
- </a>
251
- <a class={menuRowClass} data-mcp-vscode rel="noreferrer">
252
- <Icon name="code" size={16} />
253
- <span class="flex-1">{a.addToVscode}</span>
254
- <Icon name="external-link" size={13} />
255
- </a>
249
+ {mcpRows.map(({ client, rule }) => (
250
+ <>
251
+ {rule && <hr class="my-1 border-border border-t" />}
252
+ {client === "claude-code" && (
253
+ <button
254
+ class={menuRowClass}
255
+ data-mcp-copy-claude
256
+ type="button"
257
+ >
258
+ <span set:html={LOGOS.claude} />
259
+ <span class="flex-1" data-mcp-claude-label>
260
+ {a.copyClaudeCode}
261
+ </span>
262
+ </button>
263
+ )}
264
+ {client === "codex" && (
265
+ <button class={menuRowClass} data-mcp-copy-codex type="button">
266
+ <span set:html={LOGOS.codex} />
267
+ <span class="flex-1" data-mcp-codex-label>
268
+ {a.copyCodex}
269
+ </span>
270
+ </button>
271
+ )}
272
+ {client === "cursor" && (
273
+ <a class={menuRowClass} data-mcp-cursor rel="noreferrer">
274
+ <span set:html={LOGOS.cursor} />
275
+ <span class="flex-1">{a.addToCursor}</span>
276
+ <Icon name="external-link" size={13} />
277
+ </a>
278
+ )}
279
+ {client === "vscode" && (
280
+ <a class={menuRowClass} data-mcp-vscode rel="noreferrer">
281
+ <Icon name="code" size={16} />
282
+ <span class="flex-1">{a.addToVscode}</span>
283
+ <Icon name="external-link" size={13} />
284
+ </a>
285
+ )}
286
+ {!isBuiltInMcpClient(client) && (
287
+ <button
288
+ class={menuRowClass}
289
+ data-mcp-copy-command={client.command}
290
+ type="button"
291
+ >
292
+ <Icon name={client.icon ?? "terminal"} size={16} />
293
+ <span class="flex-1" data-mcp-command-label>
294
+ {client.label}
295
+ </span>
296
+ </button>
297
+ )}
298
+ </>
299
+ ))}
256
300
  </div>
257
301
  </details>
258
302
  )
@@ -299,6 +343,7 @@ const panelClass =
299
343
  </style>
300
344
 
301
345
  <script>
346
+ import { mcpClientCommand } from "../../core/mcp-clients.ts";
302
347
  import { copyDeferredText, copyText, flashLabel } from "../copy-feedback.ts";
303
348
  import { installDropdownDismiss } from "../dropdown-dismiss.ts";
304
349
  import { mountBase } from "../islands/base-path.ts";
@@ -572,6 +617,19 @@ hr { border: 0; border-top: 1px solid #ddd; margin: 2em 0; }`;
572
617
  root.querySelector("[data-mcp-codex-label]")
573
618
  )
574
619
  );
620
+ for (const button of root.querySelectorAll<HTMLElement>(
621
+ "[data-mcp-copy-command]"
622
+ )) {
623
+ button.addEventListener("click", () =>
624
+ copy(
625
+ mcpClientCommand(button.dataset.mcpCopyCommand ?? "", {
626
+ name: id,
627
+ url: mcpUrl,
628
+ }),
629
+ button.querySelector("[data-mcp-command-label]")
630
+ )
631
+ );
632
+ }
575
633
  }
576
634
 
577
635
  const label = root.querySelector("[data-blume-copy-label]");
@@ -9,6 +9,8 @@ import type { UIStrings } from "../../core/i18n-ui.ts";
9
9
  import { resolveDateFormatOptions } from "../../core/date-format.ts";
10
10
  import type { ResolvedDateFormat } from "../../core/schema.ts";
11
11
  import type { OpenInChatProvider } from "../../core/open-in-chat.ts";
12
+ import { mcpMenuEntries } from "../../core/mcp-clients.ts";
13
+ import type { McpClientEntry } from "../../core/mcp-clients.ts";
12
14
  import type {
13
15
  BlumeBanner,
14
16
  BlumeClientData,
@@ -114,7 +116,15 @@ interface Props {
114
116
  banner?: BlumeBanner | null;
115
117
  analytics?: BlumeDataConfig["analytics"] | null;
116
118
  navigation: Navigation;
117
- mcp?: { name: string; route: string } | null;
119
+ /**
120
+ * The hosted MCP server. `clients` lists the "Connect to MCP" menu's
121
+ * clients in order, and defaults to every built-in one.
122
+ */
123
+ mcp?: {
124
+ clients?: readonly McpClientEntry[];
125
+ name: string;
126
+ route: string;
127
+ } | null;
118
128
  page: { title: string; description?: string; route: string };
119
129
  headings: Heading[];
120
130
  imageZoom?: boolean;
@@ -471,6 +481,11 @@ const markdownMirror = isBare
471
481
  // Needs a configured site to be useful, so the menu is hidden without one.
472
482
  const mcpUrl =
473
483
  mcp && siteUrl ? new URL(withMountedBase(mcp.route), siteUrl).href : null;
484
+ // Custom clients' labels resolve to the page's locale. Without a list,
485
+ // PageActions shows every built-in client.
486
+ const mcpMenu = mcp?.clients
487
+ ? mcpMenuEntries(mcp.clients, locale, data.config.i18n?.defaultLocale)
488
+ : undefined;
474
489
 
475
490
  // Scope the sidebar (and the breadcrumbs/pagination derived from it) to the
476
491
  // active tab's section, so a multi-section site drills each tab into its own
@@ -910,14 +925,16 @@ const localeRedirect = localeTargets
910
925
  <slot />
911
926
  </article>
912
927
  {
913
- !isApiOperation && showChrome && (
928
+ showChrome && (
914
929
  <div
915
930
  class:list={[
916
931
  // Aligned with the content column, not centered under the
917
- // prose measure: a rail page's, a wide page's, or a center
918
- // page's wider one.
919
- apiRail && "xl:pe-[30.5rem]",
920
- (apiRail || modeLayout.width === "full") && "[&>*]:max-w-none",
932
+ // prose measure: an API page's (beside its example or
933
+ // request column), a wide page's, or a center page's wider
934
+ // one.
935
+ isApiLayout && "xl:pe-[30.5rem]",
936
+ (isApiLayout || modeLayout.width === "full") &&
937
+ "[&>*]:max-w-none",
921
938
  modeLayout.width === "center" && "[&>*]:max-w-[54rem]",
922
939
  ]}
923
940
  data-blume-page-end
@@ -966,6 +983,7 @@ const localeRedirect = localeTargets
966
983
  editUrl={editUrl}
967
984
  exportEpub={exportEpub}
968
985
  exportPdf={exportPdf}
986
+ mcpMenu={mcpMenu}
969
987
  mcpName={mcp?.name}
970
988
  mcpUrl={mcpUrl}
971
989
  openInChat={openInChat}
@@ -1005,6 +1023,8 @@ const localeRedirect = localeTargets
1005
1023
  ></button>
1006
1024
  <script>
1007
1025
  import type { Zoom } from "medium-zoom";
1026
+ import { copyableCode } from "../code-copy.ts";
1027
+ import { languageLabel } from "../code-language.ts";
1008
1028
  import { copyText, createCopyFlash } from "../copy-feedback.ts";
1009
1029
  import { syncDrawerInert } from "./drawer-inert.ts";
1010
1030
  import { chromeIcons as icons } from "../../theme/chrome-icons.ts";
@@ -1081,27 +1101,6 @@ const localeRedirect = localeTargets
1081
1101
  return panel.matches("pre") ? panel : panel.querySelector("pre");
1082
1102
  };
1083
1103
 
1084
- const languageLabels: Record<string, string> = {
1085
- astro: "Astro",
1086
- bash: "Bash",
1087
- css: "CSS",
1088
- html: "HTML",
1089
- js: "JavaScript",
1090
- json: "JSON",
1091
- jsx: "JSX",
1092
- md: "Markdown",
1093
- mdx: "MDX",
1094
- plaintext: "Text",
1095
- sh: "Shell",
1096
- shell: "Shell",
1097
- ts: "TypeScript",
1098
- tsx: "TSX",
1099
- txt: "Text",
1100
- yaml: "YAML",
1101
- yml: "YAML",
1102
- zsh: "Zsh",
1103
- };
1104
-
1105
1104
  // Per-page setup, run on the initial load and again after every
1106
1105
  // client-router swap (which replaces the body with server-rendered
1107
1106
  // markup that has none of this applied). Everything in here binds to
@@ -1176,7 +1175,14 @@ const localeRedirect = localeTargets
1176
1175
  };
1177
1176
  const flash = createCopyFlash(setChecked, copiedLabel);
1178
1177
  button.addEventListener("click", async () => {
1179
- if (await copyText(codeText(readPre()))) {
1178
+ // A terminal session copies its commands alone (see
1179
+ // code-copy.ts), keyed off the fence's own language id.
1180
+ const pre = readPre();
1181
+ const language =
1182
+ pre instanceof HTMLElement
1183
+ ? (pre.dataset.blumeLanguage ?? pre.dataset.language)
1184
+ : undefined;
1185
+ if (await copyText(copyableCode(codeText(pre), language))) {
1180
1186
  flash();
1181
1187
  }
1182
1188
  });
@@ -1277,10 +1283,7 @@ const localeRedirect = localeTargets
1277
1283
  if (pre instanceof HTMLElement) {
1278
1284
  pre.dataset.blumeLanguage = language;
1279
1285
  }
1280
- pre.setAttribute(
1281
- "data-language",
1282
- languageLabels[language.toLowerCase()] ?? language
1283
- );
1286
+ pre.setAttribute("data-language", languageLabel(language));
1284
1287
  }
1285
1288
  pre.classList.add("group", "relative");
1286
1289
  // The code element is the scroll container (see the theme entry), but
@@ -1,8 +1,8 @@
1
1
  ---
2
2
  // The site footer, below the content on every page: one row, as tall as the
3
- // header, with `footer.links` on one side and the social profile icons on the
4
- // other. The site's repository (`github`, unless `navigation.repo` hides it)
5
- // is the first icon; the footer is the only place it's linked. With `consent` configured the links end with the
3
+ // header, with `footer.links` on one side, the social profile icons on the
4
+ // other, and `footer.copyright` centered between them. The site's repository
5
+ // (`github`, unless `navigation.repo` hides it) is the first icon; the footer is the only place it's linked. With `consent` configured the links end with the
6
6
  // Cookie settings link, which reopens the reader's consent choices. Renders
7
7
  // nothing when there's none of these; a `components.ts` `Footer` replaces it.
8
8
  import data from "blume:data";
@@ -12,6 +12,7 @@ import type { Navigation } from "../../core/types.ts";
12
12
  import { EN_UI } from "../../core/i18n-ui.ts";
13
13
  import type { UIStrings } from "../../core/i18n-ui.ts";
14
14
  import { routeSetFor } from "../../core/locale-links.ts";
15
+ import { resolveLocalizable } from "../../core/localizable.ts";
15
16
  import { newTabAttrs } from "../../core/new-tab.ts";
16
17
  import Icon from "../Icon.astro";
17
18
  import { withBase } from "../islands/base-path.ts";
@@ -21,8 +22,9 @@ import { footerSocialLinks } from "./footer-socials.ts";
21
22
  interface Props {
22
23
  footer?: BlumeDataConfig["footer"];
23
24
  /**
24
- * Active locale of the page: labels written as per-locale maps show its
25
- * entry, and internal links move into it (see `footer-links.ts`).
25
+ * Active locale of the page: the copyright and labels written as
26
+ * per-locale maps show its entry, and internal links move into it (see
27
+ * `footer-links.ts`).
26
28
  */
27
29
  locale?: string;
28
30
  /** The page's navigation: its `repoUrl` is the repository link. */
@@ -40,6 +42,14 @@ const socials = footerSocialLinks(
40
42
  ? { href: repoUrl, label: { ...EN_UI.nav, ...ui?.nav }.githubRepository }
41
43
  : null
42
44
  );
45
+ const copyright =
46
+ footer?.copyright === undefined
47
+ ? null
48
+ : resolveLocalizable(
49
+ footer.copyright,
50
+ locale,
51
+ data.config.i18n?.defaultLocale
52
+ );
43
53
  const links = footerLinks(footer?.links ?? [], {
44
54
  basePath: data.config.basePath,
45
55
  i18n: data.config.i18n,
@@ -58,9 +68,17 @@ const hasLinks = links.length > 0 || consentSettings !== null;
58
68
  ---
59
69
 
60
70
  {
61
- (hasLinks || socials.length > 0) && (
71
+ (copyright !== null || hasLinks || socials.length > 0) && (
62
72
  <footer class="border-border border-t px-4 md:px-6" data-blume-footer>
63
- <div class="flex min-h-16 flex-wrap items-center justify-between gap-x-6 gap-y-3 py-3">
73
+ {/* With a copyright, three columns from `md`, the outer two sharing
74
+ what it leaves so it sits at the true center; without one, the
75
+ links keep the row's full width. Narrower, the row wraps. */}
76
+ <div
77
+ class:list={[
78
+ "flex min-h-16 flex-wrap items-center gap-x-6 gap-y-3 py-3",
79
+ copyright !== null && "md:grid md:grid-cols-[1fr_auto_1fr]",
80
+ ]}
81
+ >
64
82
  {hasLinks && (
65
83
  <ul class="flex flex-wrap items-center gap-x-5 gap-y-2 text-sm">
66
84
  {links.map((link) => (
@@ -87,10 +105,15 @@ const hasLinks = links.length > 0 || consentSettings !== null;
87
105
  )}
88
106
  </ul>
89
107
  )}
108
+ {copyright !== null && (
109
+ <p class="text-muted-foreground text-sm md:col-start-2">
110
+ {copyright}
111
+ </p>
112
+ )}
90
113
  {/* Pulled out by the buttons' padding, so the glyphs line up with the
91
114
  footer's edge, and pushed to the far side even with no links. */}
92
115
  {socials.length > 0 && (
93
- <ul class="-me-[9px] -my-[9px] ms-auto flex flex-wrap items-center gap-1">
116
+ <ul class="-me-[9px] -my-[9px] ms-auto flex flex-wrap items-center gap-1 md:col-start-3">
94
117
  {socials.map((social) => (
95
118
  <li>
96
119
  <a
@@ -273,6 +273,20 @@ export const getPagination = (flat: FlatPage[], route: string) => {
273
273
  /** A group node in the sidebar tree. */
274
274
  export type NavGroup = Extract<NavNode, { kind: "group" }>;
275
275
 
276
+ /**
277
+ * Whether a `group`-mode disclosure renders open: it holds the current page,
278
+ * its meta forces it open (`collapsed: false`), or it is the only row at the
279
+ * top of the sidebar, where starting closed would leave a sidebar of one row
280
+ * to click before any link shows (a wrapper folder around every page). An
281
+ * explicit `collapsed: true` keeps that lone group closed.
282
+ */
283
+ export const groupStartsOpen = (
284
+ group: NavGroup,
285
+ active: boolean,
286
+ lone: boolean
287
+ ): boolean =>
288
+ active || group.collapsed === false || (lone && group.collapsed !== true);
289
+
276
290
  /**
277
291
  * The group whose own page `route` is, when that page lists the group's
278
292
  * pages: its `directory` (inherited when the group sets none) is `card` or
@@ -32,7 +32,8 @@ interface PagefindEntry {
32
32
 
33
33
  // Pagefind names each page after its built file (`quickstart/index.html`), so
34
34
  // its URLs end in a slash. Blume serves pages without one (`trailingSlash:
35
- // "never"`): hosts redirect the slashed URL, and `blume preview` 404s it.
35
+ // "never"`): Vercel, `blume dev`, and `blume preview` redirect the slashed
36
+ // URL, so linking it costs a hop.
36
37
  const TRAILING_SLASH = /(?<=.)\/(?=[#?]|$)/u;
37
38
 
38
39
  /**
@@ -159,6 +159,7 @@ const model =
159
159
  parameters: params,
160
160
  path: ref.path,
161
161
  requestBody,
162
+ responses,
162
163
  schemas,
163
164
  security,
164
165
  // An operation's own `servers` override its path item's, which
@@ -268,6 +269,7 @@ const callbackParts = (entry: CallbackEntry) => ({
268
269
  <ParametersTable parameters={params} schemas={schemas} />
269
270
  {requestBody && (
270
271
  <RequestBody
272
+ components={components}
271
273
  direction={bodyDirection}
272
274
  expandAll={spec.expandSchemas}
273
275
  requestBody={requestBody}
@@ -321,9 +323,11 @@ const callbackParts = (entry: CallbackEntry) => ({
321
323
  />
322
324
  {parts.requestBody && (
323
325
  <RequestBody
326
+ components={components}
324
327
  direction="response"
325
328
  expandAll={spec.expandSchemas}
326
329
  level={4}
330
+ name={`callback-${entry.key}`}
327
331
  requestBody={parts.requestBody}
328
332
  schemas={schemas}
329
333
  />
@@ -28,16 +28,22 @@ interface Props {
28
28
  copy?: boolean;
29
29
  /** Render tab labels in a monospace face (status codes). */
30
30
  mono?: boolean;
31
+ /**
32
+ * Names this tab set in its element ids, for a heading that repeats on
33
+ * the page (a callback's request body examples). Defaults to the heading.
34
+ */
35
+ name?: string;
31
36
  }
32
37
 
33
- const { heading, panels, copy = false, mono = false } = Astro.props;
38
+ const { heading, panels, copy = false, mono = false, name = heading } =
39
+ Astro.props;
34
40
 
35
41
  // A stable per-instance id prefix so tab/tabpanel pairs can reference each
36
42
  // other without colliding across the multiple PanelTabs on one page (the
37
- // heading is unique per rail group; panel keys are unique within a group).
43
+ // name is unique per tab set; panel keys are unique within a set).
38
44
  const NON_ID = /[^a-z0-9]+/gu;
39
45
  const idFor = (kind: "tab" | "panel", key: string): string =>
40
- ["panel-tabs", heading, kind, key]
46
+ ["panel-tabs", name, kind, key]
41
47
  .map((part) => part.toLowerCase().replace(NON_ID, "-"))
42
48
  .join("-");
43
49
 
@@ -1,22 +1,31 @@
1
1
  ---
2
+ import data from "blume:data";
3
+
4
+ import { highlightCode } from "../../markdown/index.ts";
2
5
  import Description from "./Description.astro";
3
- import type { SchemaLike } from "./helpers.ts";
6
+ import { preferredMedia } from "./example-panels.ts";
7
+ import {
8
+ type ComponentsLike,
9
+ type MediaLike,
10
+ namedExamples,
11
+ type SchemaLike,
12
+ toJson,
13
+ } from "./helpers.ts";
14
+ import PanelTabs from "./PanelTabs.astro";
4
15
  import type { SchemaDirection } from "./schema-tree.ts";
5
16
  import SchemaTable from "./SchemaTable.astro";
6
17
 
7
- interface MediaTypeLike {
8
- schema?: SchemaLike;
9
- }
10
-
11
18
  interface RequestBodyLike {
12
19
  description?: string;
13
20
  required?: boolean;
14
- content?: Record<string, MediaTypeLike>;
21
+ content?: Record<string, MediaLike>;
15
22
  }
16
23
 
17
24
  interface Props {
18
25
  requestBody: RequestBodyLike;
19
26
  schemas: Record<string, SchemaLike>;
27
+ /** The document's `components`, which `$ref`'d examples resolve against. */
28
+ components?: ComponentsLike;
20
29
  expandAll?: boolean;
21
30
  /** The heading's level: 2 on an operation, deeper inside a callback. */
22
31
  level?: number;
@@ -25,20 +34,38 @@ interface Props {
25
34
  * callback, which the API sends, reads like a response instead.
26
35
  */
27
36
  direction?: SchemaDirection;
37
+ /** Names the body's example tabs in their element ids (one per callback). */
38
+ name?: string;
28
39
  }
29
40
 
30
41
  const {
31
42
  requestBody,
32
43
  schemas,
44
+ components,
33
45
  expandAll = false,
34
46
  level = 2,
35
47
  direction = "request",
48
+ name = "request-body",
36
49
  } = Astro.props;
37
50
 
38
- const entries = Object.entries(requestBody.content ?? {});
39
- const chosen = entries.find(([type]) => type.includes("json")) ?? entries[0];
51
+ const chosen = preferredMedia(requestBody.content);
40
52
  const contentType = chosen?.[0] ?? "application/json";
41
53
  const schema = chosen?.[1]?.schema ?? {};
54
+
55
+ // A body whose `examples` map names two or more shows each, under its name:
56
+ // the code samples and Try it start from the first one only.
57
+ const examplePanels = await Promise.all(
58
+ (chosen ? namedExamples(chosen[1], schemas, direction, components) : []).map(
59
+ async (example) => ({
60
+ html: await highlightCode(toJson(example.value), "json", {
61
+ icons: false,
62
+ themes: data.config.codeThemes,
63
+ }),
64
+ key: example.key,
65
+ label: example.label,
66
+ })
67
+ )
68
+ );
42
69
  ---
43
70
 
44
71
  <section class:list={[level > 2 ? "mt-5" : "mt-8"]}>
@@ -72,4 +99,11 @@ const schema = chosen?.[1]?.schema ?? {};
72
99
  schemas={schemas}
73
100
  />
74
101
  </div>
102
+ {
103
+ examplePanels.length > 0 && (
104
+ <div class="not-prose mt-4">
105
+ <PanelTabs copy heading="Examples" name={`${name}-examples`} panels={examplePanels} />
106
+ </div>
107
+ )
108
+ }
75
109
  </section>
@@ -4,27 +4,21 @@ import data from "blume:data";
4
4
  import { highlightCode } from "../../markdown/index.ts";
5
5
  import {
6
6
  type ComponentsLike,
7
- responseExample,
7
+ type MediaLike,
8
8
  type SchemaLike,
9
- type SpecValue,
10
9
  toJson,
11
10
  } from "./helpers.ts";
12
11
  import type { CustomCodeSample } from "./code-samples.ts";
12
+ import {
13
+ type ExamplePanel,
14
+ payloadPanels as payloadExamples,
15
+ type ResponseLike,
16
+ responsePanels as responseExamples,
17
+ } from "./example-panels.ts";
13
18
  import { languageSamplePanels } from "./sample-panels.ts";
14
19
  import PanelTabs from "./PanelTabs.astro";
15
20
  import type { RequestSample, SampleLanguage } from "./snippets.ts";
16
21
 
17
- interface MediaTypeLike {
18
- schema?: SchemaLike;
19
- example?: SpecValue;
20
- examples?: SpecValue;
21
- }
22
-
23
- interface ResponseLike {
24
- description?: string;
25
- content?: Record<string, MediaTypeLike>;
26
- }
27
-
28
22
  interface Props {
29
23
  /** The document's `components`, which `$ref`'d examples resolve against. */
30
24
  components?: ComponentsLike;
@@ -34,7 +28,7 @@ interface Props {
34
28
  * A webhook's body: the rail shows it as a "Payload" example per media
35
29
  * type in place of client code, since the API sends the request.
36
30
  */
37
- payload?: Record<string, MediaTypeLike>;
31
+ payload?: Record<string, MediaLike>;
38
32
  sample?: RequestSample;
39
33
  languages?: SampleLanguage[];
40
34
  responses: Record<string, ResponseLike>;
@@ -51,18 +45,26 @@ const {
51
45
  schemas,
52
46
  } = Astro.props;
53
47
 
54
- /** A media type's example, highlighted, or null when it has none. */
55
- const highlightExample = async (media: MediaTypeLike, type: string) => {
56
- const example = responseExample(media, schemas, components);
57
- if (example === undefined || example === null) {
58
- return null;
59
- }
60
- const json = type.includes("json");
61
- return highlightCode(
62
- json || typeof example !== "string" ? toJson(example) : example,
63
- json ? "json" : "text",
64
- { icons: false, themes: data.config.codeThemes }
65
- );
48
+ /**
49
+ * One example tab, highlighted. A payload that isn't JSON shows a string
50
+ * example as written; a response example always reads as JSON.
51
+ */
52
+ const highlightPanel = async (panel: ExamplePanel, asText: boolean) => {
53
+ const { value } = panel;
54
+ const text = asText && typeof value === "string";
55
+ return {
56
+ html:
57
+ value === undefined || value === null
58
+ ? null
59
+ : await highlightCode(
60
+ text ? value : toJson(value),
61
+ text ? "text" : "json",
62
+ { icons: false, themes: data.config.codeThemes }
63
+ ),
64
+ key: panel.key,
65
+ label: panel.label,
66
+ text: panel.text,
67
+ };
66
68
  };
67
69
 
68
70
  // Hand-written samples carry no `lang` key, so the playground leaves them
@@ -78,12 +80,9 @@ const customPanels = await Promise.all(
78
80
  }))
79
81
  );
80
82
  const payloadPanels = await Promise.all(
81
- Object.entries(payload ?? {}).map(async ([type, media]) => ({
82
- html: await highlightExample(media, type),
83
- key: type,
84
- label: type,
85
- text: "No example payload.",
86
- }))
83
+ payloadExamples(payload, schemas, components).map((panel) =>
84
+ highlightPanel(panel, !panel.type.includes("json"))
85
+ )
87
86
  );
88
87
  const requestPanels = [
89
88
  ...customPanels,
@@ -91,28 +90,10 @@ const requestPanels = [
91
90
  ? await languageSamplePanels(languages, sample, data.config.codeThemes)
92
91
  : []),
93
92
  ];
94
-
95
93
  const responsePanels = await Promise.all(
96
- Object.entries(responses).map(async ([status, response]) => {
97
- const media =
98
- Object.entries(response.content ?? {}).find(([type]) =>
99
- type.includes("json")
100
- )?.[1] ?? Object.values(response.content ?? {})[0];
101
- const example = media ? responseExample(media, schemas, components) : undefined;
102
- const html =
103
- example === undefined || example === null
104
- ? null
105
- : await highlightCode(toJson(example), "json", {
106
- icons: false,
107
- themes: data.config.codeThemes,
108
- });
109
- return {
110
- html,
111
- key: status,
112
- label: status,
113
- text: response.description || "No example response.",
114
- };
115
- })
94
+ responseExamples(responses, schemas, components).map((panel) =>
95
+ highlightPanel(panel, false)
96
+ )
116
97
  );
117
98
  ---
118
99