blume 2.0.2 → 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 (601) hide show
  1. package/AGENTS.md +1 -0
  2. package/CHANGELOG.md +113 -0
  3. package/README.md +1 -1
  4. package/dist/cli/{chunk-6k4ftwze.js → chunk-0gz36akr.js} +18 -2
  5. package/dist/cli/{chunk-6k4ftwze.js.map → chunk-0gz36akr.js.map} +3 -3
  6. package/dist/cli/{chunk-zp79m0ts.js → chunk-1n228xqb.js} +2 -2
  7. package/dist/cli/{chunk-ddndchfr.js → chunk-2f3ndkpw.js} +57 -29
  8. package/dist/cli/chunk-2f3ndkpw.js.map +14 -0
  9. package/dist/cli/{chunk-fsmrqk8a.js → chunk-2f7n4qzt.js} +8 -2
  10. package/dist/cli/{chunk-fsmrqk8a.js.map → chunk-2f7n4qzt.js.map} +3 -3
  11. package/dist/cli/{chunk-ppzjqwx2.js → chunk-2n4ps1b3.js} +17 -18
  12. package/dist/cli/{chunk-ppzjqwx2.js.map → chunk-2n4ps1b3.js.map} +3 -3
  13. package/dist/cli/{chunk-5r8g91qn.js → chunk-43zcf4gz.js} +398 -159
  14. package/dist/cli/chunk-43zcf4gz.js.map +36 -0
  15. package/dist/cli/chunk-4ejxbzee.js +39 -0
  16. package/dist/cli/chunk-4ejxbzee.js.map +10 -0
  17. package/dist/cli/{chunk-hr8ne106.js → chunk-4h8z9vv5.js} +296 -77
  18. package/dist/cli/chunk-4h8z9vv5.js.map +16 -0
  19. package/dist/cli/{chunk-k7pj68a8.js → chunk-5cg2rzgf.js} +18 -16
  20. package/dist/cli/{chunk-k7pj68a8.js.map → chunk-5cg2rzgf.js.map} +2 -2
  21. package/dist/cli/{chunk-h2ez8dzb.js → chunk-5ffywsmb.js} +4 -4
  22. package/dist/cli/{chunk-g698a744.js → chunk-5wj2qwn2.js} +5 -5
  23. package/dist/cli/chunk-6bm0xpgd.js +18 -0
  24. package/dist/cli/chunk-6bm0xpgd.js.map +10 -0
  25. package/dist/cli/{chunk-3yce002v.js → chunk-713mngdq.js} +15 -8
  26. package/dist/cli/chunk-713mngdq.js.map +14 -0
  27. package/dist/cli/{chunk-91ws1n6j.js → chunk-9geymqns.js} +17 -15
  28. package/dist/cli/{chunk-91ws1n6j.js.map → chunk-9geymqns.js.map} +2 -2
  29. package/dist/cli/{chunk-6dtt0zfn.js → chunk-9xr4dgee.js} +27 -18
  30. package/dist/cli/chunk-9xr4dgee.js.map +10 -0
  31. package/dist/cli/{chunk-sqw4ekg1.js → chunk-amrb6hgh.js} +2 -2
  32. package/dist/cli/chunk-cm89scdv.js +443 -0
  33. package/dist/cli/chunk-cm89scdv.js.map +11 -0
  34. package/dist/cli/{chunk-wdrt2k2v.js → chunk-cmtcnh9f.js} +90 -26
  35. package/dist/cli/chunk-cmtcnh9f.js.map +10 -0
  36. package/dist/cli/chunk-cy5bm0j0.js +104 -0
  37. package/dist/cli/chunk-cy5bm0j0.js.map +11 -0
  38. package/dist/cli/{chunk-7mbqtmgb.js → chunk-e7vvk5ya.js} +21 -12
  39. package/dist/cli/chunk-e7vvk5ya.js.map +10 -0
  40. package/dist/cli/{chunk-qkb5a8sa.js → chunk-g2z9x01v.js} +6 -98
  41. package/dist/cli/chunk-g2z9x01v.js.map +10 -0
  42. package/dist/cli/{chunk-273ygyr4.js → chunk-ga0pf4aj.js} +7 -7
  43. package/dist/cli/{chunk-273ygyr4.js.map → chunk-ga0pf4aj.js.map} +3 -3
  44. package/dist/cli/{chunk-h7k3nq3v.js → chunk-gp84za0h.js} +2 -2
  45. package/dist/cli/{chunk-hqp2ajnh.js → chunk-h2a48yhd.js} +195 -73
  46. package/dist/cli/chunk-h2a48yhd.js.map +36 -0
  47. package/dist/cli/{chunk-pbg5a4s3.js → chunk-jnwj5kjj.js} +71 -47
  48. package/dist/cli/chunk-jnwj5kjj.js.map +19 -0
  49. package/dist/cli/chunk-kwrg63tm.js +31 -0
  50. package/dist/cli/chunk-kwrg63tm.js.map +10 -0
  51. package/dist/cli/{chunk-8g8ytmgx.js → chunk-nd1821sk.js} +21 -19
  52. package/dist/cli/{chunk-8g8ytmgx.js.map → chunk-nd1821sk.js.map} +2 -2
  53. package/dist/cli/{chunk-w4bxdvsa.js → chunk-ntv4qrt7.js} +17 -15
  54. package/dist/cli/{chunk-w4bxdvsa.js.map → chunk-ntv4qrt7.js.map} +2 -2
  55. package/dist/cli/{chunk-bfwp9vp6.js → chunk-p804f3pe.js} +7 -7
  56. package/dist/cli/{chunk-4e9b9ra6.js → chunk-py62fewh.js} +6 -6
  57. package/dist/cli/{chunk-4e9b9ra6.js.map → chunk-py62fewh.js.map} +1 -1
  58. package/dist/cli/chunk-q3wxxq39.js +126 -0
  59. package/dist/cli/chunk-q3wxxq39.js.map +11 -0
  60. package/dist/cli/{chunk-j85scx15.js → chunk-rekqz4qg.js} +142 -46
  61. package/dist/cli/chunk-rekqz4qg.js.map +10 -0
  62. package/dist/cli/{chunk-00gs3wqs.js → chunk-ta27feev.js} +1 -1
  63. package/dist/cli/{chunk-v6ya5kcb.js → chunk-xkz5xbtv.js} +4031 -1132
  64. package/dist/cli/chunk-xkz5xbtv.js.map +213 -0
  65. package/dist/cli/{chunk-bbnwccaz.js → chunk-y4ykbark.js} +2 -2
  66. package/dist/cli/{chunk-n1yg3tj3.js → chunk-y5h4r2y0.js} +6 -6
  67. package/dist/cli/{chunk-n1yg3tj3.js.map → chunk-y5h4r2y0.js.map} +3 -3
  68. package/dist/cli/{chunk-nfcyttvj.js → chunk-yj17vs39.js} +9 -9
  69. package/dist/cli/{chunk-nfcyttvj.js.map → chunk-yj17vs39.js.map} +1 -1
  70. package/dist/cli/{chunk-7vtckvaw.js → chunk-yxmk4pec.js} +16 -14
  71. package/dist/cli/{chunk-7vtckvaw.js.map → chunk-yxmk4pec.js.map} +2 -2
  72. package/dist/cli/{chunk-gs7r695n.js → chunk-zccbjcj0.js} +22 -8
  73. package/dist/cli/{chunk-gs7r695n.js.map → chunk-zccbjcj0.js.map} +3 -3
  74. package/dist/cli/{chunk-xh43dwgw.js → chunk-zf748avv.js} +415 -225
  75. package/dist/cli/chunk-zf748avv.js.map +37 -0
  76. package/dist/cli/{chunk-d1v5rhy0.js → chunk-zhvd6adm.js} +16 -14
  77. package/dist/cli/{chunk-d1v5rhy0.js.map → chunk-zhvd6adm.js.map} +2 -2
  78. package/dist/cli/index.js +28 -20
  79. package/dist/cli/index.js.map +5 -5
  80. package/dist/types/ai/agent-surface.d.ts +8 -7
  81. package/dist/types/ai/ai-catalog.d.ts +7 -0
  82. package/dist/types/ai/ask-context.d.ts +22 -0
  83. package/dist/types/ai/ask.d.ts +271 -58
  84. package/dist/types/ai/index.d.ts +2 -2
  85. package/dist/types/ai/link-headers.d.ts +8 -1
  86. package/dist/types/ai/openapi-components.d.ts +5 -2
  87. package/dist/types/ai/relative-links.d.ts +9 -7
  88. package/dist/types/ai/site-skill.d.ts +26 -0
  89. package/dist/types/ai/skills.d.ts +20 -1
  90. package/dist/types/ai/tar.d.ts +1 -3
  91. package/dist/types/analytics/index.d.ts +2 -0
  92. package/dist/types/analytics/one-dollar-stats.d.ts +48 -0
  93. package/dist/types/analytics/schema.d.ts +14 -0
  94. package/dist/types/astro/integration.d.ts +11 -2
  95. package/dist/types/captcha/hcaptcha.d.ts +27 -0
  96. package/dist/types/captcha/index.d.ts +28 -0
  97. package/dist/types/captcha/schema.d.ts +29 -0
  98. package/dist/types/captcha/turnstile.d.ts +28 -0
  99. package/dist/types/components/content/api-field.d.ts +59 -0
  100. package/dist/types/components/content/api-page.d.ts +95 -0
  101. package/dist/types/components/layout/nav-utils.d.ts +26 -0
  102. package/dist/types/components/openapi/helpers.d.ts +137 -0
  103. package/dist/types/components/openapi/security.d.ts +85 -0
  104. package/dist/types/consent/ethyca.d.ts +48 -0
  105. package/dist/types/consent/index.d.ts +37 -0
  106. package/dist/types/consent/native.d.ts +28 -0
  107. package/dist/types/consent/osano.d.ts +42 -0
  108. package/dist/types/consent/schema.d.ts +110 -0
  109. package/dist/types/core/base-path.d.ts +21 -7
  110. package/dist/types/core/config-input.d.ts +196 -27
  111. package/dist/types/core/data.d.ts +49 -4
  112. package/dist/types/core/directive-diagnostics.d.ts +12 -0
  113. package/dist/types/core/footer.d.ts +8 -0
  114. package/dist/types/core/heading-markers.d.ts +5 -7
  115. package/dist/types/core/i18n-ui.d.ts +86 -0
  116. package/dist/types/core/i18n.d.ts +2 -0
  117. package/dist/types/core/includes.d.ts +8 -2
  118. package/dist/types/core/last-modified.d.ts +10 -0
  119. package/dist/types/core/localizable.d.ts +7 -0
  120. package/dist/types/core/meta.d.ts +8 -0
  121. package/dist/types/core/page-modes.d.ts +29 -0
  122. package/dist/types/core/redirect-patterns.d.ts +144 -0
  123. package/dist/types/core/related.d.ts +40 -0
  124. package/dist/types/core/schema.d.ts +558 -56
  125. package/dist/types/core/sources/lower.d.ts +29 -16
  126. package/dist/types/core/sources/normalize.d.ts +13 -1
  127. package/dist/types/core/sources/read.d.ts +5 -0
  128. package/dist/types/core/sources/watch.d.ts +12 -5
  129. package/dist/types/core/standard-schema.d.ts +5 -0
  130. package/dist/types/core/types.d.ts +1 -1
  131. package/dist/types/core/variables.d.ts +42 -0
  132. package/dist/types/deploy/artifacts.d.ts +6 -4
  133. package/dist/types/deploy/headers.d.ts +5 -0
  134. package/dist/types/deploy/node-headers.d.ts +13 -2
  135. package/dist/types/deploy/platforms/cloudflare.d.ts +5 -0
  136. package/dist/types/deploy/platforms/netlify.d.ts +7 -0
  137. package/dist/types/deploy/platforms/types.d.ts +8 -0
  138. package/dist/types/deploy/platforms/vercel.d.ts +6 -0
  139. package/dist/types/deploy/redirects.d.ts +61 -22
  140. package/dist/types/deploy/vercel-negotiation.d.ts +9 -0
  141. package/dist/types/markdown/code-title.d.ts +17 -0
  142. package/dist/types/markdown/directives.d.ts +62 -0
  143. package/dist/types/markdown/features.d.ts +24 -2
  144. package/dist/types/markdown/mdast.d.ts +63 -0
  145. package/dist/types/narration/build.d.ts +70 -0
  146. package/dist/types/narration/provider.d.ts +26 -0
  147. package/dist/types/narration/script.d.ts +154 -0
  148. package/dist/types/openapi/asyncapi.d.ts +4 -2
  149. package/dist/types/openapi/model.d.ts +12 -6
  150. package/dist/types/openapi/overlay.d.ts +54 -0
  151. package/dist/types/openapi/parse.d.ts +13 -0
  152. package/dist/types/openapi/references.d.ts +14 -4
  153. package/dist/types/ratelimit/cloudflare.d.ts +39 -0
  154. package/dist/types/ratelimit/index.d.ts +27 -0
  155. package/dist/types/ratelimit/memory.d.ts +36 -0
  156. package/dist/types/ratelimit/schema.d.ts +111 -0
  157. package/dist/types/ratelimit/upstash.d.ts +21 -0
  158. package/dist/types/ratelimit/wrangler.d.ts +23 -0
  159. package/dist/types/reference/asyncapi.d.ts +10 -10
  160. package/dist/types/reference/graphql.d.ts +13 -10
  161. package/dist/types/reference/openapi.d.ts +33 -12
  162. package/dist/types/reference/options.d.ts +45 -5
  163. package/dist/types/reference/scalar.d.ts +13 -0
  164. package/dist/types/reference/schema.d.ts +60 -36
  165. package/dist/types/search/documents.d.ts +12 -1
  166. package/dist/types/search/orama-index.d.ts +4 -0
  167. package/dist/types/seo/metatags.d.ts +20 -0
  168. package/docs/02-deployment.mdx +44 -6
  169. package/docs/03-upgrading.mdx +4 -4
  170. package/docs/04-migrating.mdx +1 -1
  171. package/docs/08-faq.mdx +1 -1
  172. package/docs/advanced/changelog.mdx +2 -2
  173. package/docs/advanced/custom-pages.mdx +3 -3
  174. package/docs/advanced/skills.mdx +14 -0
  175. package/docs/cli/audit.mdx +38 -2
  176. package/docs/cli/evals.mdx +3 -3
  177. package/docs/cli/index.mdx +1 -0
  178. package/docs/cli/translate.mdx +3 -3
  179. package/docs/cli/version.mdx +1 -1
  180. package/docs/configuration/analytics.mdx +22 -1
  181. package/docs/configuration/assistant.mdx +188 -36
  182. package/docs/configuration/consent.mdx +100 -0
  183. package/docs/configuration/customization.mdx +6 -4
  184. package/docs/configuration/index.mdx +63 -5
  185. package/docs/configuration/meta.ts +3 -0
  186. package/docs/configuration/narration.mdx +119 -0
  187. package/docs/configuration/rate-limiting.mdx +69 -0
  188. package/docs/configuration/search.mdx +30 -2
  189. package/docs/configuration/theming.mdx +3 -1
  190. package/docs/content/components.mdx +107 -1
  191. package/docs/content/frontmatter.mdx +77 -1
  192. package/docs/content/i18n.mdx +40 -3
  193. package/docs/content/includes.mdx +16 -0
  194. package/docs/content/index.mdx +4 -2
  195. package/docs/content/islands.mdx +1 -1
  196. package/docs/content/meta.mdx +4 -1
  197. package/docs/content/meta.ts +1 -0
  198. package/docs/content/navigation.mdx +28 -6
  199. package/docs/content/sources/contentful.mdx +32 -0
  200. package/docs/content/sources/custom.mdx +39 -0
  201. package/docs/content/sources/github-releases.mdx +31 -0
  202. package/docs/content/sources/index.mdx +91 -0
  203. package/docs/content/sources/meta.ts +16 -0
  204. package/docs/content/sources/notion.mdx +32 -0
  205. package/docs/content/sources/obsidian.mdx +37 -0
  206. package/docs/content/sources/payload.mdx +30 -0
  207. package/docs/content/sources/remote-mdx.mdx +40 -0
  208. package/docs/content/sources/sanity.mdx +29 -0
  209. package/docs/content/sources/strapi.mdx +31 -0
  210. package/docs/content/syntax.mdx +81 -2
  211. package/docs/content/variables.mdx +41 -0
  212. package/docs/content/versioning.mdx +1 -1
  213. package/docs/discoverability/agent-discovery.mdx +30 -3
  214. package/docs/discoverability/index.mdx +3 -2
  215. package/docs/discoverability/llms-txt.mdx +1 -1
  216. package/docs/discoverability/markdown.mdx +2 -2
  217. package/docs/discoverability/metadata.mdx +17 -0
  218. package/docs/discoverability/open-graph.mdx +5 -3
  219. package/docs/discoverability/rss.mdx +1 -1
  220. package/docs/references/api-pages.mdx +74 -0
  221. package/docs/references/asyncapi.mdx +2 -2
  222. package/docs/references/graphql.mdx +3 -3
  223. package/docs/references/meta.ts +1 -1
  224. package/docs/references/openapi.mdx +106 -5
  225. package/docs/references/scalar.mdx +3 -1
  226. package/package.json +36 -1
  227. package/skills/blume/SKILL.md +3 -0
  228. package/skills/blume-migrate/SKILL.md +5 -5
  229. package/skills/blume-migrate/references/docusaurus.md +3 -3
  230. package/skills/blume-migrate/references/fumadocs.md +1 -1
  231. package/skills/blume-migrate/references/mintlify.md +37 -19
  232. package/skills/blume-migrate/references/nextra.md +3 -3
  233. package/skills/blume-migrate/references/starlight.md +2 -1
  234. package/skills/blume-migrate/scripts/mintlify-codemod.mjs +67 -16
  235. package/skills/blume-write-skill/SKILL.md +82 -0
  236. package/src/ai/agent-readability.ts +4 -3
  237. package/src/ai/agent-surface.ts +9 -7
  238. package/src/ai/ai-catalog.ts +12 -5
  239. package/src/ai/api/handlers.ts +2 -2
  240. package/src/ai/api/spec.ts +2 -2
  241. package/src/ai/api-catalog.ts +6 -2
  242. package/src/ai/ask-context.ts +73 -20
  243. package/src/ai/ask-data.ts +6 -0
  244. package/src/ai/ask-tools.ts +142 -0
  245. package/src/ai/ask.ts +354 -76
  246. package/src/ai/changelog-markdown.ts +2 -2
  247. package/src/ai/component-markdown.ts +75 -0
  248. package/src/ai/cors.ts +2 -0
  249. package/src/ai/index.ts +12 -0
  250. package/src/ai/link-headers.ts +14 -5
  251. package/src/ai/llms.ts +24 -9
  252. package/src/ai/markdown.ts +2 -1
  253. package/src/ai/mcp/data.ts +6 -0
  254. package/src/ai/mcp/discovery.ts +2 -2
  255. package/src/ai/mcp/query.ts +2 -2
  256. package/src/ai/mcp/server.ts +2 -2
  257. package/src/ai/openapi-components.ts +31 -9
  258. package/src/ai/relative-links.ts +42 -26
  259. package/src/ai/serializers.ts +2 -1
  260. package/src/ai/site-skill.ts +274 -0
  261. package/src/ai/skills.ts +49 -3
  262. package/src/ai/tar.ts +139 -12
  263. package/src/ai/write-skill.ts +97 -0
  264. package/src/analytics/head.ts +4 -0
  265. package/src/analytics/index.ts +5 -0
  266. package/src/analytics/one-dollar-stats.ts +86 -0
  267. package/src/analytics/schema.ts +2 -0
  268. package/src/astro/generate.ts +133 -19
  269. package/src/astro/include-hmr.ts +31 -12
  270. package/src/astro/include-refresh.ts +19 -8
  271. package/src/astro/index.ts +1 -0
  272. package/src/astro/integration.ts +143 -14
  273. package/src/astro/module-types.ts +6 -0
  274. package/src/astro/runtime-modules.ts +28 -13
  275. package/src/astro/templates.ts +364 -67
  276. package/src/astro/variables.ts +31 -0
  277. package/src/audit/catalog.ts +19 -0
  278. package/src/audit/checks/contrast.ts +183 -0
  279. package/src/audit/checks/i18n.ts +2 -3
  280. package/src/audit/checks/indexability.ts +2 -3
  281. package/src/audit/checks/links.ts +13 -13
  282. package/src/audit/checks/sitemap.ts +2 -3
  283. package/src/audit/graph.ts +3 -1
  284. package/src/audit/redirects.ts +46 -15
  285. package/src/audit/run.ts +14 -9
  286. package/src/audit/snapshot.ts +5 -0
  287. package/src/audit/types.ts +2 -1
  288. package/src/blume-modules.d.ts +7 -0
  289. package/src/captcha/client.ts +152 -0
  290. package/src/captcha/hcaptcha.ts +36 -0
  291. package/src/captcha/index.ts +28 -0
  292. package/src/captcha/schema.ts +32 -0
  293. package/src/captcha/turnstile.ts +38 -0
  294. package/src/captcha/verify.ts +71 -0
  295. package/src/cli/args.ts +32 -0
  296. package/src/cli/command-meta.ts +5 -0
  297. package/src/cli/commands/audit.ts +4 -1
  298. package/src/cli/commands/doctor.ts +25 -17
  299. package/src/cli/commands/eval.ts +19 -11
  300. package/src/cli/commands/skill.ts +96 -0
  301. package/src/cli/commands/translate.ts +9 -12
  302. package/src/cli/commands/validate.ts +3 -1
  303. package/src/cli/dev-lock.ts +157 -37
  304. package/src/cli/index.ts +5 -0
  305. package/src/cli/report-format.ts +11 -6
  306. package/src/cli/required-secrets.ts +71 -29
  307. package/src/components/BlumePage.astro +2 -0
  308. package/src/components/colors.ts +19 -0
  309. package/src/components/content/ApiEndpoint.astro +18 -0
  310. package/src/components/content/ApiField.astro +79 -0
  311. package/src/components/content/ApiPlayground.astro +62 -0
  312. package/src/components/content/ApiRail.astro +19 -0
  313. package/src/components/content/Badge.astro +5 -3
  314. package/src/components/content/Color.astro +1 -0
  315. package/src/components/content/Component.astro +2 -2
  316. package/src/components/content/FileTree.astro +1 -0
  317. package/src/components/content/GithubInfo.astro +1 -0
  318. package/src/components/content/Panel.astro +1 -0
  319. package/src/components/content/ParamField.astro +25 -0
  320. package/src/components/content/RequestExample.astro +17 -0
  321. package/src/components/content/ResponseExample.astro +17 -0
  322. package/src/components/content/ResponseField.astro +32 -0
  323. package/src/components/content/Step.astro +1 -0
  324. package/src/components/content/Tab.astro +0 -1
  325. package/src/components/content/Tabs.astro +4 -0
  326. package/src/components/content/Tile.astro +1 -0
  327. package/src/components/content/Tooltip.astro +1 -0
  328. package/src/components/content/Tree.astro +1 -0
  329. package/src/components/content/TypeTable.astro +4 -1
  330. package/src/components/content/View.astro +21 -0
  331. package/src/components/content/ViewSwitcher.astro +168 -0
  332. package/src/components/content/api-field.ts +100 -0
  333. package/src/components/content/api-page.ts +224 -0
  334. package/src/components/content/badge-color.ts +5 -2
  335. package/src/components/content/base-href.ts +13 -36
  336. package/src/components/content/view-switcher.ts +71 -0
  337. package/src/components/index.ts +2 -0
  338. package/src/components/islands/Assistant.astro +10 -1
  339. package/src/components/islands/assistant.tsx +125 -18
  340. package/src/components/islands/base-path.ts +47 -10
  341. package/src/components/islands/code-question.ts +62 -0
  342. package/src/components/islands/hooks.ts +145 -40
  343. package/src/components/islands/support-link.ts +68 -0
  344. package/src/components/islands/webmcp.ts +4 -2
  345. package/src/components/layout/Analytics.astro +11 -2
  346. package/src/components/layout/Banner.astro +14 -10
  347. package/src/components/layout/Breadcrumbs.astro +11 -4
  348. package/src/components/layout/ConsentBanner.astro +62 -0
  349. package/src/components/layout/ConsentHead.astro +38 -0
  350. package/src/components/layout/Directory.astro +94 -0
  351. package/src/components/layout/DiscoveryLinks.astro +9 -5
  352. package/src/components/layout/DrawerSwitchers.astro +51 -0
  353. package/src/components/layout/Empty.astro +2 -2
  354. package/src/components/layout/Header.astro +124 -82
  355. package/src/components/layout/LanguageSwitcher.astro +50 -17
  356. package/src/components/layout/NarrationPlayer.astro +1085 -0
  357. package/src/components/layout/NavSelector.astro +35 -9
  358. package/src/components/layout/NavTabMenu.astro +3 -3
  359. package/src/components/layout/NavTree.astro +16 -6
  360. package/src/components/layout/PageActions.astro +4 -3
  361. package/src/components/layout/PageFeedback.astro +54 -49
  362. package/src/components/layout/PageLayout.astro +78 -9
  363. package/src/components/layout/Pagination.astro +3 -3
  364. package/src/components/layout/ReferenceLayout.astro +17 -6
  365. package/src/components/layout/RelatedPages.astro +42 -0
  366. package/src/components/layout/RootLayout.astro +313 -50
  367. package/src/components/layout/Search.astro +64 -30
  368. package/src/components/layout/SiteFooter.astro +124 -0
  369. package/src/components/layout/SiteMetatags.astro +16 -0
  370. package/src/components/layout/VersionBanner.astro +2 -2
  371. package/src/components/layout/analytics-client.ts +12 -0
  372. package/src/components/layout/consent/native.ts +79 -0
  373. package/src/components/layout/consent/types.ts +37 -0
  374. package/src/components/layout/footer-links.ts +53 -0
  375. package/src/components/layout/footer-socials.ts +87 -0
  376. package/src/components/layout/locale-redirect.ts +107 -0
  377. package/src/components/layout/nav-utils.ts +83 -0
  378. package/src/components/layout/page-feedback.ts +96 -0
  379. package/src/components/layout/search/analytics.ts +98 -0
  380. package/src/components/layout/search/flexsearch.ts +17 -5
  381. package/src/components/layout/search/orama-cloud.ts +26 -14
  382. package/src/components/layout/search/types.ts +20 -0
  383. package/src/components/layout/search/typesense.ts +5 -1
  384. package/src/components/layout/toc-active.ts +41 -0
  385. package/src/components/layout/toc-element.ts +8 -14
  386. package/src/components/openapi/ApiTagOperations.astro +2 -2
  387. package/src/components/openapi/AsyncApiOperation.astro +7 -4
  388. package/src/components/openapi/GraphqlChip.astro +2 -2
  389. package/src/components/openapi/Operation.astro +163 -13
  390. package/src/components/openapi/Playground.astro +4 -4
  391. package/src/components/openapi/RequestBody.astro +6 -4
  392. package/src/components/openapi/RequestPanel.astro +63 -8
  393. package/src/components/openapi/Responses.astro +6 -4
  394. package/src/components/openapi/SchemaTable.astro +7 -0
  395. package/src/components/openapi/async-snippets.ts +4 -4
  396. package/src/components/openapi/async.ts +38 -6
  397. package/src/components/openapi/code-samples.ts +97 -0
  398. package/src/components/openapi/helpers.ts +19 -9
  399. package/src/components/openapi/message-composer.ts +6 -1
  400. package/src/components/openapi/message.ts +17 -2
  401. package/src/components/openapi/operation-model.ts +81 -11
  402. package/src/components/openapi/panel.ts +4 -2
  403. package/src/components/openapi/param-style.ts +181 -0
  404. package/src/components/openapi/playground-client.ts +73 -16
  405. package/src/components/openapi/request.ts +190 -26
  406. package/src/components/openapi/schema-tree.ts +30 -24
  407. package/src/components/openapi/security.ts +6 -3
  408. package/src/components/openapi/snippets.ts +953 -13
  409. package/src/components/openapi/ws-client.ts +18 -2
  410. package/src/components/props.ts +6 -0
  411. package/src/consent/client.ts +95 -0
  412. package/src/consent/clients.ts +19 -0
  413. package/src/consent/ethyca.ts +71 -0
  414. package/src/consent/head.ts +24 -0
  415. package/src/consent/index.ts +37 -0
  416. package/src/consent/init.ts +29 -0
  417. package/src/consent/native.ts +36 -0
  418. package/src/consent/osano.ts +59 -0
  419. package/src/consent/schema.ts +30 -0
  420. package/src/core/base-path.ts +62 -16
  421. package/src/core/builtin-tags.ts +5 -0
  422. package/src/core/client-address.ts +19 -0
  423. package/src/core/config-input.ts +203 -27
  424. package/src/core/data.ts +44 -4
  425. package/src/core/diagnostics.ts +5 -2
  426. package/src/core/directive-diagnostics.ts +99 -0
  427. package/src/core/footer.ts +26 -0
  428. package/src/core/frontmatter.ts +21 -18
  429. package/src/core/graph.ts +39 -36
  430. package/src/core/heading-markers.ts +5 -18
  431. package/src/core/i18n-ui.ts +82 -2
  432. package/src/core/i18n.ts +31 -3
  433. package/src/core/includes.ts +20 -3
  434. package/src/core/last-modified.ts +25 -3
  435. package/src/core/links.ts +16 -2
  436. package/src/core/locale-links.ts +5 -1
  437. package/src/core/localizable.ts +26 -0
  438. package/src/core/meta.ts +33 -19
  439. package/src/core/navigation.ts +71 -20
  440. package/src/core/page-modes.ts +56 -0
  441. package/src/core/project-graph.ts +138 -10
  442. package/src/core/redirect-patterns.ts +491 -0
  443. package/src/core/related.ts +131 -0
  444. package/src/core/schema.ts +342 -68
  445. package/src/core/sources/contentful-rich-text.ts +27 -20
  446. package/src/core/sources/filesystem.ts +20 -2
  447. package/src/core/sources/github-releases.ts +25 -6
  448. package/src/core/sources/lexical.ts +23 -18
  449. package/src/core/sources/lower.ts +201 -34
  450. package/src/core/sources/mdx-remote.ts +51 -16
  451. package/src/core/sources/normalize.ts +327 -91
  452. package/src/core/sources/notion.ts +52 -25
  453. package/src/core/sources/obsidian.ts +23 -6
  454. package/src/core/sources/portable-text.ts +38 -21
  455. package/src/core/sources/read.ts +8 -3
  456. package/src/core/sources/strapi-blocks.ts +20 -16
  457. package/src/core/sources/watch.ts +20 -7
  458. package/src/core/standard-schema.ts +10 -6
  459. package/src/core/types.ts +1 -1
  460. package/src/core/ui-packs/ar.ts +43 -0
  461. package/src/core/ui-packs/bg.ts +45 -0
  462. package/src/core/ui-packs/bn.ts +43 -0
  463. package/src/core/ui-packs/ca.ts +46 -0
  464. package/src/core/ui-packs/cs.ts +44 -0
  465. package/src/core/ui-packs/da.ts +45 -0
  466. package/src/core/ui-packs/de.ts +46 -0
  467. package/src/core/ui-packs/el.ts +45 -0
  468. package/src/core/ui-packs/es.ts +46 -0
  469. package/src/core/ui-packs/fa.ts +45 -0
  470. package/src/core/ui-packs/fi.ts +45 -0
  471. package/src/core/ui-packs/fr.ts +46 -0
  472. package/src/core/ui-packs/he.ts +43 -0
  473. package/src/core/ui-packs/hi.ts +44 -0
  474. package/src/core/ui-packs/hr.ts +45 -0
  475. package/src/core/ui-packs/hu.ts +44 -0
  476. package/src/core/ui-packs/id.ts +45 -0
  477. package/src/core/ui-packs/it.ts +45 -0
  478. package/src/core/ui-packs/ja.ts +45 -0
  479. package/src/core/ui-packs/ko.ts +44 -0
  480. package/src/core/ui-packs/nl.ts +46 -0
  481. package/src/core/ui-packs/no.ts +44 -0
  482. package/src/core/ui-packs/pl.ts +45 -0
  483. package/src/core/ui-packs/pt-br.ts +46 -0
  484. package/src/core/ui-packs/pt.ts +45 -0
  485. package/src/core/ui-packs/ro.ts +45 -0
  486. package/src/core/ui-packs/ru.ts +45 -0
  487. package/src/core/ui-packs/sk.ts +44 -0
  488. package/src/core/ui-packs/sr.ts +45 -0
  489. package/src/core/ui-packs/sv.ts +44 -0
  490. package/src/core/ui-packs/th.ts +43 -0
  491. package/src/core/ui-packs/tr.ts +45 -0
  492. package/src/core/ui-packs/uk.ts +45 -0
  493. package/src/core/ui-packs/vi.ts +45 -0
  494. package/src/core/ui-packs/zh-tw.ts +43 -0
  495. package/src/core/ui-packs/zh.ts +43 -0
  496. package/src/core/variables.ts +103 -0
  497. package/src/core/version-cut.ts +52 -15
  498. package/src/deploy/artifacts.ts +104 -14
  499. package/src/deploy/cloudflare-negotiation.ts +39 -17
  500. package/src/deploy/headers.ts +10 -5
  501. package/src/deploy/node-headers.ts +64 -10
  502. package/src/deploy/platforms/cloudflare.ts +44 -3
  503. package/src/deploy/platforms/netlify.ts +34 -1
  504. package/src/deploy/platforms/node.ts +1 -0
  505. package/src/deploy/platforms/static.ts +1 -0
  506. package/src/deploy/platforms/types.ts +8 -0
  507. package/src/deploy/platforms/vercel.ts +38 -2
  508. package/src/deploy/redirects.ts +139 -36
  509. package/src/deploy/robots.ts +2 -2
  510. package/src/deploy/rss.ts +4 -3
  511. package/src/deploy/sitemap.ts +7 -5
  512. package/src/deploy/vercel-negotiation.ts +36 -0
  513. package/src/markdown/api-rail.ts +259 -0
  514. package/src/markdown/base-links.ts +34 -36
  515. package/src/markdown/code-title.ts +35 -9
  516. package/src/markdown/directives.ts +242 -36
  517. package/src/markdown/features.ts +20 -2
  518. package/src/markdown/fence-meta.ts +2 -0
  519. package/src/markdown/include.ts +11 -1
  520. package/src/markdown/index.ts +35 -11
  521. package/src/markdown/math.ts +4 -4
  522. package/src/markdown/mdast.ts +5 -2
  523. package/src/markdown/relative-links.ts +12 -4
  524. package/src/markdown/variables.ts +56 -0
  525. package/src/markdown/views.ts +82 -0
  526. package/src/narration/build.ts +412 -0
  527. package/src/narration/player.ts +249 -0
  528. package/src/narration/provider.ts +29 -0
  529. package/src/narration/script.ts +592 -0
  530. package/src/og/card.ts +136 -11
  531. package/src/og/derive.ts +41 -31
  532. package/src/og/index.ts +1 -0
  533. package/src/openapi/asyncapi.ts +4 -2
  534. package/src/openapi/model.ts +77 -36
  535. package/src/openapi/overlay.ts +256 -0
  536. package/src/openapi/parse.ts +55 -2
  537. package/src/openapi/proxy.ts +63 -10
  538. package/src/openapi/references.ts +21 -5
  539. package/src/openapi/render-mdx.ts +46 -3
  540. package/src/openapi/scalar.ts +40 -6
  541. package/src/openapi/source.ts +1 -1
  542. package/src/ratelimit/cloudflare.ts +54 -0
  543. package/src/ratelimit/index.ts +30 -0
  544. package/src/ratelimit/memory.ts +45 -0
  545. package/src/ratelimit/runtime.ts +229 -0
  546. package/src/ratelimit/schema.ts +28 -0
  547. package/src/ratelimit/upstash.ts +30 -0
  548. package/src/ratelimit/wrangler.ts +70 -0
  549. package/src/reference/asyncapi.ts +2 -2
  550. package/src/reference/graphql.ts +5 -2
  551. package/src/reference/openapi.ts +21 -7
  552. package/src/reference/options.ts +51 -7
  553. package/src/reference/scalar.ts +33 -8
  554. package/src/registry/eject.ts +201 -28
  555. package/src/registry/registry.ts +7 -0
  556. package/src/search/adapters/version-scope.ts +30 -0
  557. package/src/search/documents.ts +93 -28
  558. package/src/search/orama-index.ts +28 -3
  559. package/src/search/pagefind-ranking.ts +39 -0
  560. package/src/search/popular.ts +2 -1
  561. package/src/search/sync/algolia.ts +15 -4
  562. package/src/search/sync/orama-cloud.ts +3 -0
  563. package/src/search/sync/typesense.ts +5 -0
  564. package/src/seo/jsonld.ts +7 -3
  565. package/src/seo/metatags.ts +67 -0
  566. package/src/theme/chrome-icons.ts +1 -0
  567. package/src/theme/color.ts +500 -0
  568. package/src/theme/entry.ts +56 -3
  569. package/src/theme/palette.ts +121 -45
  570. package/src/translate/meta.ts +68 -24
  571. package/src/translate/prompts.ts +1 -0
  572. package/src/translate/run.ts +3 -3
  573. package/src/translate/validate.ts +12 -3
  574. package/src/translate/work-list.ts +56 -17
  575. package/src/upgrade/upgrade.ts +4 -171
  576. package/dist/cli/chunk-3yce002v.js.map +0 -14
  577. package/dist/cli/chunk-5r8g91qn.js.map +0 -34
  578. package/dist/cli/chunk-6dtt0zfn.js.map +0 -10
  579. package/dist/cli/chunk-7mbqtmgb.js.map +0 -10
  580. package/dist/cli/chunk-ddndchfr.js.map +0 -14
  581. package/dist/cli/chunk-esh98wmb.js +0 -23
  582. package/dist/cli/chunk-esh98wmb.js.map +0 -10
  583. package/dist/cli/chunk-hqp2ajnh.js.map +0 -35
  584. package/dist/cli/chunk-hr8ne106.js.map +0 -13
  585. package/dist/cli/chunk-j85scx15.js.map +0 -10
  586. package/dist/cli/chunk-mqc662a6.js +0 -75
  587. package/dist/cli/chunk-mqc662a6.js.map +0 -11
  588. package/dist/cli/chunk-pbg5a4s3.js.map +0 -19
  589. package/dist/cli/chunk-qkb5a8sa.js.map +0 -10
  590. package/dist/cli/chunk-v6ya5kcb.js.map +0 -189
  591. package/dist/cli/chunk-wdrt2k2v.js.map +0 -10
  592. package/dist/cli/chunk-xh43dwgw.js.map +0 -36
  593. package/docs/content/sources.mdx +0 -338
  594. /package/dist/cli/{chunk-zp79m0ts.js.map → chunk-1n228xqb.js.map} +0 -0
  595. /package/dist/cli/{chunk-h2ez8dzb.js.map → chunk-5ffywsmb.js.map} +0 -0
  596. /package/dist/cli/{chunk-g698a744.js.map → chunk-5wj2qwn2.js.map} +0 -0
  597. /package/dist/cli/{chunk-sqw4ekg1.js.map → chunk-amrb6hgh.js.map} +0 -0
  598. /package/dist/cli/{chunk-h7k3nq3v.js.map → chunk-gp84za0h.js.map} +0 -0
  599. /package/dist/cli/{chunk-bfwp9vp6.js.map → chunk-p804f3pe.js.map} +0 -0
  600. /package/dist/cli/{chunk-00gs3wqs.js.map → chunk-ta27feev.js.map} +0 -0
  601. /package/dist/cli/{chunk-bbnwccaz.js.map → chunk-y4ykbark.js.map} +0 -0
@@ -11,9 +11,18 @@ export interface RequestSample {
11
11
  method: string;
12
12
  url: string;
13
13
  headers: Record<string, string>;
14
- /** JSON-stringified request body, when the operation takes one. */
14
+ /**
15
+ * The request body as sent, when the operation takes one: JSON text,
16
+ * form-urlencoded pairs, or a raw media type's text.
17
+ */
15
18
  body?: string;
16
19
  bodyValue?: unknown;
20
+ /**
21
+ * A `multipart/form-data` body's parts as `[name, value]` pairs, set
22
+ * instead of `body`. The request carries no Content-Type of its own for
23
+ * them: the client writes one with the parts' boundary.
24
+ */
25
+ formData?: [string, string][];
17
26
  }
18
27
 
19
28
  const headerLines = (
@@ -40,7 +49,11 @@ const stringLiteral = (value: string): string => JSON.stringify(value);
40
49
 
41
50
  const curlSnippet = (sample: RequestSample): string => {
42
51
  const lines = [
43
- `curl -X ${sample.method} ${shellQuote(sample.url)}`,
52
+ // `-X HEAD` sends a HEAD but still waits for the body the response
53
+ // announces, so the command hangs; `--head` knows there is none.
54
+ sample.method === "HEAD"
55
+ ? `curl --head ${shellQuote(sample.url)}`
56
+ : `curl -X ${sample.method} ${shellQuote(sample.url)}`,
44
57
  ...headerLines(
45
58
  sample.headers,
46
59
  (key, value) => ` -H ${shellQuote(`${key}: ${value}`)}`
@@ -49,10 +62,37 @@ const curlSnippet = (sample: RequestSample): string => {
49
62
  if (sample.body) {
50
63
  lines.push(` -d ${shellQuote(sample.body)}`);
51
64
  }
65
+ // `--form-string`, not `-F`: `-F` reads a value starting with `@` or `<` as
66
+ // a file to upload.
67
+ for (const [name, value] of sample.formData ?? []) {
68
+ lines.push(` --form-string ${shellQuote(`${name}=${value}`)}`);
69
+ }
52
70
  return lines.join(" \\\n");
53
71
  };
54
72
 
55
- const fetchSnippet = (sample: RequestSample): string => {
73
+ /**
74
+ * Whether fetch refuses to send `method` (an upper-case HTTP method). TRACE is
75
+ * one of the Fetch standard's forbidden methods, and the only one an OpenAPI
76
+ * spec can declare: browsers and Node's fetch alike throw a TypeError before
77
+ * any request goes out.
78
+ */
79
+ export const fetchRefusesMethod = (method: string): boolean =>
80
+ method === "TRACE";
81
+
82
+ /**
83
+ * The JavaScript sample for a method fetch refuses: a note in place of a call
84
+ * that could only throw.
85
+ */
86
+ const FETCH_REFUSED_NOTE = [
87
+ "// fetch() refuses the TRACE method: browsers and Node both throw before",
88
+ "// sending, so there's no fetch sample for this operation. Send it with",
89
+ "// curl or another HTTP client instead.",
90
+ ].join("\n");
91
+
92
+ const fetchCall = (sample: RequestSample, declaration: string): string => {
93
+ if (fetchRefusesMethod(sample.method)) {
94
+ return FETCH_REFUSED_NOTE;
95
+ }
56
96
  const options = [` method: ${stringLiteral(sample.method)}`];
57
97
  if (Object.keys(sample.headers).length > 0) {
58
98
  const headers = headerLines(
@@ -71,11 +111,41 @@ const fetchSnippet = (sample: RequestSample): string => {
71
111
  // text that isn't JSON yet.
72
112
  options.push(` body: ${stringLiteral(sample.body)}`);
73
113
  }
74
- return `const response = await fetch(${stringLiteral(sample.url)}, {\n${options.join(
114
+ // A FormData body: fetch writes the multipart Content-Type and boundary.
115
+ let form = "";
116
+ if (sample.formData) {
117
+ form = [
118
+ "const form = new FormData();\n",
119
+ ...sample.formData.map(
120
+ ([name, value]) =>
121
+ `form.append(${stringLiteral(name)}, ${stringLiteral(value)});\n`
122
+ ),
123
+ "\n",
124
+ ].join("");
125
+ options.push(" body: form");
126
+ }
127
+ return `${form}const ${declaration} = await fetch(${stringLiteral(sample.url)}, {\n${options.join(
75
128
  ",\n"
76
129
  )}\n});`;
77
130
  };
78
131
 
132
+ const fetchSnippet = (sample: RequestSample): string =>
133
+ fetchCall(sample, "response");
134
+
135
+ const typescriptSnippet = (sample: RequestSample): string =>
136
+ fetchCall(sample, "response: Response");
137
+
138
+ /** The methods `requests` has a module-level helper for. */
139
+ const PYTHON_METHODS = new Set([
140
+ "delete",
141
+ "get",
142
+ "head",
143
+ "options",
144
+ "patch",
145
+ "post",
146
+ "put",
147
+ ]);
148
+
79
149
  const pythonSnippet = (sample: RequestSample): string => {
80
150
  const args = [` ${stringLiteral(sample.url)}`];
81
151
  if (Object.keys(sample.headers).length > 0) {
@@ -91,9 +161,809 @@ const pythonSnippet = (sample: RequestSample): string => {
91
161
  // `1e400` as `Infinity` and would otherwise diverge from the live send.
92
162
  args.push(` data=${stringLiteral(sample.body)}`);
93
163
  }
94
- return `import requests\n\nresponse = requests.${sample.method.toLowerCase()}(\n${args.join(
95
- ",\n"
96
- )},\n)`;
164
+ if (sample.formData) {
165
+ // `files=` is what makes requests encode multipart; a `(None, value)`
166
+ // tuple is a plain field rather than an upload.
167
+ const parts = sample.formData.map(
168
+ ([name, value]) =>
169
+ ` (${stringLiteral(name)}, (None, ${stringLiteral(value)})),\n`
170
+ );
171
+ args.push(` files=[\n${parts.join("")} ]`);
172
+ }
173
+ // requests has a helper per common method; anything else (TRACE) goes
174
+ // through `requests.request` with the method named.
175
+ const method = sample.method.toLowerCase();
176
+ let call = `requests.${method}`;
177
+ if (!PYTHON_METHODS.has(method)) {
178
+ call = "requests.request";
179
+ args.unshift(` ${stringLiteral(sample.method)}`);
180
+ }
181
+ return `import requests\n\nresponse = ${call}(\n${args.join(",\n")},\n)`;
182
+ };
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");
97
967
  };
98
968
 
99
969
  /** A code-sample language: config id -> label, Shiki lang, and builder. */
@@ -106,27 +976,97 @@ export interface SampleLanguage {
106
976
 
107
977
  const LANGUAGES: SampleLanguage[] = [
108
978
  { build: curlSnippet, id: "curl", label: "cURL", lang: "bash" },
109
- { build: fetchSnippet, id: "js", label: "JavaScript", lang: "js" },
110
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" },
111
1006
  ];
112
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. */
113
1012
  const ALIASES = new Map([
1013
+ [".net", "dotnet"],
114
1014
  ["bash", "curl"],
1015
+ ["c#", "csharp"],
1016
+ ["c++", "cpp"],
1017
+ ["cs", "csharp"],
1018
+ ["dot-net", "dotnet"],
1019
+ ["flutter", "dart"],
1020
+ ["golang", "go"],
115
1021
  ["javascript", "js"],
116
- ["node", "js"],
1022
+ ["kt", "kotlin"],
1023
+ ["node.js", "node"],
1024
+ ["nodejs", "node"],
1025
+ ["ps1", "powershell"],
117
1026
  ["py", "python"],
1027
+ ["rb", "ruby"],
1028
+ ["rs", "rust"],
1029
+ ["sh", "curl"],
118
1030
  ["shell", "curl"],
119
- ["typescript", "js"],
1031
+ ["ts", "typescript"],
120
1032
  ]);
121
1033
 
122
- /** The sample languages to render, resolved from config ids (unknown ids dropped). */
123
- 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
+ }
124
1064
  const wanted = ids.length > 0 ? ids : ["curl", "js", "python"];
125
1065
  const byId = new Map(LANGUAGES.map((entry) => [entry.id, entry]));
126
1066
  const out: SampleLanguage[] = [];
127
1067
  const seen = new Set<string>();
128
1068
  for (const raw of wanted) {
129
- const id = ALIASES.get(raw.toLowerCase()) ?? raw.toLowerCase();
1069
+ const id = sampleLanguageId(raw);
130
1070
  const language = byId.get(id);
131
1071
  if (language && !seen.has(id)) {
132
1072
  seen.add(id);