blume 2.0.3 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (455) hide show
  1. package/AGENTS.md +1 -0
  2. package/CHANGELOG.md +48 -0
  3. package/README.md +1 -1
  4. package/dist/cli/{chunk-1d7ve1dm.js → chunk-0gz36akr.js} +18 -2
  5. package/dist/cli/{chunk-1d7ve1dm.js.map → chunk-0gz36akr.js.map} +3 -3
  6. package/dist/cli/{chunk-2eytanqx.js → chunk-1n228xqb.js} +2 -2
  7. package/dist/cli/{chunk-yfyb25rh.js → chunk-2f3ndkpw.js} +24 -8
  8. package/dist/cli/chunk-2f3ndkpw.js.map +14 -0
  9. package/dist/cli/{chunk-r20tn01b.js → chunk-2f7n4qzt.js} +8 -2
  10. package/dist/cli/{chunk-r20tn01b.js.map → chunk-2f7n4qzt.js.map} +3 -3
  11. package/dist/cli/{chunk-35d4wj9f.js → chunk-2n4ps1b3.js} +7 -7
  12. package/dist/cli/{chunk-j85vccga.js → chunk-43zcf4gz.js} +97 -57
  13. package/dist/cli/{chunk-j85vccga.js.map → chunk-43zcf4gz.js.map} +6 -6
  14. package/dist/cli/{chunk-e04dxsz1.js → chunk-4ejxbzee.js} +1 -1
  15. package/dist/cli/{chunk-5m5nmvyq.js → chunk-4h8z9vv5.js} +233 -39
  16. package/dist/cli/chunk-4h8z9vv5.js.map +16 -0
  17. package/dist/cli/{chunk-5f86nr5m.js → chunk-5cg2rzgf.js} +18 -16
  18. package/dist/cli/{chunk-5f86nr5m.js.map → chunk-5cg2rzgf.js.map} +2 -2
  19. package/dist/cli/{chunk-tzmab476.js → chunk-5ffywsmb.js} +4 -4
  20. package/dist/cli/{chunk-2z928egk.js → chunk-5wj2qwn2.js} +5 -5
  21. package/dist/cli/chunk-6bm0xpgd.js +18 -0
  22. package/dist/cli/chunk-6bm0xpgd.js.map +10 -0
  23. package/dist/cli/{chunk-vg9r4eb9.js → chunk-713mngdq.js} +12 -5
  24. package/dist/cli/{chunk-vg9r4eb9.js.map → chunk-713mngdq.js.map} +3 -3
  25. package/dist/cli/{chunk-5xvm6tfj.js → chunk-9geymqns.js} +17 -15
  26. package/dist/cli/{chunk-5xvm6tfj.js.map → chunk-9geymqns.js.map} +2 -2
  27. package/dist/cli/{chunk-q58y5e6a.js → chunk-9xr4dgee.js} +26 -17
  28. package/dist/cli/chunk-9xr4dgee.js.map +10 -0
  29. package/dist/cli/{chunk-b07cmahc.js → chunk-amrb6hgh.js} +2 -2
  30. package/dist/cli/chunk-cm89scdv.js +443 -0
  31. package/dist/cli/chunk-cm89scdv.js.map +11 -0
  32. package/dist/cli/{chunk-akbpwfxc.js → chunk-cmtcnh9f.js} +2 -2
  33. package/dist/cli/chunk-cy5bm0j0.js +104 -0
  34. package/dist/cli/chunk-cy5bm0j0.js.map +11 -0
  35. package/dist/cli/{chunk-crgn1q09.js → chunk-e7vvk5ya.js} +4 -4
  36. package/dist/cli/{chunk-pehfxfta.js → chunk-g2z9x01v.js} +6 -98
  37. package/dist/cli/chunk-g2z9x01v.js.map +10 -0
  38. package/dist/cli/{chunk-tkacnehg.js → chunk-gp84za0h.js} +2 -2
  39. package/dist/cli/{chunk-wrr3j9w9.js → chunk-h2a48yhd.js} +186 -63
  40. package/dist/cli/chunk-h2a48yhd.js.map +36 -0
  41. package/dist/cli/{chunk-hm3vjy5s.js → chunk-jnwj5kjj.js} +14 -9
  42. package/dist/cli/{chunk-hm3vjy5s.js.map → chunk-jnwj5kjj.js.map} +4 -4
  43. package/dist/cli/chunk-kwrg63tm.js +31 -0
  44. package/dist/cli/chunk-kwrg63tm.js.map +10 -0
  45. package/dist/cli/{chunk-2hsdwb9n.js → chunk-nd1821sk.js} +18 -16
  46. package/dist/cli/{chunk-2hsdwb9n.js.map → chunk-nd1821sk.js.map} +2 -2
  47. package/dist/cli/{chunk-6dsbexzp.js → chunk-ntv4qrt7.js} +17 -15
  48. package/dist/cli/{chunk-6dsbexzp.js.map → chunk-ntv4qrt7.js.map} +2 -2
  49. package/dist/cli/{chunk-r9rcc4w7.js → chunk-p804f3pe.js} +7 -7
  50. package/dist/cli/{chunk-ey84smr6.js → chunk-py62fewh.js} +6 -6
  51. package/dist/cli/{chunk-ey84smr6.js.map → chunk-py62fewh.js.map} +1 -1
  52. package/dist/cli/chunk-q3wxxq39.js +126 -0
  53. package/dist/cli/chunk-q3wxxq39.js.map +11 -0
  54. package/dist/cli/{chunk-9t7a85s3.js → chunk-rekqz4qg.js} +23 -13
  55. package/dist/cli/chunk-rekqz4qg.js.map +10 -0
  56. package/dist/cli/{chunk-8ktnccpt.js → chunk-ta27feev.js} +1 -1
  57. package/dist/cli/{chunk-pv29h0wf.js → chunk-xkz5xbtv.js} +3476 -822
  58. package/dist/cli/chunk-xkz5xbtv.js.map +213 -0
  59. package/dist/cli/{chunk-a58773jm.js → chunk-y4ykbark.js} +2 -2
  60. package/dist/cli/{chunk-p3v96n38.js → chunk-y5h4r2y0.js} +4 -4
  61. package/dist/cli/{chunk-acanzt5p.js → chunk-yj17vs39.js} +6 -6
  62. package/dist/cli/{chunk-p73c0m7w.js → chunk-yxmk4pec.js} +16 -14
  63. package/dist/cli/{chunk-p73c0m7w.js.map → chunk-yxmk4pec.js.map} +2 -2
  64. package/dist/cli/{chunk-3em5wd2y.js → chunk-zccbjcj0.js} +4 -3
  65. package/dist/cli/{chunk-3em5wd2y.js.map → chunk-zccbjcj0.js.map} +2 -2
  66. package/dist/cli/{chunk-c8chx29p.js → chunk-zf748avv.js} +384 -207
  67. package/dist/cli/chunk-zf748avv.js.map +37 -0
  68. package/dist/cli/{chunk-g4hq16wv.js → chunk-zhvd6adm.js} +16 -14
  69. package/dist/cli/{chunk-g4hq16wv.js.map → chunk-zhvd6adm.js.map} +2 -2
  70. package/dist/cli/index.js +26 -20
  71. package/dist/cli/index.js.map +5 -5
  72. package/dist/types/ai/agent-surface.d.ts +8 -7
  73. package/dist/types/ai/ai-catalog.d.ts +7 -0
  74. package/dist/types/ai/ask-context.d.ts +22 -0
  75. package/dist/types/ai/ask.d.ts +271 -58
  76. package/dist/types/ai/index.d.ts +2 -2
  77. package/dist/types/ai/site-skill.d.ts +26 -0
  78. package/dist/types/ai/skills.d.ts +16 -0
  79. package/dist/types/astro/integration.d.ts +8 -0
  80. package/dist/types/captcha/hcaptcha.d.ts +27 -0
  81. package/dist/types/captcha/index.d.ts +28 -0
  82. package/dist/types/captcha/schema.d.ts +29 -0
  83. package/dist/types/captcha/turnstile.d.ts +28 -0
  84. package/dist/types/components/content/api-field.d.ts +59 -0
  85. package/dist/types/components/content/api-page.d.ts +95 -0
  86. package/dist/types/components/layout/nav-utils.d.ts +26 -0
  87. package/dist/types/components/openapi/helpers.d.ts +137 -0
  88. package/dist/types/components/openapi/security.d.ts +85 -0
  89. package/dist/types/consent/ethyca.d.ts +48 -0
  90. package/dist/types/consent/index.d.ts +37 -0
  91. package/dist/types/consent/native.d.ts +28 -0
  92. package/dist/types/consent/osano.d.ts +42 -0
  93. package/dist/types/consent/schema.d.ts +110 -0
  94. package/dist/types/core/config-input.d.ts +187 -20
  95. package/dist/types/core/data.d.ts +49 -4
  96. package/dist/types/core/footer.d.ts +8 -0
  97. package/dist/types/core/i18n-ui.d.ts +86 -0
  98. package/dist/types/core/includes.d.ts +8 -2
  99. package/dist/types/core/localizable.d.ts +7 -0
  100. package/dist/types/core/page-modes.d.ts +29 -0
  101. package/dist/types/core/redirect-patterns.d.ts +144 -0
  102. package/dist/types/core/related.d.ts +40 -0
  103. package/dist/types/core/schema.d.ts +551 -56
  104. package/dist/types/core/sources/read.d.ts +5 -0
  105. package/dist/types/core/types.d.ts +1 -1
  106. package/dist/types/core/variables.d.ts +42 -0
  107. package/dist/types/deploy/node-headers.d.ts +13 -2
  108. package/dist/types/deploy/platforms/cloudflare.d.ts +5 -0
  109. package/dist/types/deploy/platforms/netlify.d.ts +7 -0
  110. package/dist/types/deploy/platforms/vercel.d.ts +6 -0
  111. package/dist/types/deploy/redirects.d.ts +39 -11
  112. package/dist/types/deploy/vercel-negotiation.d.ts +9 -0
  113. package/dist/types/markdown/code-title.d.ts +17 -0
  114. package/dist/types/markdown/features.d.ts +3 -2
  115. package/dist/types/narration/build.d.ts +70 -0
  116. package/dist/types/narration/provider.d.ts +26 -0
  117. package/dist/types/narration/script.d.ts +154 -0
  118. package/dist/types/openapi/model.d.ts +12 -6
  119. package/dist/types/openapi/overlay.d.ts +54 -0
  120. package/dist/types/openapi/parse.d.ts +13 -0
  121. package/dist/types/openapi/references.d.ts +14 -4
  122. package/dist/types/ratelimit/cloudflare.d.ts +39 -0
  123. package/dist/types/ratelimit/index.d.ts +27 -0
  124. package/dist/types/ratelimit/memory.d.ts +36 -0
  125. package/dist/types/ratelimit/schema.d.ts +111 -0
  126. package/dist/types/ratelimit/upstash.d.ts +21 -0
  127. package/dist/types/ratelimit/wrangler.d.ts +23 -0
  128. package/dist/types/reference/asyncapi.d.ts +10 -10
  129. package/dist/types/reference/graphql.d.ts +13 -10
  130. package/dist/types/reference/openapi.d.ts +33 -12
  131. package/dist/types/reference/options.d.ts +45 -5
  132. package/dist/types/reference/scalar.d.ts +13 -0
  133. package/dist/types/reference/schema.d.ts +60 -36
  134. package/dist/types/search/documents.d.ts +12 -1
  135. package/dist/types/search/orama-index.d.ts +4 -0
  136. package/dist/types/seo/metatags.d.ts +20 -0
  137. package/docs/02-deployment.mdx +41 -5
  138. package/docs/03-upgrading.mdx +4 -4
  139. package/docs/04-migrating.mdx +1 -1
  140. package/docs/advanced/changelog.mdx +2 -2
  141. package/docs/advanced/skills.mdx +14 -0
  142. package/docs/cli/audit.mdx +21 -1
  143. package/docs/cli/index.mdx +1 -0
  144. package/docs/configuration/analytics.mdx +3 -1
  145. package/docs/configuration/assistant.mdx +188 -36
  146. package/docs/configuration/consent.mdx +100 -0
  147. package/docs/configuration/customization.mdx +4 -4
  148. package/docs/configuration/index.mdx +63 -5
  149. package/docs/configuration/meta.ts +3 -0
  150. package/docs/configuration/narration.mdx +119 -0
  151. package/docs/configuration/rate-limiting.mdx +69 -0
  152. package/docs/configuration/search.mdx +29 -1
  153. package/docs/configuration/theming.mdx +3 -1
  154. package/docs/content/components.mdx +106 -0
  155. package/docs/content/frontmatter.mdx +76 -0
  156. package/docs/content/i18n.mdx +38 -3
  157. package/docs/content/includes.mdx +16 -0
  158. package/docs/content/meta.mdx +1 -0
  159. package/docs/content/meta.ts +1 -0
  160. package/docs/content/navigation.mdx +28 -6
  161. package/docs/content/sources/contentful.mdx +32 -0
  162. package/docs/content/sources/custom.mdx +39 -0
  163. package/docs/content/sources/github-releases.mdx +31 -0
  164. package/docs/content/sources/index.mdx +91 -0
  165. package/docs/content/sources/meta.ts +16 -0
  166. package/docs/content/sources/notion.mdx +32 -0
  167. package/docs/content/sources/obsidian.mdx +37 -0
  168. package/docs/content/sources/payload.mdx +30 -0
  169. package/docs/content/sources/remote-mdx.mdx +40 -0
  170. package/docs/content/sources/sanity.mdx +29 -0
  171. package/docs/content/sources/strapi.mdx +31 -0
  172. package/docs/content/syntax.mdx +67 -2
  173. package/docs/content/variables.mdx +41 -0
  174. package/docs/discoverability/agent-discovery.mdx +29 -2
  175. package/docs/discoverability/index.mdx +1 -1
  176. package/docs/discoverability/llms-txt.mdx +1 -1
  177. package/docs/discoverability/metadata.mdx +17 -0
  178. package/docs/references/api-pages.mdx +74 -0
  179. package/docs/references/asyncapi.mdx +1 -1
  180. package/docs/references/graphql.mdx +2 -2
  181. package/docs/references/meta.ts +1 -1
  182. package/docs/references/openapi.mdx +102 -3
  183. package/docs/references/scalar.mdx +3 -1
  184. package/package.json +36 -1
  185. package/skills/blume/SKILL.md +3 -0
  186. package/skills/blume-migrate/SKILL.md +5 -5
  187. package/skills/blume-migrate/references/docusaurus.md +3 -3
  188. package/skills/blume-migrate/references/mintlify.md +37 -19
  189. package/skills/blume-migrate/references/nextra.md +2 -2
  190. package/skills/blume-migrate/references/starlight.md +2 -1
  191. package/skills/blume-migrate/scripts/mintlify-codemod.mjs +60 -14
  192. package/skills/blume-write-skill/SKILL.md +82 -0
  193. package/src/ai/agent-readability.ts +2 -1
  194. package/src/ai/agent-surface.ts +9 -7
  195. package/src/ai/ai-catalog.ts +6 -3
  196. package/src/ai/ask-context.ts +73 -20
  197. package/src/ai/ask-data.ts +6 -0
  198. package/src/ai/ask-tools.ts +142 -0
  199. package/src/ai/ask.ts +354 -76
  200. package/src/ai/component-markdown.ts +75 -0
  201. package/src/ai/cors.ts +2 -0
  202. package/src/ai/index.ts +12 -0
  203. package/src/ai/llms.ts +6 -2
  204. package/src/ai/markdown.ts +2 -1
  205. package/src/ai/mcp/data.ts +6 -0
  206. package/src/ai/openapi-components.ts +9 -3
  207. package/src/ai/site-skill.ts +274 -0
  208. package/src/ai/skills.ts +35 -2
  209. package/src/ai/write-skill.ts +97 -0
  210. package/src/astro/generate.ts +132 -18
  211. package/src/astro/index.ts +1 -0
  212. package/src/astro/integration.ts +35 -0
  213. package/src/astro/module-types.ts +6 -0
  214. package/src/astro/templates.ts +283 -30
  215. package/src/astro/variables.ts +31 -0
  216. package/src/audit/catalog.ts +19 -0
  217. package/src/audit/checks/contrast.ts +183 -0
  218. package/src/audit/checks/i18n.ts +2 -3
  219. package/src/audit/checks/indexability.ts +2 -3
  220. package/src/audit/checks/links.ts +5 -12
  221. package/src/audit/checks/sitemap.ts +2 -3
  222. package/src/audit/redirects.ts +46 -15
  223. package/src/audit/run.ts +2 -0
  224. package/src/audit/types.ts +2 -1
  225. package/src/blume-modules.d.ts +7 -0
  226. package/src/captcha/client.ts +152 -0
  227. package/src/captcha/hcaptcha.ts +36 -0
  228. package/src/captcha/index.ts +28 -0
  229. package/src/captcha/schema.ts +32 -0
  230. package/src/captcha/turnstile.ts +38 -0
  231. package/src/captcha/verify.ts +71 -0
  232. package/src/cli/command-meta.ts +5 -0
  233. package/src/cli/commands/doctor.ts +22 -16
  234. package/src/cli/commands/skill.ts +96 -0
  235. package/src/cli/index.ts +5 -0
  236. package/src/cli/required-secrets.ts +71 -29
  237. package/src/components/BlumePage.astro +2 -0
  238. package/src/components/content/ApiEndpoint.astro +18 -0
  239. package/src/components/content/ApiField.astro +79 -0
  240. package/src/components/content/ApiPlayground.astro +62 -0
  241. package/src/components/content/ApiRail.astro +19 -0
  242. package/src/components/content/Color.astro +1 -0
  243. package/src/components/content/FileTree.astro +1 -0
  244. package/src/components/content/GithubInfo.astro +1 -0
  245. package/src/components/content/Panel.astro +1 -0
  246. package/src/components/content/ParamField.astro +25 -0
  247. package/src/components/content/RequestExample.astro +17 -0
  248. package/src/components/content/ResponseExample.astro +17 -0
  249. package/src/components/content/ResponseField.astro +32 -0
  250. package/src/components/content/Step.astro +1 -0
  251. package/src/components/content/Tile.astro +1 -0
  252. package/src/components/content/Tooltip.astro +1 -0
  253. package/src/components/content/Tree.astro +1 -0
  254. package/src/components/content/TypeTable.astro +4 -1
  255. package/src/components/content/View.astro +21 -0
  256. package/src/components/content/ViewSwitcher.astro +168 -0
  257. package/src/components/content/api-field.ts +100 -0
  258. package/src/components/content/api-page.ts +224 -0
  259. package/src/components/content/view-switcher.ts +71 -0
  260. package/src/components/index.ts +2 -0
  261. package/src/components/islands/Assistant.astro +10 -1
  262. package/src/components/islands/assistant.tsx +104 -14
  263. package/src/components/islands/code-question.ts +62 -0
  264. package/src/components/islands/hooks.ts +110 -21
  265. package/src/components/islands/support-link.ts +68 -0
  266. package/src/components/layout/Analytics.astro +11 -2
  267. package/src/components/layout/Banner.astro +14 -10
  268. package/src/components/layout/Breadcrumbs.astro +6 -2
  269. package/src/components/layout/ConsentBanner.astro +62 -0
  270. package/src/components/layout/ConsentHead.astro +38 -0
  271. package/src/components/layout/Directory.astro +94 -0
  272. package/src/components/layout/DrawerSwitchers.astro +51 -0
  273. package/src/components/layout/Empty.astro +2 -2
  274. package/src/components/layout/Header.astro +122 -80
  275. package/src/components/layout/LanguageSwitcher.astro +48 -15
  276. package/src/components/layout/NarrationPlayer.astro +1085 -0
  277. package/src/components/layout/NavSelector.astro +33 -7
  278. package/src/components/layout/PageFeedback.astro +54 -49
  279. package/src/components/layout/PageLayout.astro +71 -3
  280. package/src/components/layout/ReferenceLayout.astro +17 -6
  281. package/src/components/layout/RelatedPages.astro +42 -0
  282. package/src/components/layout/RootLayout.astro +308 -45
  283. package/src/components/layout/Search.astro +31 -17
  284. package/src/components/layout/SiteFooter.astro +124 -0
  285. package/src/components/layout/SiteMetatags.astro +16 -0
  286. package/src/components/layout/consent/native.ts +79 -0
  287. package/src/components/layout/consent/types.ts +37 -0
  288. package/src/components/layout/footer-links.ts +53 -0
  289. package/src/components/layout/footer-socials.ts +87 -0
  290. package/src/components/layout/locale-redirect.ts +107 -0
  291. package/src/components/layout/nav-utils.ts +83 -0
  292. package/src/components/layout/page-feedback.ts +96 -0
  293. package/src/components/layout/search/analytics.ts +98 -0
  294. package/src/components/layout/search/flexsearch.ts +17 -5
  295. package/src/components/layout/search/orama-cloud.ts +26 -14
  296. package/src/components/layout/search/types.ts +20 -0
  297. package/src/components/layout/search/typesense.ts +5 -1
  298. package/src/components/openapi/Operation.astro +161 -13
  299. package/src/components/openapi/RequestBody.astro +6 -4
  300. package/src/components/openapi/RequestPanel.astro +59 -7
  301. package/src/components/openapi/Responses.astro +6 -4
  302. package/src/components/openapi/async-snippets.ts +4 -4
  303. package/src/components/openapi/code-samples.ts +97 -0
  304. package/src/components/openapi/security.ts +6 -3
  305. package/src/components/openapi/snippets.ts +869 -8
  306. package/src/components/props.ts +6 -0
  307. package/src/consent/client.ts +95 -0
  308. package/src/consent/clients.ts +19 -0
  309. package/src/consent/ethyca.ts +71 -0
  310. package/src/consent/head.ts +24 -0
  311. package/src/consent/index.ts +37 -0
  312. package/src/consent/init.ts +29 -0
  313. package/src/consent/native.ts +36 -0
  314. package/src/consent/osano.ts +59 -0
  315. package/src/consent/schema.ts +30 -0
  316. package/src/core/builtin-tags.ts +5 -0
  317. package/src/core/client-address.ts +19 -0
  318. package/src/core/config-input.ts +194 -20
  319. package/src/core/data.ts +44 -4
  320. package/src/core/diagnostics.ts +3 -2
  321. package/src/core/footer.ts +26 -0
  322. package/src/core/graph.ts +39 -36
  323. package/src/core/i18n-ui.ts +82 -2
  324. package/src/core/includes.ts +20 -3
  325. package/src/core/links.ts +16 -2
  326. package/src/core/localizable.ts +26 -0
  327. package/src/core/navigation.ts +43 -12
  328. package/src/core/page-modes.ts +56 -0
  329. package/src/core/project-graph.ts +111 -5
  330. package/src/core/redirect-patterns.ts +491 -0
  331. package/src/core/related.ts +131 -0
  332. package/src/core/schema.ts +318 -64
  333. package/src/core/sources/normalize.ts +2 -1
  334. package/src/core/sources/read.ts +8 -3
  335. package/src/core/types.ts +1 -1
  336. package/src/core/ui-packs/ar.ts +43 -0
  337. package/src/core/ui-packs/bg.ts +45 -0
  338. package/src/core/ui-packs/bn.ts +43 -0
  339. package/src/core/ui-packs/ca.ts +46 -0
  340. package/src/core/ui-packs/cs.ts +44 -0
  341. package/src/core/ui-packs/da.ts +45 -0
  342. package/src/core/ui-packs/de.ts +46 -0
  343. package/src/core/ui-packs/el.ts +45 -0
  344. package/src/core/ui-packs/es.ts +46 -0
  345. package/src/core/ui-packs/fa.ts +45 -0
  346. package/src/core/ui-packs/fi.ts +45 -0
  347. package/src/core/ui-packs/fr.ts +46 -0
  348. package/src/core/ui-packs/he.ts +43 -0
  349. package/src/core/ui-packs/hi.ts +44 -0
  350. package/src/core/ui-packs/hr.ts +45 -0
  351. package/src/core/ui-packs/hu.ts +44 -0
  352. package/src/core/ui-packs/id.ts +45 -0
  353. package/src/core/ui-packs/it.ts +45 -0
  354. package/src/core/ui-packs/ja.ts +45 -0
  355. package/src/core/ui-packs/ko.ts +44 -0
  356. package/src/core/ui-packs/nl.ts +46 -0
  357. package/src/core/ui-packs/no.ts +44 -0
  358. package/src/core/ui-packs/pl.ts +45 -0
  359. package/src/core/ui-packs/pt-br.ts +46 -0
  360. package/src/core/ui-packs/pt.ts +45 -0
  361. package/src/core/ui-packs/ro.ts +45 -0
  362. package/src/core/ui-packs/ru.ts +45 -0
  363. package/src/core/ui-packs/sk.ts +44 -0
  364. package/src/core/ui-packs/sr.ts +45 -0
  365. package/src/core/ui-packs/sv.ts +44 -0
  366. package/src/core/ui-packs/th.ts +43 -0
  367. package/src/core/ui-packs/tr.ts +45 -0
  368. package/src/core/ui-packs/uk.ts +45 -0
  369. package/src/core/ui-packs/vi.ts +45 -0
  370. package/src/core/ui-packs/zh-tw.ts +43 -0
  371. package/src/core/ui-packs/zh.ts +43 -0
  372. package/src/core/variables.ts +103 -0
  373. package/src/deploy/artifacts.ts +77 -8
  374. package/src/deploy/cloudflare-negotiation.ts +35 -5
  375. package/src/deploy/headers.ts +2 -1
  376. package/src/deploy/node-headers.ts +63 -9
  377. package/src/deploy/platforms/cloudflare.ts +32 -0
  378. package/src/deploy/platforms/netlify.ts +33 -1
  379. package/src/deploy/platforms/vercel.ts +37 -2
  380. package/src/deploy/redirects.ts +91 -24
  381. package/src/deploy/vercel-negotiation.ts +36 -0
  382. package/src/markdown/api-rail.ts +259 -0
  383. package/src/markdown/code-title.ts +35 -9
  384. package/src/markdown/features.ts +3 -2
  385. package/src/markdown/fence-meta.ts +2 -0
  386. package/src/markdown/include.ts +11 -1
  387. package/src/markdown/index.ts +28 -5
  388. package/src/markdown/math.ts +4 -4
  389. package/src/markdown/variables.ts +56 -0
  390. package/src/markdown/views.ts +82 -0
  391. package/src/narration/build.ts +412 -0
  392. package/src/narration/player.ts +249 -0
  393. package/src/narration/provider.ts +29 -0
  394. package/src/narration/script.ts +592 -0
  395. package/src/og/card.ts +7 -6
  396. package/src/openapi/model.ts +57 -24
  397. package/src/openapi/overlay.ts +256 -0
  398. package/src/openapi/parse.ts +55 -2
  399. package/src/openapi/references.ts +21 -5
  400. package/src/openapi/render-mdx.ts +5 -1
  401. package/src/openapi/scalar.ts +40 -6
  402. package/src/openapi/source.ts +1 -1
  403. package/src/ratelimit/cloudflare.ts +54 -0
  404. package/src/ratelimit/index.ts +30 -0
  405. package/src/ratelimit/memory.ts +45 -0
  406. package/src/ratelimit/runtime.ts +229 -0
  407. package/src/ratelimit/schema.ts +28 -0
  408. package/src/ratelimit/upstash.ts +30 -0
  409. package/src/ratelimit/wrangler.ts +70 -0
  410. package/src/reference/asyncapi.ts +2 -2
  411. package/src/reference/graphql.ts +5 -2
  412. package/src/reference/openapi.ts +21 -7
  413. package/src/reference/options.ts +51 -7
  414. package/src/reference/scalar.ts +33 -8
  415. package/src/registry/eject.ts +20 -4
  416. package/src/registry/registry.ts +7 -0
  417. package/src/search/documents.ts +26 -2
  418. package/src/search/orama-index.ts +28 -3
  419. package/src/search/pagefind-ranking.ts +39 -0
  420. package/src/search/sync/algolia.ts +15 -4
  421. package/src/search/sync/orama-cloud.ts +3 -0
  422. package/src/search/sync/typesense.ts +5 -0
  423. package/src/seo/metatags.ts +67 -0
  424. package/src/theme/chrome-icons.ts +1 -0
  425. package/src/theme/color.ts +500 -0
  426. package/src/theme/entry.ts +56 -3
  427. package/src/theme/palette.ts +121 -45
  428. package/src/translate/prompts.ts +1 -0
  429. package/src/translate/validate.ts +8 -2
  430. package/src/upgrade/upgrade.ts +4 -171
  431. package/dist/cli/chunk-364znk6q.js +0 -75
  432. package/dist/cli/chunk-364znk6q.js.map +0 -11
  433. package/dist/cli/chunk-5m5nmvyq.js.map +0 -13
  434. package/dist/cli/chunk-9t7a85s3.js.map +0 -10
  435. package/dist/cli/chunk-c8chx29p.js.map +0 -36
  436. package/dist/cli/chunk-pehfxfta.js.map +0 -10
  437. package/dist/cli/chunk-pv29h0wf.js.map +0 -190
  438. package/dist/cli/chunk-q58y5e6a.js.map +0 -10
  439. package/dist/cli/chunk-wrr3j9w9.js.map +0 -35
  440. package/dist/cli/chunk-yfyb25rh.js.map +0 -14
  441. package/docs/content/sources.mdx +0 -334
  442. /package/dist/cli/{chunk-2eytanqx.js.map → chunk-1n228xqb.js.map} +0 -0
  443. /package/dist/cli/{chunk-35d4wj9f.js.map → chunk-2n4ps1b3.js.map} +0 -0
  444. /package/dist/cli/{chunk-e04dxsz1.js.map → chunk-4ejxbzee.js.map} +0 -0
  445. /package/dist/cli/{chunk-tzmab476.js.map → chunk-5ffywsmb.js.map} +0 -0
  446. /package/dist/cli/{chunk-2z928egk.js.map → chunk-5wj2qwn2.js.map} +0 -0
  447. /package/dist/cli/{chunk-b07cmahc.js.map → chunk-amrb6hgh.js.map} +0 -0
  448. /package/dist/cli/{chunk-akbpwfxc.js.map → chunk-cmtcnh9f.js.map} +0 -0
  449. /package/dist/cli/{chunk-crgn1q09.js.map → chunk-e7vvk5ya.js.map} +0 -0
  450. /package/dist/cli/{chunk-tkacnehg.js.map → chunk-gp84za0h.js.map} +0 -0
  451. /package/dist/cli/{chunk-r9rcc4w7.js.map → chunk-p804f3pe.js.map} +0 -0
  452. /package/dist/cli/{chunk-8ktnccpt.js.map → chunk-ta27feev.js.map} +0 -0
  453. /package/dist/cli/{chunk-a58773jm.js.map → chunk-y4ykbark.js.map} +0 -0
  454. /package/dist/cli/{chunk-p3v96n38.js.map → chunk-y5h4r2y0.js.map} +0 -0
  455. /package/dist/cli/{chunk-acanzt5p.js.map → chunk-yj17vs39.js.map} +0 -0
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  import specs from "blume:openapi";
3
- import { operationOf, specOf } from "../../openapi/model.ts";
3
+ import { HTTP_METHODS, operationOf, specOf } from "../../openapi/model.ts";
4
4
  import {
5
5
  mergeParameters,
6
6
  type ParameterLike,
@@ -21,10 +21,13 @@ import {
21
21
  type SecuritySchemeLike,
22
22
  } from "./security.ts";
23
23
  import { DEPRECATED_LABEL_CLASS } from "../colors.ts";
24
+ import { customCodeSamples } from "./code-samples.ts";
25
+ import type { CodeSamplesCarrier } from "./code-samples.ts";
24
26
  import { sampleLanguages } from "./snippets.ts";
25
27
  import AsyncApiOperation from "./AsyncApiOperation.astro";
26
28
  import GraphqlOperation from "./GraphqlOperation.astro";
27
29
  import Authorization from "./Authorization.astro";
30
+ import Description from "./Description.astro";
28
31
  import MethodBadge from "./MethodBadge.astro";
29
32
  import ParametersTable from "./ParametersTable.astro";
30
33
  import Playground from "./Playground.astro";
@@ -57,7 +60,18 @@ interface ResponseLike {
57
60
  content?: Record<string, MediaTypeLike>;
58
61
  }
59
62
 
60
- interface FullOperation {
63
+ /**
64
+ * One callback: a map of runtime URL expressions
65
+ * (`{$request.body#/callbackUrl}`) to the path item the API will call there,
66
+ * or a `$ref` to one under `components.callbacks`.
67
+ */
68
+ type CallbackLike = { $ref?: string } & Record<
69
+ string,
70
+ Record<string, unknown> | string | undefined
71
+ >;
72
+
73
+ interface FullOperation extends CodeSamplesCarrier {
74
+ callbacks?: Record<string, CallbackLike>;
61
75
  summary?: string;
62
76
  description?: string;
63
77
  deprecated?: boolean;
@@ -77,15 +91,17 @@ const ref = spec ? operationOf(spec, id) : undefined;
77
91
  const isAsyncApi = spec?.kind === "asyncapi";
78
92
  const isGraphql = spec?.kind === "graphql";
79
93
 
94
+ type PathItemLike = Record<string, unknown> & {
95
+ parameters?: ParameterLike[];
96
+ servers?: ServerLike[];
97
+ };
98
+
80
99
  const doc = (spec?.document ?? {}) as {
81
- paths?: Record<
82
- string,
83
- Record<string, unknown> & {
84
- parameters?: ParameterLike[];
85
- servers?: ServerLike[];
86
- }
87
- >;
100
+ paths?: Record<string, PathItemLike>;
101
+ // OpenAPI 3.1 webhooks: requests the API sends, keyed by name.
102
+ webhooks?: Record<string, PathItemLike>;
88
103
  components?: {
104
+ callbacks?: Record<string, CallbackLike>;
89
105
  schemas?: Record<string, SchemaLike>;
90
106
  parameters?: Record<string, ParameterLike>;
91
107
  requestBodies?: Record<string, RequestBodyLike>;
@@ -95,7 +111,12 @@ const doc = (spec?.document ?? {}) as {
95
111
  security?: SecurityRequirementLike[];
96
112
  servers?: ServerLike[];
97
113
  };
98
- const pathItem = ref ? doc.paths?.[ref.path] : undefined;
114
+ const isWebhook = ref?.webhook === true;
115
+ const pathItem = ref
116
+ ? isWebhook
117
+ ? doc.webhooks?.[ref.path]
118
+ : doc.paths?.[ref.path]
119
+ : undefined;
99
120
  const operation = (
100
121
  pathItem && ref ? pathItem[ref.method] : undefined
101
122
  ) as FullOperation | undefined;
@@ -118,14 +139,16 @@ const responses = Object.fromEntries(
118
139
  );
119
140
 
120
141
  const security = resolveSecurity(
121
- effectiveSecurity(operation?.security, doc.security),
142
+ effectiveSecurity(operation?.security, doc.security, isWebhook),
122
143
  doc.components?.securitySchemes
123
144
  );
124
145
 
125
146
  // One derived model feeds the static default sample, the playground form,
126
- // and (client-side) the live samples + fetch — they can never drift apart.
147
+ // and (client-side) the live samples + fetch — they can never drift apart. A
148
+ // webhook has none: the API sends it, so there is nothing for a reader to
149
+ // call, and its rail shows the payload instead.
127
150
  const model =
128
- ref && operation
151
+ ref && operation && !isWebhook
129
152
  ? operationModel({
130
153
  components,
131
154
  method: ref.method,
@@ -145,6 +168,59 @@ const model =
145
168
  : null;
146
169
  const sample = model ? buildRequest(model, defaultValues(model)) : null;
147
170
  const languages = sampleLanguages(spec?.codeSamples ?? []);
171
+ // The spec's own `x-codeSamples`, shown ahead of the generated ones.
172
+ const customSamples = operation ? customCodeSamples(operation) : [];
173
+
174
+ /** One request the API makes back to the reader, flattened for rendering. */
175
+ interface CallbackEntry {
176
+ expression: string;
177
+ key: string;
178
+ method: string;
179
+ name: string;
180
+ operation: FullOperation;
181
+ }
182
+
183
+ // Callbacks: for each named callback (resolved through
184
+ // `components.callbacks`), each URL expression, each method it declares.
185
+ const callbacks: CallbackEntry[] = Object.entries(
186
+ operation?.callbacks ?? {}
187
+ ).flatMap(([name, raw]) => {
188
+ const callback = resolveComponentRef(raw, components, "callbacks");
189
+ return Object.entries(callback).flatMap(([expression, item]) => {
190
+ if (expression === "$ref" || typeof item !== "object" || item === null) {
191
+ return [];
192
+ }
193
+ return HTTP_METHODS.flatMap((method) => {
194
+ const entry = item[method];
195
+ return typeof entry === "object" && entry !== null
196
+ ? [
197
+ {
198
+ expression,
199
+ key: `${name}-${expression}-${method}`,
200
+ method,
201
+ name,
202
+ // SAFETY: a path item's method entry is an operation object; the
203
+ // object check above guards a spec that put a scalar there.
204
+ operation: entry as FullOperation,
205
+ },
206
+ ]
207
+ : [];
208
+ });
209
+ });
210
+ });
211
+
212
+ /** A callback's request body and responses, `$ref`s resolved. */
213
+ const callbackParts = (entry: CallbackEntry) => ({
214
+ requestBody: entry.operation.requestBody
215
+ ? resolveComponentRef(entry.operation.requestBody, components, "requestBodies")
216
+ : undefined,
217
+ responses: Object.fromEntries(
218
+ Object.entries(entry.operation.responses ?? {}).map(([status, response]) => [
219
+ status,
220
+ resolveComponentRef(response, components, "responses"),
221
+ ])
222
+ ),
223
+ });
148
224
  ---
149
225
  {
150
226
  isAsyncApi ? (
@@ -160,6 +236,11 @@ const languages = sampleLanguages(spec?.codeSamples ?? []);
160
236
  <code class="break-all font-mono text-foreground text-sm">
161
237
  {ref.path}
162
238
  </code>
239
+ {isWebhook && (
240
+ <span class="rounded-md border border-border px-1.5 py-0.5 font-medium text-[0.625rem] text-muted-foreground uppercase tracking-wide">
241
+ Webhook
242
+ </span>
243
+ )}
163
244
  {operation.deprecated && (
164
245
  <span class={DEPRECATED_LABEL_CLASS}>
165
246
  deprecated
@@ -184,7 +265,73 @@ const languages = sampleLanguages(spec?.codeSamples ?? []);
184
265
  schemas={schemas}
185
266
  />
186
267
  )}
268
+ {callbacks.length > 0 && (
269
+ <section class="mt-10">
270
+ <div
271
+ aria-level="2"
272
+ class="mb-3 font-semibold text-foreground text-lg"
273
+ role="heading"
274
+ >
275
+ Callbacks
276
+ </div>
277
+ <div class="flex flex-col gap-6">
278
+ {callbacks.map((entry) => {
279
+ const parts = callbackParts(entry);
280
+ return (
281
+ <div class="rounded-blume border border-border p-4">
282
+ <div
283
+ aria-level="3"
284
+ class="font-semibold text-foreground"
285
+ role="heading"
286
+ >
287
+ {entry.operation.summary || entry.name}
288
+ </div>
289
+ <div class="mt-2 flex flex-wrap items-center gap-2">
290
+ <MethodBadge method={entry.method} />
291
+ <code class="break-all font-mono text-muted-foreground text-xs">
292
+ {entry.expression}
293
+ </code>
294
+ </div>
295
+ {entry.operation.description && (
296
+ <Description
297
+ class="mt-2 text-muted-foreground text-sm"
298
+ text={entry.operation.description}
299
+ />
300
+ )}
301
+ {parts.requestBody && (
302
+ <RequestBody
303
+ expandAll={spec.expandSchemas}
304
+ level={4}
305
+ requestBody={parts.requestBody}
306
+ schemas={schemas}
307
+ />
308
+ )}
309
+ {entry.operation.responses && (
310
+ <Responses
311
+ expandAll={spec.expandSchemas}
312
+ level={4}
313
+ responses={parts.responses}
314
+ schemas={schemas}
315
+ />
316
+ )}
317
+ </div>
318
+ );
319
+ })}
320
+ </div>
321
+ </section>
322
+ )}
187
323
  </div>
324
+ {isWebhook && (requestBody?.content || operation.responses) && (
325
+ <OperationPanel>
326
+ <RequestPanel
327
+ components={components}
328
+ customSamples={customSamples}
329
+ payload={requestBody?.content}
330
+ responses={responses}
331
+ schemas={schemas}
332
+ />
333
+ </OperationPanel>
334
+ )}
188
335
  {sample && model && (
189
336
  <OperationPanel>
190
337
  {spec.playground.enabled && (
@@ -197,6 +344,7 @@ const languages = sampleLanguages(spec?.codeSamples ?? []);
197
344
  )}
198
345
  <RequestPanel
199
346
  components={components}
347
+ customSamples={customSamples}
200
348
  languages={languages}
201
349
  responses={responses}
202
350
  sample={sample}
@@ -17,9 +17,11 @@ interface Props {
17
17
  requestBody: RequestBodyLike;
18
18
  schemas: Record<string, SchemaLike>;
19
19
  expandAll?: boolean;
20
+ /** The heading's level: 2 on an operation, deeper inside a callback. */
21
+ level?: number;
20
22
  }
21
23
 
22
- const { requestBody, schemas, expandAll = false } = Astro.props;
24
+ const { requestBody, schemas, expandAll = false, level = 2 } = Astro.props;
23
25
 
24
26
  const entries = Object.entries(requestBody.content ?? {});
25
27
  const chosen = entries.find(([type]) => type.includes("json")) ?? entries[0];
@@ -27,11 +29,11 @@ const contentType = chosen?.[0] ?? "application/json";
27
29
  const schema = chosen?.[1]?.schema ?? {};
28
30
  ---
29
31
 
30
- <section class="mt-8">
32
+ <section class:list={[level > 2 ? "mt-5" : "mt-8"]}>
31
33
  <div class="mb-2 flex flex-wrap items-center gap-2">
32
34
  <div
33
- aria-level="2"
34
- class="font-semibold text-foreground text-lg"
35
+ aria-level={level}
36
+ class:list={["font-semibold text-foreground", level > 2 ? "text-sm" : "text-lg"]}
35
37
  role="heading"
36
38
  >
37
39
  Request body
@@ -9,6 +9,7 @@ import {
9
9
  type SpecValue,
10
10
  toJson,
11
11
  } from "./helpers.ts";
12
+ import type { CustomCodeSample } from "./code-samples.ts";
12
13
  import { languageSamplePanels } from "./sample-panels.ts";
13
14
  import PanelTabs from "./PanelTabs.astro";
14
15
  import type { RequestSample, SampleLanguage } from "./snippets.ts";
@@ -27,19 +28,69 @@ interface ResponseLike {
27
28
  interface Props {
28
29
  /** The document's `components`, which `$ref`'d examples resolve against. */
29
30
  components?: ComponentsLike;
30
- sample: RequestSample;
31
- languages: SampleLanguage[];
31
+ /** The spec's hand-written `x-codeSamples`, shown first and never re-rendered live. */
32
+ customSamples?: CustomCodeSample[];
33
+ /**
34
+ * A webhook's body: the rail shows it as a "Payload" example per media
35
+ * type in place of client code, since the API sends the request.
36
+ */
37
+ payload?: Record<string, MediaTypeLike>;
38
+ sample?: RequestSample;
39
+ languages?: SampleLanguage[];
32
40
  responses: Record<string, ResponseLike>;
33
41
  schemas: Record<string, SchemaLike>;
34
42
  }
35
43
 
36
- const { components, sample, languages, responses, schemas } = Astro.props;
37
-
38
- const requestPanels = await languageSamplePanels(
39
- languages,
44
+ const {
45
+ components,
46
+ customSamples = [],
47
+ payload,
40
48
  sample,
41
- data.config.codeThemes
49
+ languages = [],
50
+ responses,
51
+ schemas,
52
+ } = Astro.props;
53
+
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
+ );
66
+ };
67
+
68
+ // Hand-written samples carry no `lang` key, so the playground leaves them
69
+ // as written when it re-renders the generated ones from the form.
70
+ const customPanels = await Promise.all(
71
+ customSamples.map(async (custom) => ({
72
+ html: await highlightCode(custom.source, custom.lang, {
73
+ icons: false,
74
+ themes: data.config.codeThemes,
75
+ }),
76
+ key: custom.key,
77
+ label: custom.label,
78
+ }))
79
+ );
80
+ 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
+ }))
42
87
  );
88
+ const requestPanels = [
89
+ ...customPanels,
90
+ ...(sample
91
+ ? await languageSamplePanels(languages, sample, data.config.codeThemes)
92
+ : []),
93
+ ];
43
94
 
44
95
  const responsePanels = await Promise.all(
45
96
  Object.entries(responses).map(async ([status, response]) => {
@@ -66,6 +117,7 @@ const responsePanels = await Promise.all(
66
117
  ---
67
118
 
68
119
  <div class="not-prose flex flex-col gap-6">
120
+ <PanelTabs copy heading="Payload" mono panels={payloadPanels} />
69
121
  <PanelTabs copy heading="Request" panels={requestPanels} />
70
122
  <PanelTabs heading="Response" mono panels={responsePanels} />
71
123
  </div>
@@ -17,19 +17,21 @@ interface Props {
17
17
  responses: Record<string, ResponseLike>;
18
18
  schemas: Record<string, SchemaLike>;
19
19
  expandAll?: boolean;
20
+ /** The heading's level: 2 on an operation, deeper inside a callback. */
21
+ level?: number;
20
22
  }
21
23
 
22
- const { responses, schemas, expandAll = false } = Astro.props;
24
+ const { responses, schemas, expandAll = false, level = 2 } = Astro.props;
23
25
 
24
26
  const items = Object.entries(responses);
25
27
  ---
26
28
 
27
29
  {
28
30
  items.length > 0 && (
29
- <section class="mt-8">
31
+ <section class:list={[level > 2 ? "mt-5" : "mt-8"]}>
30
32
  <div
31
- aria-level="2"
32
- class="mb-3 font-semibold text-foreground text-lg"
33
+ aria-level={level}
34
+ class:list={["mb-3 font-semibold text-foreground", level > 2 ? "text-sm" : "text-lg"]}
33
35
  role="heading"
34
36
  >
35
37
  Responses
@@ -157,15 +157,15 @@ const ALIASES = new Map<string, ToolId>([
157
157
  /**
158
158
  * The sample tools to render for an operation. The protocol picks the
159
159
  * candidate set; a non-empty `codeSamples` config filters and orders it
160
- * (unknown ids are dropped, aliases accepted). No protocol, an unsupported
161
- * one, or a filter that matches nothing yields no samples.
160
+ * (unknown ids are dropped, aliases accepted). `false`, no protocol, an
161
+ * unsupported one, or a filter that matches nothing yields no samples.
162
162
  */
163
163
  export const asyncSampleLanguages = (
164
- ids: string[],
164
+ ids: string[] | false,
165
165
  protocol?: string
166
166
  ): AsyncSampleLanguage[] => {
167
167
  const candidates = PROTOCOL_TOOLS.get(protocol ?? "") ?? [];
168
- if (candidates.length === 0) {
168
+ if (ids === false || candidates.length === 0) {
169
169
  return [];
170
170
  }
171
171
  if (ids.length === 0) {
@@ -0,0 +1,97 @@
1
+ import { sampleLanguageInfo } from "./snippets.ts";
2
+ import type { SampleLanguageInfo } from "./snippets.ts";
3
+
4
+ /**
5
+ * Hand-written code samples from a spec: the `x-codeSamples` extension on an
6
+ * operation (Redocly's, which Mintlify, Speakeasy, and Stainless write too),
7
+ * or its older spelling `x-code-samples`. Each entry names a `lang`, holds
8
+ * the `source`, and may carry a `label`. They render as their own tabs ahead
9
+ * of the generated samples and, being fixed text, the playground leaves them
10
+ * as written.
11
+ */
12
+
13
+ /** One hand-written sample, ready to render as a tab. */
14
+ export interface CustomCodeSample {
15
+ /** A stable, unique tab key. */
16
+ key: string;
17
+ /** The tab label: the entry's own, else the language's name. */
18
+ label: string;
19
+ /** The Shiki language to highlight `source` with. */
20
+ lang: string;
21
+ source: string;
22
+ }
23
+
24
+ /** The two places an operation keeps them. */
25
+ export interface CodeSamplesCarrier {
26
+ "x-code-samples"?: unknown;
27
+ "x-codeSamples"?: unknown;
28
+ }
29
+
30
+ interface RawSample {
31
+ label?: string;
32
+ lang: string;
33
+ source: string;
34
+ }
35
+
36
+ const isRawSample = (value: unknown): value is RawSample =>
37
+ typeof value === "object" &&
38
+ value !== null &&
39
+ "lang" in value &&
40
+ typeof value.lang === "string" &&
41
+ "source" in value &&
42
+ typeof value.source === "string" &&
43
+ (!("label" in value) || typeof value.label === "string");
44
+
45
+ // Shell tools in an `x-codeSamples` entry are usually an SDK's CLI, not curl,
46
+ // so they're named for the shell rather than "cURL".
47
+ const SHELL_LANGS = new Set(["bash", "sh", "shell", "zsh"]);
48
+
49
+ /** The display name and highlighter for a sample's `lang`. */
50
+ const describe = (lang: string): SampleLanguageInfo => {
51
+ const lower = lang.toLowerCase();
52
+ if (SHELL_LANGS.has(lower)) {
53
+ return { label: "Shell", lang: "bash" };
54
+ }
55
+ // A language Blume doesn't generate keeps its own name, and Shiki tries
56
+ // it as written (it knows most languages by their common names).
57
+ return sampleLanguageInfo(lower) ?? { label: lang, lang: lower };
58
+ };
59
+
60
+ /**
61
+ * The operation's hand-written samples, with a unique tab label each. A label
62
+ * two samples share gains its language (`List plants · JavaScript`), and one
63
+ * that still repeats gains a number. Malformed entries are skipped.
64
+ */
65
+ export const customCodeSamples = (
66
+ operation: CodeSamplesCarrier
67
+ ): CustomCodeSample[] => {
68
+ const raw = operation["x-codeSamples"] ?? operation["x-code-samples"];
69
+ if (!Array.isArray(raw)) {
70
+ return [];
71
+ }
72
+ const entries = raw.filter(isRawSample).map((entry) => {
73
+ const language = describe(entry.lang);
74
+ return {
75
+ base: entry.label?.trim() || language.label,
76
+ language,
77
+ source: entry.source.replace(/\n$/u, ""),
78
+ };
79
+ });
80
+ const counts = new Map<string, number>();
81
+ for (const { base } of entries) {
82
+ counts.set(base, (counts.get(base) ?? 0) + 1);
83
+ }
84
+ const used = new Set<string>();
85
+ return entries.map(({ base, language, source }, index) => {
86
+ const first =
87
+ (counts.get(base) ?? 0) > 1 && base !== language.label
88
+ ? `${base} · ${language.label}`
89
+ : base;
90
+ let label = first;
91
+ for (let n = 2; used.has(label); n += 1) {
92
+ label = `${first} (${n})`;
93
+ }
94
+ used.add(label);
95
+ return { key: `x-${index}`, label, lang: language.lang, source };
96
+ });
97
+ };
@@ -45,12 +45,15 @@ export interface OperationSecurity {
45
45
  /**
46
46
  * The requirement list an operation actually enforces: its own `security` when
47
47
  * declared — the OpenAPI override rule, where `[]` removes the default and
48
- * makes the operation public — else the document's root `security`.
48
+ * makes the operation public — else the document's root `security`. A webhook
49
+ * takes only its own: the API sends it, so the root requirements, which guard
50
+ * calls to the API, say nothing about what the receiving endpoint checks.
49
51
  */
50
52
  export const effectiveSecurity = (
51
53
  operation?: SecurityRequirementLike[],
52
- document?: SecurityRequirementLike[]
53
- ): SecurityRequirementLike[] => operation ?? document ?? [];
54
+ document?: SecurityRequirementLike[],
55
+ webhook = false
56
+ ): SecurityRequirementLike[] => operation ?? (webhook ? [] : document) ?? [];
54
57
 
55
58
  /**
56
59
  * Resolve requirement names against `components.securitySchemes`. A name with