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
@@ -89,7 +89,7 @@ const FETCH_REFUSED_NOTE = [
89
89
  "// curl or another HTTP client instead.",
90
90
  ].join("\n");
91
91
 
92
- const fetchSnippet = (sample: RequestSample): string => {
92
+ const fetchCall = (sample: RequestSample, declaration: string): string => {
93
93
  if (fetchRefusesMethod(sample.method)) {
94
94
  return FETCH_REFUSED_NOTE;
95
95
  }
@@ -124,11 +124,17 @@ const fetchSnippet = (sample: RequestSample): string => {
124
124
  ].join("");
125
125
  options.push(" body: form");
126
126
  }
127
- return `${form}const response = await fetch(${stringLiteral(sample.url)}, {\n${options.join(
127
+ return `${form}const ${declaration} = await fetch(${stringLiteral(sample.url)}, {\n${options.join(
128
128
  ",\n"
129
129
  )}\n});`;
130
130
  };
131
131
 
132
+ const fetchSnippet = (sample: RequestSample): string =>
133
+ fetchCall(sample, "response");
134
+
135
+ const typescriptSnippet = (sample: RequestSample): string =>
136
+ fetchCall(sample, "response: Response");
137
+
132
138
  /** The methods `requests` has a module-level helper for. */
133
139
  const PYTHON_METHODS = new Set([
134
140
  "delete",
@@ -175,6 +181,791 @@ const pythonSnippet = (sample: RequestSample): string => {
175
181
  return `import requests\n\nresponse = ${call}(\n${args.join(",\n")},\n)`;
176
182
  };
177
183
 
184
+ // ---------------------------------------------------------------------------
185
+ // Shared helpers for the compiled and scripting languages below
186
+ // ---------------------------------------------------------------------------
187
+
188
+ const hex = (code: number, width: number): string =>
189
+ code.toString(16).padStart(width, "0");
190
+
191
+ /**
192
+ * A string literal for a language with C-style escapes: the delimiter and
193
+ * `\` escaped, newline, carriage return, and tab by name, any other control
194
+ * character through `control` (each language spells a code point
195
+ * differently, and some reject JSON's `\uXXXX`), and each character in
196
+ * `sigils` backslash-escaped so it can't start an interpolation (`$` in
197
+ * Kotlin and Dart, `#` in Ruby).
198
+ */
199
+ const quoted = (
200
+ value: string,
201
+ control: (code: number) => string,
202
+ sigils = "",
203
+ delimiter = '"'
204
+ ): string => {
205
+ let out = delimiter;
206
+ for (const char of value) {
207
+ const code = char.codePointAt(0) ?? 0;
208
+ if (char === delimiter || char === "\\" || sigils.includes(char)) {
209
+ out += `\\${char}`;
210
+ } else if (char === "\n") {
211
+ out += String.raw`\n`;
212
+ } else if (char === "\r") {
213
+ out += String.raw`\r`;
214
+ } else if (char === "\t") {
215
+ out += String.raw`\t`;
216
+ } else if (code < 0x20 || code === 0x7f) {
217
+ out += control(code);
218
+ } else {
219
+ out += char;
220
+ }
221
+ }
222
+ return `${out}${delimiter}`;
223
+ };
224
+
225
+ const goString = (value: string): string =>
226
+ quoted(value, (code) => `\\x${hex(code, 2)}`);
227
+ // Java decodes `\uXXXX` before it tokenizes, which is safe for a control
228
+ // character inside a string literal (only a line break isn't, and those are
229
+ // spelled `\n` and `\r`).
230
+ const javaString = (value: string): string =>
231
+ quoted(value, (code) => `\\u${hex(code, 4)}`);
232
+ const csharpString = javaString;
233
+ // C and C++ reject a `\u` escape below U+00A0; octal always works. A `?`
234
+ // before another is escaped too, so no `??=`-style trigraph can form (strict
235
+ // C modes still translate them).
236
+ const cString = (value: string): string =>
237
+ quoted(value, (code) => `\\${code.toString(8).padStart(3, "0")}`).replaceAll(
238
+ /\?(?=\?)/gu,
239
+ "?\\"
240
+ );
241
+ const braceString = (value: string): string =>
242
+ quoted(value, (code) => `\\u{${hex(code, 1)}}`);
243
+ const kotlinString = (value: string): string =>
244
+ quoted(value, (code) => `\\u${hex(code, 4)}`, "$");
245
+ const rubyString = (value: string): string =>
246
+ quoted(value, (code) => `\\u${hex(code, 4)}`, "#");
247
+ const dartString = (value: string): string =>
248
+ quoted(value, (code) => `\\x${hex(code, 2)}`, "$", "'");
249
+
250
+ /**
251
+ * A single-quoted PHP string: nothing interpolates, and only `\` and `'`
252
+ * need escaping.
253
+ */
254
+ const phpString = (value: string): string =>
255
+ `'${value.replaceAll("\\", "\\\\").replaceAll("'", String.raw`\'`)}'`;
256
+
257
+ /**
258
+ * A single-quoted PowerShell string: nothing expands, and a quote is doubled.
259
+ * PowerShell also closes a string on the typographic single quotes, so those
260
+ * are doubled too.
261
+ */
262
+ const powershellString = (value: string): string =>
263
+ `'${value.replaceAll(/['‘’‚‛]/gu, (quote) => quote + quote)}'`;
264
+
265
+ /** A request's `Content-Type` apart from its other headers. */
266
+ interface ContentTypeSplit {
267
+ contentType: string | undefined;
268
+ rest: [string, string][];
269
+ }
270
+
271
+ /** The request's `Content-Type`, and its other headers without it. */
272
+ const splitContentType = (
273
+ headers: Record<string, string>
274
+ ): ContentTypeSplit => {
275
+ const entries = Object.entries(headers);
276
+ const match = entries.find(([key]) => key.toLowerCase() === "content-type");
277
+ return {
278
+ contentType: match?.[1],
279
+ rest: entries.filter(([key]) => key.toLowerCase() !== "content-type"),
280
+ };
281
+ };
282
+
283
+ /**
284
+ * The boundary for a multipart body written out by hand, for clients with no
285
+ * form builder of their own (Java's and Swift's).
286
+ */
287
+ const MULTIPART_BOUNDARY = "BlumeFormBoundary7MA4YWxkTrZu0gW";
288
+
289
+ /** A `multipart/form-data` body for `parts`, as the bytes on the wire. */
290
+ const multipartBody = (parts: [string, string][]): string =>
291
+ [
292
+ ...parts.map(
293
+ ([name, value]) =>
294
+ `--${MULTIPART_BOUNDARY}\r\nContent-Disposition: form-data; name="${name
295
+ .replaceAll('"', "%22")
296
+ .replaceAll("\r", "%0D")
297
+ .replaceAll("\n", "%0A")}"\r\n\r\n${value}\r\n`
298
+ ),
299
+ `--${MULTIPART_BOUNDARY}--\r\n`,
300
+ ].join("");
301
+
302
+ /** A note in place of a sample whose client can't send `method`. */
303
+ const unsupportedMethod = (
304
+ comment: string,
305
+ client: string,
306
+ method: string
307
+ ): string =>
308
+ `${comment} ${client} has no ${method} method, so there's no sample for this\n${comment} operation here. Send it with curl or another HTTP client instead.`;
309
+
310
+ // ---------------------------------------------------------------------------
311
+ // Node.js (axios)
312
+ // ---------------------------------------------------------------------------
313
+
314
+ const nodeSnippet = (sample: RequestSample): string => {
315
+ const config = [
316
+ ` method: ${stringLiteral(sample.method)}`,
317
+ ` url: ${stringLiteral(sample.url)}`,
318
+ ];
319
+ if (Object.keys(sample.headers).length > 0) {
320
+ const headers = headerLines(
321
+ sample.headers,
322
+ (key, value) => ` ${stringLiteral(key)}: ${stringLiteral(value)}`
323
+ ).join(",\n");
324
+ config.push(` headers: {\n${headers}\n }`);
325
+ }
326
+ if (sample.body) {
327
+ config.push(` data: ${stringLiteral(sample.body)}`);
328
+ }
329
+ let form = "";
330
+ if (sample.formData) {
331
+ form = [
332
+ "const form = new FormData();\n",
333
+ ...sample.formData.map(
334
+ ([name, value]) =>
335
+ `form.append(${stringLiteral(name)}, ${stringLiteral(value)});\n`
336
+ ),
337
+ "\n",
338
+ ].join("");
339
+ config.push(" data: form");
340
+ }
341
+ return `import axios from "axios";\n\n${form}const response = await axios.request({\n${config.join(",\n")}\n});`;
342
+ };
343
+
344
+ // ---------------------------------------------------------------------------
345
+ // PHP (the curl extension)
346
+ // ---------------------------------------------------------------------------
347
+
348
+ const phpSnippet = (sample: RequestSample): string => {
349
+ const options = [
350
+ ` CURLOPT_URL => ${phpString(sample.url)}`,
351
+ " CURLOPT_RETURNTRANSFER => true",
352
+ // As with `curl --head`: a HEAD request expects no body.
353
+ sample.method === "HEAD"
354
+ ? " CURLOPT_NOBODY => true"
355
+ : ` CURLOPT_CUSTOMREQUEST => ${phpString(sample.method)}`,
356
+ ];
357
+ const headers = Object.entries(sample.headers);
358
+ if (headers.length > 0) {
359
+ const lines = headers.map(
360
+ ([key, value]) => ` ${phpString(`${key}: ${value}`)},\n`
361
+ );
362
+ options.push(` CURLOPT_HTTPHEADER => [\n${lines.join("")} ]`);
363
+ }
364
+ if (sample.body) {
365
+ options.push(` CURLOPT_POSTFIELDS => ${phpString(sample.body)}`);
366
+ }
367
+ if (sample.formData) {
368
+ // An array (rather than a string) makes curl send multipart/form-data.
369
+ const fields = sample.formData.map(
370
+ ([name, value]) => ` ${phpString(name)} => ${phpString(value)},\n`
371
+ );
372
+ options.push(` CURLOPT_POSTFIELDS => [\n${fields.join("")} ]`);
373
+ }
374
+ return `<?php\n\n$curl = curl_init();\n\ncurl_setopt_array($curl, [\n${options.join(",\n")},\n]);\n\n$response = curl_exec($curl);`;
375
+ };
376
+
377
+ // ---------------------------------------------------------------------------
378
+ // Go (net/http)
379
+ // ---------------------------------------------------------------------------
380
+
381
+ const goSnippet = (sample: RequestSample): string => {
382
+ const imports = new Set(["fmt", "io", "net/http"]);
383
+ const lines: string[] = [];
384
+ let body = "nil";
385
+ if (sample.formData) {
386
+ imports.add("bytes");
387
+ imports.add("mime/multipart");
388
+ lines.push(
389
+ "\tvar body bytes.Buffer",
390
+ "\tform := multipart.NewWriter(&body)",
391
+ ...sample.formData.map(
392
+ ([name, value]) =>
393
+ `\tform.WriteField(${goString(name)}, ${goString(value)})`
394
+ ),
395
+ "\tform.Close()",
396
+ ""
397
+ );
398
+ body = "&body";
399
+ } else if (sample.body) {
400
+ imports.add("strings");
401
+ lines.push(`\tbody := strings.NewReader(${goString(sample.body)})`);
402
+ body = "body";
403
+ }
404
+ lines.push(
405
+ `\treq, err := http.NewRequest(${goString(sample.method)}, ${goString(sample.url)}, ${body})`,
406
+ "\tif err != nil {",
407
+ "\t\tpanic(err)",
408
+ "\t}"
409
+ );
410
+ for (const [key, value] of Object.entries(sample.headers)) {
411
+ lines.push(`\treq.Header.Set(${goString(key)}, ${goString(value)})`);
412
+ }
413
+ if (sample.formData) {
414
+ lines.push('\treq.Header.Set("Content-Type", form.FormDataContentType())');
415
+ }
416
+ lines.push(
417
+ "",
418
+ "\tres, err := http.DefaultClient.Do(req)",
419
+ "\tif err != nil {",
420
+ "\t\tpanic(err)",
421
+ "\t}",
422
+ "\tdefer res.Body.Close()",
423
+ "",
424
+ "\tdata, _ := io.ReadAll(res.Body)",
425
+ "\tfmt.Println(string(data))"
426
+ );
427
+ const importBlock = [...imports]
428
+ .toSorted()
429
+ .map((path) => `\t"${path}"`)
430
+ .join("\n");
431
+ return `package main\n\nimport (\n${importBlock}\n)\n\nfunc main() {\n${lines.join("\n")}\n}`;
432
+ };
433
+
434
+ // ---------------------------------------------------------------------------
435
+ // Java (java.net.http)
436
+ // ---------------------------------------------------------------------------
437
+
438
+ const javaSnippet = (sample: RequestSample): string => {
439
+ const builder = [` .uri(URI.create(${javaString(sample.url)}))`];
440
+ for (const [key, value] of Object.entries(sample.headers)) {
441
+ builder.push(` .header(${javaString(key)}, ${javaString(value)})`);
442
+ }
443
+ let body = "HttpRequest.BodyPublishers.noBody()";
444
+ if (sample.formData) {
445
+ builder.push(
446
+ ` .header("Content-Type", ${javaString(`multipart/form-data; boundary=${MULTIPART_BOUNDARY}`)})`
447
+ );
448
+ body = `HttpRequest.BodyPublishers.ofString(${javaString(multipartBody(sample.formData))})`;
449
+ } else if (sample.body) {
450
+ body = `HttpRequest.BodyPublishers.ofString(${javaString(sample.body)})`;
451
+ }
452
+ builder.push(
453
+ ` .method(${javaString(sample.method)}, ${body})`,
454
+ " .build();"
455
+ );
456
+ return [
457
+ "import java.net.URI;",
458
+ "import java.net.http.HttpClient;",
459
+ "import java.net.http.HttpRequest;",
460
+ "import java.net.http.HttpResponse;",
461
+ "",
462
+ "HttpRequest request = HttpRequest.newBuilder()",
463
+ ...builder,
464
+ "HttpResponse<String> response = HttpClient.newHttpClient()",
465
+ " .send(request, HttpResponse.BodyHandlers.ofString());",
466
+ ].join("\n");
467
+ };
468
+
469
+ // ---------------------------------------------------------------------------
470
+ // Ruby (net/http)
471
+ // ---------------------------------------------------------------------------
472
+
473
+ /** The `Net::HTTP` request class for each method it names. */
474
+ const RUBY_METHODS = new Map([
475
+ ["DELETE", "Delete"],
476
+ ["GET", "Get"],
477
+ ["HEAD", "Head"],
478
+ ["OPTIONS", "Options"],
479
+ ["PATCH", "Patch"],
480
+ ["POST", "Post"],
481
+ ["PUT", "Put"],
482
+ ["TRACE", "Trace"],
483
+ ]);
484
+
485
+ const rubySnippet = (sample: RequestSample): string => {
486
+ const known = RUBY_METHODS.get(sample.method);
487
+ const lines = [
488
+ 'require "net/http"',
489
+ "",
490
+ `uri = URI(${rubyString(sample.url)})`,
491
+ known
492
+ ? `request = Net::HTTP::${known}.new(uri)`
493
+ : `request = Net::HTTPGenericRequest.new(${rubyString(sample.method)}, true, true, uri)`,
494
+ ];
495
+ for (const [key, value] of Object.entries(sample.headers)) {
496
+ lines.push(`request[${rubyString(key)}] = ${rubyString(value)}`);
497
+ }
498
+ if (sample.body) {
499
+ lines.push(`request.body = ${rubyString(sample.body)}`);
500
+ }
501
+ if (sample.formData) {
502
+ const fields = sample.formData
503
+ .map(([name, value]) => `[${rubyString(name)}, ${rubyString(value)}]`)
504
+ .join(", ");
505
+ lines.push(`request.set_form([${fields}], "multipart/form-data")`);
506
+ }
507
+ lines.push(
508
+ "",
509
+ 'response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == "https") do |http|',
510
+ " http.request(request)",
511
+ "end"
512
+ );
513
+ return lines.join("\n");
514
+ };
515
+
516
+ // ---------------------------------------------------------------------------
517
+ // PowerShell (Invoke-RestMethod)
518
+ // ---------------------------------------------------------------------------
519
+
520
+ /** The methods `Invoke-RestMethod -Method` names; others take `-CustomMethod`. */
521
+ const POWERSHELL_METHODS = new Set([
522
+ "DELETE",
523
+ "GET",
524
+ "HEAD",
525
+ "OPTIONS",
526
+ "PATCH",
527
+ "POST",
528
+ "PUT",
529
+ "TRACE",
530
+ ]);
531
+
532
+ const powershellSnippet = (sample: RequestSample): string => {
533
+ const { contentType, rest } = splitContentType(sample.headers);
534
+ const lines: string[] = [];
535
+ const args = [
536
+ `-Uri ${powershellString(sample.url)}`,
537
+ POWERSHELL_METHODS.has(sample.method)
538
+ ? `-Method ${sample.method}`
539
+ : `-CustomMethod ${powershellString(sample.method)}`,
540
+ ];
541
+ if (rest.length > 0) {
542
+ lines.push(
543
+ "$headers = @{",
544
+ ...rest.map(
545
+ ([key, value]) =>
546
+ ` ${powershellString(key)} = ${powershellString(value)}`
547
+ ),
548
+ "}"
549
+ );
550
+ args.push("-Headers $headers");
551
+ }
552
+ // Invoke-RestMethod takes the body's type as a parameter, not a header.
553
+ if (contentType) {
554
+ args.push(`-ContentType ${powershellString(contentType)}`);
555
+ }
556
+ if (sample.body) {
557
+ args.push(`-Body ${powershellString(sample.body)}`);
558
+ }
559
+ if (sample.formData) {
560
+ lines.push(
561
+ "$form = @{",
562
+ ...sample.formData.map(
563
+ ([name, value]) =>
564
+ ` ${powershellString(name)} = ${powershellString(value)}`
565
+ ),
566
+ "}"
567
+ );
568
+ args.push("-Form $form");
569
+ }
570
+ lines.push(`$response = Invoke-RestMethod ${args.join(" ")}`);
571
+ return lines.join("\n");
572
+ };
573
+
574
+ // ---------------------------------------------------------------------------
575
+ // Swift (URLSession)
576
+ // ---------------------------------------------------------------------------
577
+
578
+ const swiftSnippet = (sample: RequestSample): string => {
579
+ const lines = [
580
+ "import Foundation",
581
+ "",
582
+ `var request = URLRequest(url: URL(string: ${braceString(sample.url)})!)`,
583
+ `request.httpMethod = ${braceString(sample.method)}`,
584
+ ];
585
+ for (const [key, value] of Object.entries(sample.headers)) {
586
+ lines.push(
587
+ `request.setValue(${braceString(value)}, forHTTPHeaderField: ${braceString(key)})`
588
+ );
589
+ }
590
+ if (sample.formData) {
591
+ lines.push(
592
+ `request.setValue(${braceString(`multipart/form-data; boundary=${MULTIPART_BOUNDARY}`)}, forHTTPHeaderField: "Content-Type")`,
593
+ `request.httpBody = Data(${braceString(multipartBody(sample.formData))}.utf8)`
594
+ );
595
+ } else if (sample.body) {
596
+ lines.push(`request.httpBody = Data(${braceString(sample.body)}.utf8)`);
597
+ }
598
+ lines.push(
599
+ "",
600
+ "let (data, response) = try await URLSession.shared.data(for: request)"
601
+ );
602
+ return lines.join("\n");
603
+ };
604
+
605
+ // ---------------------------------------------------------------------------
606
+ // C# (HttpClient)
607
+ // ---------------------------------------------------------------------------
608
+
609
+ const csharpSnippet = (sample: RequestSample): string => {
610
+ const { contentType, rest } = splitContentType(sample.headers);
611
+ const lines = [
612
+ "using System.Net.Http;",
613
+ "using System.Net.Http.Headers;",
614
+ "",
615
+ "using var client = new HttpClient();",
616
+ `var request = new HttpRequestMessage(new HttpMethod(${csharpString(sample.method)}), ${csharpString(sample.url)});`,
617
+ ];
618
+ // Without validation, so a header .NET would reformat is sent as written.
619
+ for (const [key, value] of rest) {
620
+ lines.push(
621
+ `request.Headers.TryAddWithoutValidation(${csharpString(key)}, ${csharpString(value)});`
622
+ );
623
+ }
624
+ if (sample.body) {
625
+ lines.push(
626
+ `request.Content = new StringContent(${csharpString(sample.body)});`
627
+ );
628
+ // The body's type is a content header in .NET, not a request header.
629
+ if (contentType) {
630
+ lines.push(
631
+ `request.Content.Headers.ContentType = MediaTypeHeaderValue.Parse(${csharpString(contentType)});`
632
+ );
633
+ }
634
+ }
635
+ if (sample.formData) {
636
+ lines.push(
637
+ "var form = new MultipartFormDataContent();",
638
+ ...sample.formData.map(
639
+ ([name, value]) =>
640
+ `form.Add(new StringContent(${csharpString(value)}), ${csharpString(name)});`
641
+ ),
642
+ "request.Content = form;"
643
+ );
644
+ }
645
+ lines.push(
646
+ "",
647
+ "var response = await client.SendAsync(request);",
648
+ "var body = await response.Content.ReadAsStringAsync();"
649
+ );
650
+ return lines.join("\n");
651
+ };
652
+
653
+ // ---------------------------------------------------------------------------
654
+ // .NET (RestSharp)
655
+ // ---------------------------------------------------------------------------
656
+
657
+ /** RestSharp's `Method` members, by HTTP method. */
658
+ const RESTSHARP_METHODS = new Map([
659
+ ["DELETE", "Delete"],
660
+ ["GET", "Get"],
661
+ ["HEAD", "Head"],
662
+ ["OPTIONS", "Options"],
663
+ ["PATCH", "Patch"],
664
+ ["POST", "Post"],
665
+ ["PUT", "Put"],
666
+ ]);
667
+
668
+ const dotnetSnippet = (sample: RequestSample): string => {
669
+ const method = RESTSHARP_METHODS.get(sample.method);
670
+ if (!method) {
671
+ return unsupportedMethod("//", "RestSharp", sample.method);
672
+ }
673
+ const { contentType, rest } = splitContentType(sample.headers);
674
+ const lines = [
675
+ "using RestSharp;",
676
+ "",
677
+ "var client = new RestClient();",
678
+ `var request = new RestRequest(${csharpString(sample.url)}, Method.${method});`,
679
+ ];
680
+ for (const [key, value] of rest) {
681
+ lines.push(
682
+ `request.AddHeader(${csharpString(key)}, ${csharpString(value)});`
683
+ );
684
+ }
685
+ if (sample.body) {
686
+ // The body carries its own type; a Content-Type header would be ignored.
687
+ lines.push(
688
+ `request.AddStringBody(${csharpString(sample.body)}, ${csharpString(contentType ?? "text/plain")});`
689
+ );
690
+ }
691
+ if (sample.formData) {
692
+ lines.push(
693
+ "request.AlwaysMultipartFormData = true;",
694
+ ...sample.formData.map(
695
+ ([name, value]) =>
696
+ `request.AddParameter(${csharpString(name)}, ${csharpString(value)});`
697
+ )
698
+ );
699
+ }
700
+ lines.push("", "var response = await client.ExecuteAsync(request);");
701
+ return lines.join("\n");
702
+ };
703
+
704
+ // ---------------------------------------------------------------------------
705
+ // C (libcurl)
706
+ // ---------------------------------------------------------------------------
707
+
708
+ const cSnippet = (sample: RequestSample): string => {
709
+ const lines = [
710
+ "#include <curl/curl.h>",
711
+ "",
712
+ "int main(void) {",
713
+ " CURL *curl = curl_easy_init();",
714
+ ];
715
+ const headers = Object.entries(sample.headers);
716
+ if (headers.length > 0) {
717
+ lines.push(" struct curl_slist *headers = NULL;");
718
+ for (const [key, value] of headers) {
719
+ lines.push(
720
+ ` headers = curl_slist_append(headers, ${cString(`${key}: ${value}`)});`
721
+ );
722
+ }
723
+ }
724
+ lines.push(
725
+ "",
726
+ ` curl_easy_setopt(curl, CURLOPT_URL, ${cString(sample.url)});`,
727
+ // As with `curl --head`: a HEAD request expects no body.
728
+ sample.method === "HEAD"
729
+ ? " curl_easy_setopt(curl, CURLOPT_NOBODY, 1L);"
730
+ : ` curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, ${cString(sample.method)});`
731
+ );
732
+ if (headers.length > 0) {
733
+ lines.push(" curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);");
734
+ }
735
+ if (sample.body) {
736
+ lines.push(
737
+ ` curl_easy_setopt(curl, CURLOPT_POSTFIELDS, ${cString(sample.body)});`
738
+ );
739
+ }
740
+ if (sample.formData) {
741
+ lines.push(
742
+ "",
743
+ " curl_mime *form = curl_mime_init(curl);",
744
+ " curl_mimepart *part;"
745
+ );
746
+ for (const [name, value] of sample.formData) {
747
+ lines.push(
748
+ " part = curl_mime_addpart(form);",
749
+ ` curl_mime_name(part, ${cString(name)});`,
750
+ ` curl_mime_data(part, ${cString(value)}, CURL_ZERO_TERMINATED);`
751
+ );
752
+ }
753
+ lines.push(" curl_easy_setopt(curl, CURLOPT_MIMEPOST, form);");
754
+ }
755
+ lines.push("", " CURLcode result = curl_easy_perform(curl);", "");
756
+ if (sample.formData) {
757
+ lines.push(" curl_mime_free(form);");
758
+ }
759
+ if (headers.length > 0) {
760
+ lines.push(" curl_slist_free_all(headers);");
761
+ }
762
+ lines.push(
763
+ " curl_easy_cleanup(curl);",
764
+ " return result == CURLE_OK ? 0 : 1;",
765
+ "}"
766
+ );
767
+ return lines.join("\n");
768
+ };
769
+
770
+ // ---------------------------------------------------------------------------
771
+ // C++ (cpr)
772
+ // ---------------------------------------------------------------------------
773
+
774
+ /** cpr's request functions, by HTTP method. */
775
+ const CPR_METHODS = new Map([
776
+ ["DELETE", "Delete"],
777
+ ["GET", "Get"],
778
+ ["HEAD", "Head"],
779
+ ["OPTIONS", "Options"],
780
+ ["PATCH", "Patch"],
781
+ ["POST", "Post"],
782
+ ["PUT", "Put"],
783
+ ]);
784
+
785
+ const cppSnippet = (sample: RequestSample): string => {
786
+ const method = CPR_METHODS.get(sample.method);
787
+ if (!method) {
788
+ return unsupportedMethod("//", "cpr", sample.method);
789
+ }
790
+ const args = [` cpr::Url{${cString(sample.url)}}`];
791
+ const headers = Object.entries(sample.headers);
792
+ if (headers.length > 0) {
793
+ const pairs = headers
794
+ .map(([key, value]) => `{${cString(key)}, ${cString(value)}}`)
795
+ .join(", ");
796
+ args.push(` cpr::Header{${pairs}}`);
797
+ }
798
+ if (sample.body) {
799
+ args.push(` cpr::Body{${cString(sample.body)}}`);
800
+ }
801
+ if (sample.formData) {
802
+ const parts = sample.formData
803
+ .map(([name, value]) => `{${cString(name)}, ${cString(value)}}`)
804
+ .join(", ");
805
+ args.push(` cpr::Multipart{${parts}}`);
806
+ }
807
+ return [
808
+ "#include <cpr/cpr.h>",
809
+ "",
810
+ "int main() {",
811
+ ` cpr::Response response = cpr::${method}(`,
812
+ `${args.join(",\n")});`,
813
+ "}",
814
+ ].join("\n");
815
+ };
816
+
817
+ // ---------------------------------------------------------------------------
818
+ // Kotlin (OkHttp)
819
+ // ---------------------------------------------------------------------------
820
+
821
+ /** Methods OkHttp refuses to send a body with, and ones it requires one for. */
822
+ const OKHTTP_NO_BODY = new Set(["GET", "HEAD"]);
823
+ const OKHTTP_NEEDS_BODY = new Set(["PATCH", "POST", "PUT"]);
824
+
825
+ const kotlinSnippet = (sample: RequestSample): string => {
826
+ const { contentType, rest } = splitContentType(sample.headers);
827
+ const imports = new Set(["okhttp3.OkHttpClient", "okhttp3.Request"]);
828
+ let body = "null";
829
+ if (sample.formData) {
830
+ imports.add("okhttp3.MultipartBody");
831
+ const parts = sample.formData
832
+ .map(
833
+ ([name, value]) =>
834
+ `\n .addFormDataPart(${kotlinString(name)}, ${kotlinString(value)})`
835
+ )
836
+ .join("");
837
+ body = `MultipartBody.Builder()\n .setType(MultipartBody.FORM)${parts}\n .build()`;
838
+ } else if (sample.body && !OKHTTP_NO_BODY.has(sample.method)) {
839
+ imports.add("okhttp3.RequestBody.Companion.toRequestBody");
840
+ // OkHttp takes the body's type from the body, not from a header.
841
+ let mediaType = "";
842
+ if (contentType) {
843
+ imports.add("okhttp3.MediaType.Companion.toMediaType");
844
+ mediaType = `${kotlinString(contentType)}.toMediaType()`;
845
+ }
846
+ body = `${kotlinString(sample.body)}.toRequestBody(${mediaType})`;
847
+ } else if (OKHTTP_NEEDS_BODY.has(sample.method)) {
848
+ imports.add("okhttp3.RequestBody.Companion.toRequestBody");
849
+ body = '"".toRequestBody()';
850
+ }
851
+ const builder = [` .url(${kotlinString(sample.url)})`];
852
+ builder.push(
853
+ ` .method(${kotlinString(sample.method)}, ${body.replaceAll("\n", "\n ")})`
854
+ );
855
+ for (const [key, value] of rest) {
856
+ builder.push(` .addHeader(${kotlinString(key)}, ${kotlinString(value)})`);
857
+ }
858
+ builder.push(" .build()");
859
+ const importLines = [...imports]
860
+ .toSorted()
861
+ .map((name) => `import ${name}`)
862
+ .join("\n");
863
+ return [
864
+ importLines,
865
+ "",
866
+ "val client = OkHttpClient()",
867
+ "",
868
+ "val request = Request.Builder()",
869
+ ...builder,
870
+ "",
871
+ "client.newCall(request).execute().use { response ->",
872
+ " println(response.body?.string())",
873
+ "}",
874
+ ].join("\n");
875
+ };
876
+
877
+ // ---------------------------------------------------------------------------
878
+ // Rust (reqwest)
879
+ // ---------------------------------------------------------------------------
880
+
881
+ /** The methods `reqwest::Method` has a constant for. */
882
+ const RUST_METHODS = new Set([
883
+ "CONNECT",
884
+ "DELETE",
885
+ "GET",
886
+ "HEAD",
887
+ "OPTIONS",
888
+ "PATCH",
889
+ "POST",
890
+ "PUT",
891
+ "TRACE",
892
+ ]);
893
+
894
+ const rustSnippet = (sample: RequestSample): string => {
895
+ const method = RUST_METHODS.has(sample.method)
896
+ ? `reqwest::Method::${sample.method}`
897
+ : `reqwest::Method::from_bytes(${braceString(sample.method)}.as_bytes()).unwrap()`;
898
+ const chain = [` .request(${method}, ${braceString(sample.url)})`];
899
+ for (const [key, value] of Object.entries(sample.headers)) {
900
+ chain.push(` .header(${braceString(key)}, ${braceString(value)})`);
901
+ }
902
+ if (sample.body) {
903
+ chain.push(` .body(${braceString(sample.body)})`);
904
+ }
905
+ const lines = [
906
+ "#[tokio::main]",
907
+ "async fn main() -> Result<(), reqwest::Error> {",
908
+ ];
909
+ if (sample.formData) {
910
+ // Needs reqwest's `multipart` feature.
911
+ const parts = sample.formData
912
+ .map(
913
+ ([name, value]) =>
914
+ `\n .text(${braceString(name)}, ${braceString(value)})`
915
+ )
916
+ .join("");
917
+ lines.push(` let form = reqwest::multipart::Form::new()${parts};`, "");
918
+ chain.push(" .multipart(form)");
919
+ }
920
+ lines.push(
921
+ " let response = reqwest::Client::new()",
922
+ ...chain,
923
+ " .send()",
924
+ " .await?;",
925
+ "",
926
+ ' println!("{}", response.text().await?);',
927
+ " Ok(())",
928
+ "}"
929
+ );
930
+ return lines.join("\n");
931
+ };
932
+
933
+ // ---------------------------------------------------------------------------
934
+ // Dart (package:http)
935
+ // ---------------------------------------------------------------------------
936
+
937
+ const dartSnippet = (sample: RequestSample): string => {
938
+ const lines = ["import 'package:http/http.dart' as http;", ""];
939
+ const target = `${dartString(sample.method)}, Uri.parse(${dartString(sample.url)})`;
940
+ if (sample.formData) {
941
+ lines.push(`final request = http.MultipartRequest(${target});`);
942
+ for (const [name, value] of sample.formData) {
943
+ lines.push(`request.fields[${dartString(name)}] = ${dartString(value)};`);
944
+ }
945
+ } else {
946
+ lines.push(`final request = http.Request(${target});`);
947
+ }
948
+ const headers = Object.entries(sample.headers);
949
+ if (headers.length > 0) {
950
+ lines.push(
951
+ "request.headers.addAll({",
952
+ ...headers.map(
953
+ ([key, value]) => ` ${dartString(key)}: ${dartString(value)},`
954
+ ),
955
+ "});"
956
+ );
957
+ }
958
+ if (sample.body) {
959
+ lines.push(`request.body = ${dartString(sample.body)};`);
960
+ }
961
+ lines.push(
962
+ "",
963
+ "final response = await request.send();",
964
+ "print(await response.stream.bytesToString());"
965
+ );
966
+ return lines.join("\n");
967
+ };
968
+
178
969
  /** A code-sample language: config id -> label, Shiki lang, and builder. */
179
970
  export interface SampleLanguage {
180
971
  id: string;
@@ -185,27 +976,97 @@ export interface SampleLanguage {
185
976
 
186
977
  const LANGUAGES: SampleLanguage[] = [
187
978
  { build: curlSnippet, id: "curl", label: "cURL", lang: "bash" },
188
- { build: fetchSnippet, id: "js", label: "JavaScript", lang: "js" },
189
979
  { build: pythonSnippet, id: "python", label: "Python", lang: "python" },
980
+ { build: fetchSnippet, id: "js", label: "JavaScript", lang: "js" },
981
+ { build: nodeSnippet, id: "node", label: "Node.js", lang: "js" },
982
+ {
983
+ build: typescriptSnippet,
984
+ id: "typescript",
985
+ label: "TypeScript",
986
+ lang: "ts",
987
+ },
988
+ { build: phpSnippet, id: "php", label: "PHP", lang: "php" },
989
+ { build: goSnippet, id: "go", label: "Go", lang: "go" },
990
+ { build: javaSnippet, id: "java", label: "Java", lang: "java" },
991
+ { build: rubySnippet, id: "ruby", label: "Ruby", lang: "ruby" },
992
+ {
993
+ build: powershellSnippet,
994
+ id: "powershell",
995
+ label: "PowerShell",
996
+ lang: "powershell",
997
+ },
998
+ { build: swiftSnippet, id: "swift", label: "Swift", lang: "swift" },
999
+ { build: csharpSnippet, id: "csharp", label: "C#", lang: "csharp" },
1000
+ { build: dotnetSnippet, id: "dotnet", label: ".NET", lang: "csharp" },
1001
+ { build: cSnippet, id: "c", label: "C", lang: "c" },
1002
+ { build: cppSnippet, id: "cpp", label: "C++", lang: "cpp" },
1003
+ { build: kotlinSnippet, id: "kotlin", label: "Kotlin", lang: "kotlin" },
1004
+ { build: rustSnippet, id: "rust", label: "Rust", lang: "rust" },
1005
+ { build: dartSnippet, id: "dart", label: "Dart", lang: "dart" },
190
1006
  ];
191
1007
 
1008
+ /** Every id `codeSamples` accepts for a generated sample. */
1009
+ export const SAMPLE_LANGUAGE_IDS = LANGUAGES.map((language) => language.id);
1010
+
1011
+ /** Other spellings of the ids, including every alias Mintlify accepts. */
192
1012
  const ALIASES = new Map([
1013
+ [".net", "dotnet"],
193
1014
  ["bash", "curl"],
1015
+ ["c#", "csharp"],
1016
+ ["c++", "cpp"],
1017
+ ["cs", "csharp"],
1018
+ ["dot-net", "dotnet"],
1019
+ ["flutter", "dart"],
1020
+ ["golang", "go"],
194
1021
  ["javascript", "js"],
195
- ["node", "js"],
1022
+ ["kt", "kotlin"],
1023
+ ["node.js", "node"],
1024
+ ["nodejs", "node"],
1025
+ ["ps1", "powershell"],
196
1026
  ["py", "python"],
1027
+ ["rb", "ruby"],
1028
+ ["rs", "rust"],
1029
+ ["sh", "curl"],
197
1030
  ["shell", "curl"],
198
- ["typescript", "js"],
1031
+ ["ts", "typescript"],
199
1032
  ]);
200
1033
 
201
- /** The sample languages to render, resolved from config ids (unknown ids dropped). */
202
- export const sampleLanguages = (ids: string[]): SampleLanguage[] => {
1034
+ /** A configured id or alias, as the id it names (lowercased). */
1035
+ export const sampleLanguageId = (raw: string): string => {
1036
+ const lower = raw.toLowerCase();
1037
+ return ALIASES.get(lower) ?? lower;
1038
+ };
1039
+
1040
+ /** A language's display name and the Shiki language that highlights it. */
1041
+ export interface SampleLanguageInfo {
1042
+ label: string;
1043
+ lang: string;
1044
+ }
1045
+
1046
+ /**
1047
+ * The display name and Shiki language of a language id or alias, or `null`
1048
+ * when Blume doesn't generate that language.
1049
+ */
1050
+ export const sampleLanguageInfo = (raw: string): SampleLanguageInfo | null => {
1051
+ const id = sampleLanguageId(raw);
1052
+ const language = LANGUAGES.find((entry) => entry.id === id);
1053
+ return language ? { label: language.label, lang: language.lang } : null;
1054
+ };
1055
+
1056
+ /**
1057
+ * The sample languages to render, resolved from config ids (unknown ids
1058
+ * dropped). `false` renders none; an empty list, the defaults.
1059
+ */
1060
+ export const sampleLanguages = (ids: string[] | false): SampleLanguage[] => {
1061
+ if (ids === false) {
1062
+ return [];
1063
+ }
203
1064
  const wanted = ids.length > 0 ? ids : ["curl", "js", "python"];
204
1065
  const byId = new Map(LANGUAGES.map((entry) => [entry.id, entry]));
205
1066
  const out: SampleLanguage[] = [];
206
1067
  const seen = new Set<string>();
207
1068
  for (const raw of wanted) {
208
- const id = ALIASES.get(raw.toLowerCase()) ?? raw.toLowerCase();
1069
+ const id = sampleLanguageId(raw);
209
1070
  const language = byId.get(id);
210
1071
  if (language && !seen.has(id)) {
211
1072
  seen.add(id);