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
@@ -0,0 +1,110 @@
1
+ import { namedExamples, responseExample } from "./helpers.ts";
2
+ import type {
3
+ ComponentsLike,
4
+ MediaLike,
5
+ SchemaLike,
6
+ SpecValue,
7
+ } from "./helpers.ts";
8
+
9
+ /**
10
+ * The example tabs of an operation's rail (`RequestPanel.astro`): one per
11
+ * response status, or per webhook payload media type — and one per named
12
+ * example where its `examples` map names two or more, labeled by the
13
+ * example's `summary` (else its key) after the status or media type, so
14
+ * every recorded example shows, under its name. Values are left for the
15
+ * component to highlight.
16
+ */
17
+
18
+ /** One tab before highlighting. */
19
+ export interface ExamplePanel {
20
+ key: string;
21
+ label: string;
22
+ /** The status or media type the tab is under. */
23
+ name: string;
24
+ /** The media type the value is an example of. */
25
+ type: string;
26
+ /** The example, or `null`/`undefined` when there's none to show. */
27
+ value: SpecValue;
28
+ /** What the tab says when there's no example. */
29
+ text: string;
30
+ }
31
+
32
+ /** A response: its description and its body's media types. */
33
+ export interface ResponseLike {
34
+ description?: string;
35
+ content?: Record<string, MediaLike>;
36
+ }
37
+
38
+ /** The media entry a body shows: the first JSON-ish one, else the first. */
39
+ export const preferredMedia = (
40
+ content?: Record<string, MediaLike>
41
+ ): [string, MediaLike] | undefined => {
42
+ const entries = Object.entries(content ?? {});
43
+ return entries.find(([type]) => type.includes("json")) ?? entries[0];
44
+ };
45
+
46
+ /**
47
+ * The tabs for one media type: each named example, or the one example
48
+ * `responseExample` gives. Webhook payloads and responses both travel from
49
+ * the API, so both read like a response.
50
+ */
51
+ const mediaPanels = (
52
+ name: string,
53
+ type: string,
54
+ media: MediaLike | undefined,
55
+ text: string,
56
+ schemas: Record<string, SchemaLike>,
57
+ components: ComponentsLike | undefined
58
+ ): ExamplePanel[] => {
59
+ const named = media
60
+ ? namedExamples(media, schemas, "response", components)
61
+ : [];
62
+ if (named.length > 0) {
63
+ return named.map((example) => ({
64
+ key: `${name}-${example.key}`,
65
+ label: `${name} · ${example.label}`,
66
+ name,
67
+ text,
68
+ type,
69
+ value: example.value,
70
+ }));
71
+ }
72
+ return [
73
+ {
74
+ key: name,
75
+ label: name,
76
+ name,
77
+ text,
78
+ type,
79
+ value: media ? responseExample(media, schemas, components) : undefined,
80
+ },
81
+ ];
82
+ };
83
+
84
+ /** The Response tabs: per status, per named example. */
85
+ export const responsePanels = (
86
+ responses: Record<string, ResponseLike>,
87
+ schemas: Record<string, SchemaLike>,
88
+ components?: ComponentsLike
89
+ ): ExamplePanel[] =>
90
+ Object.entries(responses).flatMap(([status, response]) => {
91
+ const [type, media] = preferredMedia(response.content) ?? ["", undefined];
92
+ return mediaPanels(
93
+ status,
94
+ type,
95
+ media,
96
+ response.description || "No example response.",
97
+ schemas,
98
+ components
99
+ );
100
+ });
101
+
102
+ /** A webhook's Payload tabs: per media type, per named example. */
103
+ export const payloadPanels = (
104
+ payload: Record<string, MediaLike> | undefined,
105
+ schemas: Record<string, SchemaLike>,
106
+ components?: ComponentsLike
107
+ ): ExamplePanel[] =>
108
+ Object.entries(payload ?? {}).flatMap(([type, media]) =>
109
+ mediaPanels(type, type, media, "No example payload.", schemas, components)
110
+ );
@@ -150,7 +150,10 @@ export const soleAllOfMember = (schema: SchemaLike): SchemaLike | undefined =>
150
150
  /**
151
151
  * Resolve one level of `$ref` against the document's component schemas. A
152
152
  * single-member `allOf` wrapper resolves like its member, with the wrapper's
153
- * own keywords on top: its `description` is about this field.
153
+ * own keywords on top: its `description` is about this field. So does a
154
+ * `$ref` with keywords beside it, which OpenAPI 3.1 allows and .NET writes
155
+ * for every enum-typed property: the `description` beside the `$ref` is the
156
+ * property's, the referenced one the type's.
154
157
  */
155
158
  export const resolveSchema = (
156
159
  schemas: Record<string, SchemaLike>,
@@ -166,8 +169,10 @@ export const resolveSchema = (
166
169
  }
167
170
  if (isString(schema.$ref)) {
168
171
  const name = REF_PATTERN.exec(schema.$ref)?.groups?.name;
169
- if (name && schemas[name]) {
170
- return schemas[name];
172
+ const target = name ? schemas[name] : undefined;
173
+ if (target) {
174
+ const { $ref: _ref, ...own } = schema;
175
+ return Object.keys(own).length > 0 ? { ...target, ...own } : target;
171
176
  }
172
177
  }
173
178
  return schema;
@@ -325,19 +330,129 @@ export const objectProperties = (
325
330
  return { properties: [...properties.entries()], required };
326
331
  };
327
332
 
333
+ /** Which way an example travels: to the API, or back from it. */
334
+ export type ExampleDirection = "request" | "response";
335
+
336
+ const isRecord = (value: SpecValue): value is Record<string, SpecValue> =>
337
+ typeof value === "object" && value !== null && !Array.isArray(value);
338
+
339
+ /** Keywords whose values are data, not schemas, so never rewritten. */
340
+ const DATA_KEYWORDS = new Set([
341
+ "const",
342
+ "default",
343
+ "enum",
344
+ "example",
345
+ "examples",
346
+ ]);
347
+
348
+ /** Keywords beside a `$ref` that give the value an example of its own. */
349
+ const VALUE_KEYWORDS = ["const", "default", "example", "examples"];
350
+
351
+ /**
352
+ * A schema as openapi-sampler should read it: each `$ref` with an example
353
+ * value beside it (`example`, `examples`, `default`, `const`) wrapped as a
354
+ * single-member `allOf`, so the value is used. The sampler follows a `$ref`
355
+ * before it looks at anything beside it, which OpenAPI 3.1 allows and .NET
356
+ * writes, so `{ $ref: Status, example: "active" }` sampled the referenced
357
+ * enum's first member instead.
358
+ */
359
+ const sampleable = (schema: SpecValue): SpecValue => {
360
+ if (Array.isArray(schema)) {
361
+ return schema.map(sampleable);
362
+ }
363
+ if (!isRecord(schema)) {
364
+ return schema;
365
+ }
366
+ const out: Record<string, SpecValue> = {};
367
+ for (const [key, value] of Object.entries(schema)) {
368
+ out[key] = DATA_KEYWORDS.has(key) ? value : sampleable(value);
369
+ }
370
+ const { $ref: ref, ...own } = out;
371
+ if (!(isString(ref) && VALUE_KEYWORDS.some((key) => key in own))) {
372
+ return out;
373
+ }
374
+ const members = Array.isArray(own.allOf) ? own.allOf : [];
375
+ return { ...own, allOf: [{ $ref: ref }, ...members] };
376
+ };
377
+
378
+ /** {@link sampleable} component schemas, once per document. */
379
+ const sampleableSchemas = new WeakMap<
380
+ Record<string, SchemaLike>,
381
+ Record<string, SchemaLike>
382
+ >();
383
+
384
+ const sampleableComponents = (
385
+ schemas: Record<string, SchemaLike>
386
+ ): Record<string, SchemaLike> => {
387
+ const cached = sampleableSchemas.get(schemas);
388
+ if (cached) {
389
+ return cached;
390
+ }
391
+ // SAFETY: rewriting a `$ref` node into an `allOf` wrapper keeps every
392
+ // entry a schema.
393
+ const rewritten = sampleable(schemas) as Record<string, SchemaLike>;
394
+ sampleableSchemas.set(schemas, rewritten);
395
+ return rewritten;
396
+ };
397
+
398
+ /**
399
+ * An example value with the properties that don't travel `direction` left
400
+ * out, at every depth its schema describes: `readOnly` ones from a request,
401
+ * which the server sets, and `writeOnly` ones from a response, which it
402
+ * never returns. openapi-sampler skips those only in the values it builds; a
403
+ * declared example (a model's `example`, which TypeSpec writes, or a media
404
+ * type's) keeps them, so a request sample would send the model's `id`. A
405
+ * property the schema doesn't describe stays as written.
406
+ */
407
+ export const forDirection = (
408
+ value: SpecValue,
409
+ schema: SchemaLike | undefined,
410
+ schemas: Record<string, SchemaLike>,
411
+ direction: ExampleDirection
412
+ ): SpecValue => {
413
+ if (!schema) {
414
+ return value;
415
+ }
416
+ if (Array.isArray(value)) {
417
+ const { items } = resolveSchema(schemas, schema);
418
+ return items
419
+ ? value.map((item) => forDirection(item, items, schemas, direction))
420
+ : value;
421
+ }
422
+ if (!isRecord(value)) {
423
+ return value;
424
+ }
425
+ const properties = new Map(objectProperties(schema, schemas).properties);
426
+ const omitted = direction === "request" ? "readOnly" : "writeOnly";
427
+ const out: Record<string, SpecValue> = {};
428
+ for (const [name, member] of Object.entries(value)) {
429
+ const property = properties.get(name);
430
+ if (
431
+ property?.[omitted] === true ||
432
+ resolveSchema(schemas, property)[omitted] === true
433
+ ) {
434
+ continue;
435
+ }
436
+ out[name] = forDirection(member, property, schemas, direction);
437
+ }
438
+ return out;
439
+ };
440
+
328
441
  /**
329
442
  * Build a representative example value for a schema via openapi-sampler
330
443
  * (Redoc's generator): declared `example`/`const`/`default`/`enum` values
331
- * win, formats produce realistic placeholders (`email`, `uuid`, `date-time`),
332
- * and circular `$ref` chains — which keeping refs intact allows — terminate
333
- * safely. A request sample skips `readOnly` fields (a server-generated field
334
- * has no place in one); a response sample skips `writeOnly` fields (a
335
- * password the client sends never comes back) and keeps the `readOnly` ones.
444
+ * win, a value beside a `$ref` included, formats produce realistic
445
+ * placeholders (`email`, `uuid`, `date-time`), and circular `$ref` chains —
446
+ * which keeping refs intact allows — terminate safely. A request sample
447
+ * leaves out `readOnly` fields (a server-generated field has no place in
448
+ * one); a response sample leaves out `writeOnly` fields (a password the
449
+ * client sends never comes back) and keeps the `readOnly` ones. That holds
450
+ * inside a declared value the sampler copies too (see {@link forDirection}).
336
451
  */
337
452
  export const exampleValue = (
338
453
  schema: SchemaLike | undefined,
339
454
  schemas: Record<string, SchemaLike>,
340
- direction: "request" | "response" = "request"
455
+ direction: ExampleDirection = "request"
341
456
  ): SpecValue => {
342
457
  if (!schema) {
343
458
  return null;
@@ -346,11 +461,12 @@ export const exampleValue = (
346
461
  try {
347
462
  // SAFETY: SchemaLike structurally covers the JSONSchema7 fields the
348
463
  // sampler reads, and the sampler only ever assembles JSON values.
349
- return sample(
350
- schema as Parameters<typeof sample>[0],
464
+ const value = sample(
465
+ sampleable(schema) as Parameters<typeof sample>[0],
351
466
  { quiet: true, skipReadOnly: !response, skipWriteOnly: response },
352
- { components: { schemas } }
467
+ { components: { schemas: sampleableComponents(schemas) } }
353
468
  ) as SpecValue;
469
+ return forDirection(value, schema, schemas, direction);
354
470
  } catch {
355
471
  // An unresolvable $ref or malformed schema is a spec problem the schema
356
472
  // tables already surface; a sample is best-effort.
@@ -364,9 +480,6 @@ export interface ExampleCarrier {
364
480
  examples?: SpecValue;
365
481
  }
366
482
 
367
- const isRecord = (value: SpecValue): value is Record<string, SpecValue> =>
368
- typeof value === "object" && value !== null && !Array.isArray(value);
369
-
370
483
  /**
371
484
  * The example a parameter or media type declares: its `example`, else the
372
485
  * first `value` in its `examples` map — inline, or behind a `$ref` to
@@ -398,18 +511,80 @@ export const declaredExample = (
398
511
  return undefined;
399
512
  };
400
513
 
514
+ /** A media type: the schema of a body and the examples it declares. */
515
+ export type MediaLike = ExampleCarrier & { schema?: SchemaLike };
516
+
517
+ /**
518
+ * A media type's example for a body travelling `direction`: the declared
519
+ * one, else a sample built from its schema, with the properties that don't
520
+ * travel that way left out (see {@link forDirection}).
521
+ */
522
+ export const mediaExample = (
523
+ media: MediaLike,
524
+ schemas: Record<string, SchemaLike>,
525
+ direction: ExampleDirection,
526
+ components?: ComponentsLike
527
+ ): SpecValue => {
528
+ const declared = declaredExample(media, components);
529
+ return declared === undefined
530
+ ? exampleValue(media.schema, schemas, direction)
531
+ : forDirection(declared, media.schema, schemas, direction);
532
+ };
533
+
401
534
  /**
402
535
  * A response media type's example for the Response panel: the declared one,
403
536
  * else a sample built for the response direction (`readOnly` kept,
404
- * `writeOnly` skipped).
537
+ * `writeOnly` left out).
405
538
  */
406
539
  export const responseExample = (
407
- media: ExampleCarrier & { schema?: SchemaLike },
540
+ media: MediaLike,
408
541
  schemas: Record<string, SchemaLike>,
409
542
  components?: ComponentsLike
410
- ): SpecValue =>
411
- declaredExample(media, components) ??
412
- exampleValue(media.schema, schemas, "response");
543
+ ): SpecValue => mediaExample(media, schemas, "response", components);
544
+
545
+ /** One of a media type's named examples, as a tab or the Markdown copy shows it. */
546
+ export interface NamedExample {
547
+ /** The example's key in the `examples` map. */
548
+ key: string;
549
+ /** Its `summary`, else its key. */
550
+ label: string;
551
+ value: SpecValue;
552
+ }
553
+
554
+ /**
555
+ * Every example a media type's `examples` map names, in order, `$ref`s to
556
+ * `#/components/examples` resolved, each labeled by its `summary` or else
557
+ * its key, with the properties that don't travel `direction` left out. Empty
558
+ * unless the map names two or more: Scalar's upgrade files a 3.0 `example`
559
+ * as the one entry `default`, which is no name to show, and a lone example
560
+ * is the one {@link mediaExample} already gives.
561
+ */
562
+ export const namedExamples = (
563
+ media: MediaLike,
564
+ schemas: Record<string, SchemaLike>,
565
+ direction: ExampleDirection,
566
+ components?: ComponentsLike
567
+ ): NamedExample[] => {
568
+ if (media.example !== undefined || !isRecord(media.examples)) {
569
+ return [];
570
+ }
571
+ const named: NamedExample[] = [];
572
+ for (const [key, entry] of Object.entries(media.examples)) {
573
+ const example = isRecord(entry)
574
+ ? resolveComponentRef(entry, components, "examples")
575
+ : undefined;
576
+ if (example?.value === undefined) {
577
+ continue;
578
+ }
579
+ named.push({
580
+ key,
581
+ label:
582
+ isString(example.summary) && example.summary ? example.summary : key,
583
+ value: forDirection(example.value, media.schema, schemas, direction),
584
+ });
585
+ }
586
+ return named.length > 1 ? named : [];
587
+ };
413
588
 
414
589
  /** Pretty-print a JSON value for an example/code block. */
415
590
  export const toJson = <T>(value: T): string => JSON.stringify(value, null, 2);
@@ -2,6 +2,7 @@ import { withServerDefaults } from "../../openapi/model.ts";
2
2
  import {
3
3
  declaredExample,
4
4
  exampleValue,
5
+ mediaExample,
5
6
  objectProperties,
6
7
  parameterDescription,
7
8
  resolveSchema,
@@ -172,7 +173,9 @@ const jsonContentType = (
172
173
  /**
173
174
  * Typed field inputs when the body schema is a flat object of primitives —
174
175
  * anything nested (object/array properties) falls back to the raw JSON editor,
175
- * where structure is easier to edit than in exploded form fields.
176
+ * where structure is easier to edit than in exploded form fields. A
177
+ * `readOnly` property gets no field: the server sets it, so a request
178
+ * doesn't send it, as the request body's schema table leaves it out.
176
179
  */
177
180
  const bodyFields = (
178
181
  schema: SchemaLike | undefined,
@@ -184,13 +187,13 @@ const bodyFields = (
184
187
  return undefined;
185
188
  }
186
189
  const { properties, required } = objectProperties(resolved, schemas);
187
- if (properties.length === 0) {
188
- return undefined;
189
- }
190
190
  const defaults = isExampleObject(example) ? example : undefined;
191
191
  const fields: PlaygroundBodyField[] = [];
192
192
  for (const [name, property] of properties) {
193
193
  const propertySchema = resolveSchema(schemas, property);
194
+ if (property.readOnly === true || propertySchema.readOnly === true) {
195
+ continue;
196
+ }
194
197
  const type = scalarType(property, schemas);
195
198
  if (
196
199
  !(type in PRIMITIVE_TYPES) ||
@@ -203,17 +206,12 @@ const bodyFields = (
203
206
  description: propertySchema.description,
204
207
  enum: propertySchema.enum?.map(String),
205
208
  name,
206
- // A `readOnly` property's `required` binds responses only: a request
207
- // shouldn't send the server-generated field at all.
208
- required:
209
- required.has(name) &&
210
- property.readOnly !== true &&
211
- propertySchema.readOnly !== true,
209
+ required: required.has(name),
212
210
  type,
213
211
  value: inputValue(defaults?.[name]),
214
212
  });
215
213
  }
216
- return fields;
214
+ return fields.length > 0 ? fields : undefined;
217
215
  };
218
216
 
219
217
  /**
@@ -232,9 +230,7 @@ const modelBody = (
232
230
  return undefined;
233
231
  }
234
232
  const [contentType, mediaType] = media;
235
- const exampleData =
236
- declaredExample(mediaType, components) ??
237
- exampleValue(mediaType.schema, schemas);
233
+ const exampleData = mediaExample(mediaType, schemas, "request", components);
238
234
  const encoding = bodyEncoding(contentType);
239
235
  const raw = encoding === "raw";
240
236
  const body: PlaygroundBody = {
@@ -344,6 +340,37 @@ export const playgroundAuth = (
344
340
  return input ? [input] : [];
345
341
  });
346
342
 
343
+ /** A response, `$ref` resolved: the media types its body comes in. */
344
+ interface ResponseLike {
345
+ content?: Record<string, MediaTypeLike>;
346
+ }
347
+
348
+ /**
349
+ * The `Accept` header a request sends: the first JSON media type among the
350
+ * operation's responses, success responses first, without its parameters.
351
+ * A request with none reads as a browser's to frameworks like Laravel, so an
352
+ * unauthenticated call got a 500 from the login redirect it tried instead
353
+ * of the 401 the API documents. `undefined` when no response is JSON.
354
+ */
355
+ export const acceptHeader = (
356
+ responses?: Record<string, ResponseLike>
357
+ ): string | undefined => {
358
+ const entries = Object.entries(responses ?? {});
359
+ const success = ([status]: [string, ResponseLike]): boolean =>
360
+ status.startsWith("2");
361
+ for (const [, response] of [
362
+ ...entries.filter(success),
363
+ ...entries.filter((entry) => !success(entry)),
364
+ ]) {
365
+ for (const type of Object.keys(response.content ?? {})) {
366
+ if (bodyEncoding(type) === "json") {
367
+ return (type.split(";")[0] ?? type).trim();
368
+ }
369
+ }
370
+ }
371
+ return undefined;
372
+ };
373
+
347
374
  /** Derive the playground request model for one operation, at build time. */
348
375
  export const operationModel = (args: {
349
376
  method: string;
@@ -351,21 +378,31 @@ export const operationModel = (args: {
351
378
  /** Pre-merged/resolved (`mergeParameters` output). */
352
379
  parameters: ParameterLike[];
353
380
  requestBody?: { content?: Record<string, MediaTypeLike> };
381
+ /** The operation's responses, `$ref`s resolved: they set its `Accept`. */
382
+ responses?: Record<string, ResponseLike>;
354
383
  /** The operation's effective servers (`effectiveServers` output). */
355
384
  servers: ServerLike[];
356
385
  schemas: Record<string, SchemaLike>;
357
386
  /** The document's `components`, which `$ref`'d examples resolve against. */
358
387
  components?: ComponentsLike;
359
388
  security: OperationSecurity;
360
- }): PlaygroundModel => ({
361
- auth: playgroundAuth(args.security),
362
- authOptional: args.security.optional,
363
- body: modelBody(args.requestBody, args.schemas, args.components),
364
- method: args.method.toUpperCase(),
365
- params: modelParams(args.parameters, args.schemas, args.components),
366
- path: args.path,
367
- // Variables resolve to their defaults: the samples and Send need a real URL.
368
- servers: args.servers.map((server) =>
369
- withServerDefaults(server.url ?? "", server.variables)
370
- ),
371
- });
389
+ }): PlaygroundModel => {
390
+ const model: PlaygroundModel = {
391
+ auth: playgroundAuth(args.security),
392
+ authOptional: args.security.optional,
393
+ body: modelBody(args.requestBody, args.schemas, args.components),
394
+ method: args.method.toUpperCase(),
395
+ params: modelParams(args.parameters, args.schemas, args.components),
396
+ path: args.path,
397
+ // Variables resolve to their defaults: the samples and Send need a real
398
+ // URL.
399
+ servers: args.servers.map((server) =>
400
+ withServerDefaults(server.url ?? "", server.variables)
401
+ ),
402
+ };
403
+ const accept = acceptHeader(args.responses);
404
+ if (accept) {
405
+ model.accept = accept;
406
+ }
407
+ return model;
408
+ };
@@ -108,6 +108,11 @@ export interface PlaygroundModel {
108
108
  /** First security alternative (AND set). */
109
109
  auth: PlaygroundAuthInput[];
110
110
  authOptional: boolean;
111
+ /**
112
+ * The `Accept` header every request sends: the operation's first JSON
113
+ * response media type. Absent when none of its responses is JSON.
114
+ */
115
+ accept?: string;
111
116
  }
112
117
 
113
118
  export interface AuthValue {
@@ -431,6 +436,14 @@ export const buildRequest = (
431
436
  headers[param.name] = value;
432
437
  }
433
438
  }
439
+ // The JSON the operation answers with, unless a header parameter already
440
+ // names the type it wants.
441
+ if (
442
+ model.accept &&
443
+ !Object.keys(headers).some((name) => name.toLowerCase() === "accept")
444
+ ) {
445
+ headers.Accept = model.accept;
446
+ }
434
447
 
435
448
  const { body, bodyValue, formData } = model.body
436
449
  ? bodyFor(model.body, values.body)
@@ -1008,12 +1008,16 @@ const LANGUAGES: SampleLanguage[] = [
1008
1008
  /** Every id `codeSamples` accepts for a generated sample. */
1009
1009
  export const SAMPLE_LANGUAGE_IDS = LANGUAGES.map((language) => language.id);
1010
1010
 
1011
- /** Other spellings of the ids, including every alias Mintlify accepts. */
1011
+ /**
1012
+ * Other spellings of the ids, including every alias Mintlify accepts and
1013
+ * ReadMe's ids for the languages Blume generates (`cplusplus`).
1014
+ */
1012
1015
  const ALIASES = new Map([
1013
1016
  [".net", "dotnet"],
1014
1017
  ["bash", "curl"],
1015
1018
  ["c#", "csharp"],
1016
1019
  ["c++", "cpp"],
1020
+ ["cplusplus", "cpp"],
1017
1021
  ["cs", "csharp"],
1018
1022
  ["dot-net", "dotnet"],
1019
1023
  ["flutter", "dart"],
@@ -1056,9 +1060,17 @@ export const sampleLanguageInfo = (raw: string): SampleLanguageInfo | null => {
1056
1060
  /** `openapi()`'s default `codeSamples`, which hand-written endpoint pages always use. */
1057
1061
  export const DEFAULT_SAMPLE_LANGUAGES = ["curl", "js", "python"];
1058
1062
 
1063
+ /**
1064
+ * The configured ids Blume generates no sample for, as written: the ones
1065
+ * {@link sampleLanguages} drops, which the reference source warns about.
1066
+ */
1067
+ export const unknownSampleLanguages = (ids: string[] | false): string[] =>
1068
+ ids === false ? [] : ids.filter((raw) => sampleLanguageInfo(raw) === null);
1069
+
1059
1070
  /**
1060
1071
  * The sample languages to render, resolved from config ids (unknown ids
1061
- * dropped), in order. `false` and an empty list both render none.
1072
+ * dropped, with a warning from the reference source), in order. `false` and
1073
+ * an empty list both render none.
1062
1074
  */
1063
1075
  export const sampleLanguages = (ids: string[] | false): SampleLanguage[] => {
1064
1076
  if (ids === false) {
@@ -0,0 +1,89 @@
1
+ import type { Nodes } from "mdast";
2
+ import { markdownToMdast } from "satteri";
3
+
4
+ import { MARKDOWN_BODY_FEATURES } from "../markdown/features.ts";
5
+ import { alertMarkerOf } from "../markdown/github-alerts.ts";
6
+ import type { AlertMarker } from "../markdown/github-alerts.ts";
7
+ import { strippedLineOffset } from "./sources/normalize.ts";
8
+ import type { SourceEntry } from "./sources/types.ts";
9
+ import type { Diagnostic } from "./types.ts";
10
+
11
+ // A quote line that may open with an alert marker.
12
+ const ALERT_LINE = /^[\t >]*>[\t ]*\[![a-z]+\]/imu;
13
+
14
+ /** An alert quote in an `.md` body: its marker and the quote's line. */
15
+ interface AlertFinding extends AlertMarker {
16
+ line: number;
17
+ }
18
+
19
+ /**
20
+ * The GitHub alerts in an `.md` body, in source order: quotes that open with
21
+ * `[!NOTE]` and the rest, as `markdown/github-alerts.ts` reads them in `.mdx`
22
+ * (an escaped marker, or one quoted inside another alert, is no alert there
23
+ * either). A page is parsed only when a line looks like one.
24
+ */
25
+ const alertFindings = (text: string): AlertFinding[] => {
26
+ if (!ALERT_LINE.test(text)) {
27
+ return [];
28
+ }
29
+ const tree: Nodes = markdownToMdast(text, {
30
+ features: MARKDOWN_BODY_FEATURES,
31
+ });
32
+ const found: AlertFinding[] = [];
33
+ const walk = (node: Nodes): void => {
34
+ if (node.type === "blockquote") {
35
+ const [paragraph] = node.children;
36
+ const first =
37
+ paragraph?.type === "paragraph" ? paragraph.children[0] : undefined;
38
+ const escaped = text[first?.position?.start.offset ?? 0] === "\\";
39
+ const alert =
40
+ first?.type === "text" && !escaped
41
+ ? alertMarkerOf(first.value)
42
+ : undefined;
43
+ if (alert !== undefined) {
44
+ found.push({ ...alert, line: node.position?.start.line ?? 1 });
45
+ // A quote inside an alert stays a quote, alert or not.
46
+ return;
47
+ }
48
+ }
49
+ if ("children" in node) {
50
+ for (const child of node.children) {
51
+ walk(child);
52
+ }
53
+ }
54
+ };
55
+ walk(tree);
56
+ return found;
57
+ };
58
+
59
+ /**
60
+ * Warn about GitHub alerts (`> [!NOTE]`) in an `.md` entry, which renders
61
+ * them as plain quotes that show the marker as text: alerts become callouts
62
+ * only in `.mdx`, where Blume renders components (see
63
+ * `markdown/github-alerts.ts`). Lines point into the file the author wrote, a
64
+ * partial's own file for an alert an `<include>` brought in.
65
+ */
66
+ export const alertDiagnostics = (
67
+ entry: SourceEntry,
68
+ sourceName: string
69
+ ): Diagnostic[] => {
70
+ if (entry.body.format !== "md") {
71
+ return [];
72
+ }
73
+ const page = entry.sourcePath ?? `${sourceName}:${entry.ref}`;
74
+ const offset =
75
+ entry.bodyLineOffset ?? strippedLineOffset(entry.raw, entry.body.text);
76
+ return alertFindings(entry.expanded?.text ?? entry.body.text).map(
77
+ ({ callout, line, marker }) => {
78
+ const origin = entry.expanded?.origins[line - 1];
79
+ return {
80
+ code: "BLUME_MD_GITHUB_ALERT",
81
+ file: origin?.file ?? page,
82
+ line: origin?.line ?? line + offset,
83
+ message: `\`${marker}\` makes a quote a callout only in .mdx; in this .md page it renders as a quote that shows \`${marker}\` as text.`,
84
+ severity: "warning",
85
+ suggestion: `Rename the page to .mdx to render it as a \`${callout}\` callout, or remove \`${marker}\` to keep a plain quote.`,
86
+ };
87
+ }
88
+ );
89
+ };