blume 2.0.3 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (607) hide show
  1. package/AGENTS.md +1 -0
  2. package/CHANGELOG.md +142 -0
  3. package/README.md +1 -1
  4. package/dist/cli/{chunk-5f86nr5m.js → chunk-073rdvgb.js} +21 -48
  5. package/dist/cli/{chunk-5f86nr5m.js.map → chunk-073rdvgb.js.map} +4 -5
  6. package/dist/cli/{chunk-ga0pf4aj.js → chunk-0n9xkp3d.js} +12 -3
  7. package/dist/cli/{chunk-ga0pf4aj.js.map → chunk-0n9xkp3d.js.map} +3 -3
  8. package/dist/cli/chunk-150jk7km.js +52 -0
  9. package/dist/cli/chunk-150jk7km.js.map +10 -0
  10. package/dist/cli/{chunk-9t7a85s3.js → chunk-313vnn40.js} +30 -19
  11. package/dist/cli/chunk-313vnn40.js.map +10 -0
  12. package/dist/cli/{chunk-tkacnehg.js → chunk-3v3ffbrj.js} +2 -2
  13. package/dist/cli/{chunk-q58y5e6a.js → chunk-3x6x3pph.js} +46 -24
  14. package/dist/cli/chunk-3x6x3pph.js.map +10 -0
  15. package/dist/cli/{chunk-1d7ve1dm.js → chunk-42t4jxdx.js} +37 -5
  16. package/dist/cli/chunk-42t4jxdx.js.map +12 -0
  17. package/dist/cli/{chunk-8ktnccpt.js → chunk-54ztw1nj.js} +21 -2
  18. package/dist/cli/chunk-54ztw1nj.js.map +10 -0
  19. package/dist/cli/{chunk-hm3vjy5s.js → chunk-5vjtgs6e.js} +28 -10
  20. package/dist/cli/{chunk-hm3vjy5s.js.map → chunk-5vjtgs6e.js.map} +4 -4
  21. package/dist/cli/chunk-66w9j7p6.js +322 -0
  22. package/dist/cli/chunk-66w9j7p6.js.map +14 -0
  23. package/dist/cli/{chunk-2hsdwb9n.js → chunk-7ppf1rva.js} +34 -18
  24. package/dist/cli/chunk-7ppf1rva.js.map +10 -0
  25. package/dist/cli/{chunk-r9rcc4w7.js → chunk-8y2kmjv8.js} +7 -7
  26. package/dist/cli/{chunk-r20tn01b.js → chunk-c1y538kd.js} +8 -2
  27. package/dist/cli/{chunk-r20tn01b.js.map → chunk-c1y538kd.js.map} +3 -3
  28. package/dist/cli/{chunk-2eytanqx.js → chunk-c887fj0s.js} +2 -2
  29. package/dist/cli/{chunk-p3v96n38.js → chunk-cfyg47zg.js} +16 -6
  30. package/dist/cli/chunk-cfyg47zg.js.map +10 -0
  31. package/dist/cli/chunk-cm89scdv.js +443 -0
  32. package/dist/cli/chunk-cm89scdv.js.map +11 -0
  33. package/dist/cli/{chunk-akbpwfxc.js → chunk-dzwehpy7.js} +2 -2
  34. package/dist/cli/{chunk-wrr3j9w9.js → chunk-e6atmn4h.js} +299 -72
  35. package/dist/cli/chunk-e6atmn4h.js.map +36 -0
  36. package/dist/cli/{chunk-tzmab476.js → chunk-e8xw9qkh.js} +8 -6
  37. package/dist/cli/{chunk-tzmab476.js.map → chunk-e8xw9qkh.js.map} +3 -3
  38. package/dist/cli/{chunk-6dsbexzp.js → chunk-e9q7a2ak.js} +17 -15
  39. package/dist/cli/{chunk-6dsbexzp.js.map → chunk-e9q7a2ak.js.map} +2 -2
  40. package/dist/cli/{chunk-pv29h0wf.js → chunk-ehweg4fr.js} +5155 -1249
  41. package/dist/cli/chunk-ehweg4fr.js.map +218 -0
  42. package/dist/cli/{chunk-c8chx29p.js → chunk-ep6nd0rn.js} +390 -318
  43. package/dist/cli/chunk-ep6nd0rn.js.map +36 -0
  44. package/dist/cli/{chunk-p73c0m7w.js → chunk-fdrkhpjy.js} +18 -15
  45. package/dist/cli/{chunk-p73c0m7w.js.map → chunk-fdrkhpjy.js.map} +3 -3
  46. package/dist/cli/{chunk-2z928egk.js → chunk-ft9vfys6.js} +5 -5
  47. package/dist/cli/{chunk-5m5nmvyq.js → chunk-gb420vvz.js} +316 -79
  48. package/dist/cli/chunk-gb420vvz.js.map +17 -0
  49. package/dist/cli/{chunk-vg9r4eb9.js → chunk-hebn0tpx.js} +86 -13
  50. package/dist/cli/chunk-hebn0tpx.js.map +14 -0
  51. package/dist/cli/{chunk-35d4wj9f.js → chunk-htrwgabr.js} +41 -23
  52. package/dist/cli/chunk-htrwgabr.js.map +15 -0
  53. package/dist/cli/{chunk-g4hq16wv.js → chunk-hx3xqnk9.js} +16 -14
  54. package/dist/cli/{chunk-g4hq16wv.js.map → chunk-hx3xqnk9.js.map} +2 -2
  55. package/dist/cli/chunk-j134vrp2.js +65 -0
  56. package/dist/cli/chunk-j134vrp2.js.map +10 -0
  57. package/dist/cli/{chunk-a58773jm.js → chunk-kma9f6sf.js} +2 -2
  58. package/dist/cli/chunk-kwrg63tm.js +31 -0
  59. package/dist/cli/chunk-kwrg63tm.js.map +10 -0
  60. package/dist/cli/{chunk-ey84smr6.js → chunk-mrcccgnh.js} +6 -6
  61. package/dist/cli/{chunk-ey84smr6.js.map → chunk-mrcccgnh.js.map} +1 -1
  62. package/dist/cli/{chunk-yfyb25rh.js → chunk-n0v7m8ry.js} +24 -8
  63. package/dist/cli/chunk-n0v7m8ry.js.map +14 -0
  64. package/dist/cli/{chunk-j85vccga.js → chunk-ncj7w596.js} +576 -367
  65. package/dist/cli/chunk-ncj7w596.js.map +35 -0
  66. package/dist/cli/{chunk-5xvm6tfj.js → chunk-nq1d20w2.js} +17 -15
  67. package/dist/cli/{chunk-5xvm6tfj.js.map → chunk-nq1d20w2.js.map} +2 -2
  68. package/dist/cli/{chunk-pehfxfta.js → chunk-nrns10mj.js} +6 -98
  69. package/dist/cli/chunk-nrns10mj.js.map +10 -0
  70. package/dist/cli/{chunk-crgn1q09.js → chunk-sds923fa.js} +4 -4
  71. package/dist/cli/chunk-t2m5j4pr.js +104 -0
  72. package/dist/cli/chunk-t2m5j4pr.js.map +11 -0
  73. package/dist/cli/{chunk-acanzt5p.js → chunk-tdqhyxfz.js} +6 -6
  74. package/dist/cli/chunk-tphac74f.js +126 -0
  75. package/dist/cli/chunk-tphac74f.js.map +11 -0
  76. package/dist/cli/chunk-w1r4vase.js +18 -0
  77. package/dist/cli/chunk-w1r4vase.js.map +10 -0
  78. package/dist/cli/{chunk-3em5wd2y.js → chunk-zccbjcj0.js} +4 -3
  79. package/dist/cli/{chunk-3em5wd2y.js.map → chunk-zccbjcj0.js.map} +2 -2
  80. package/dist/cli/index.js +31 -20
  81. package/dist/cli/index.js.map +5 -5
  82. package/dist/types/ai/agent-surface.d.ts +8 -7
  83. package/dist/types/ai/ai-catalog.d.ts +7 -0
  84. package/dist/types/ai/ask-context.d.ts +26 -0
  85. package/dist/types/ai/ask.d.ts +271 -58
  86. package/dist/types/ai/component-markdown.d.ts +2 -0
  87. package/dist/types/ai/index.d.ts +2 -2
  88. package/dist/types/ai/openapi-components.d.ts +4 -1
  89. package/dist/types/ai/site-skill.d.ts +26 -0
  90. package/dist/types/ai/skills.d.ts +16 -0
  91. package/dist/types/astro/integration.d.ts +9 -0
  92. package/dist/types/astro/markdown-negotiation.d.ts +3 -0
  93. package/dist/types/astro/pages.d.ts +10 -0
  94. package/dist/types/captcha/hcaptcha.d.ts +27 -0
  95. package/dist/types/captcha/index.d.ts +28 -0
  96. package/dist/types/captcha/schema.d.ts +29 -0
  97. package/dist/types/captcha/turnstile.d.ts +28 -0
  98. package/dist/types/cli/init/scaffold.d.ts +1 -1
  99. package/dist/types/components/content/api-field.d.ts +62 -0
  100. package/dist/types/components/content/api-page.d.ts +100 -0
  101. package/dist/types/components/layout/nav-utils.d.ts +26 -0
  102. package/dist/types/components/openapi/code-samples.d.ts +29 -0
  103. package/dist/types/components/openapi/helpers.d.ts +158 -0
  104. package/dist/types/components/openapi/security.d.ts +95 -0
  105. package/dist/types/components/openapi/snippets.d.ts +60 -0
  106. package/dist/types/consent/ethyca.d.ts +48 -0
  107. package/dist/types/consent/index.d.ts +37 -0
  108. package/dist/types/consent/native.d.ts +28 -0
  109. package/dist/types/consent/osano.d.ts +42 -0
  110. package/dist/types/consent/schema.d.ts +110 -0
  111. package/dist/types/core/changelog-index.d.ts +8 -0
  112. package/dist/types/core/config-input.d.ts +217 -23
  113. package/dist/types/core/data.d.ts +53 -4
  114. package/dist/types/core/footer.d.ts +8 -0
  115. package/dist/types/core/i18n-ui.d.ts +88 -0
  116. package/dist/types/core/includes.d.ts +8 -2
  117. package/dist/types/core/links.d.ts +8 -0
  118. package/dist/types/core/localizable.d.ts +7 -0
  119. package/dist/types/core/page-modes.d.ts +29 -0
  120. package/dist/types/core/probe.d.ts +2 -0
  121. package/dist/types/core/project-graph.d.ts +6 -0
  122. package/dist/types/core/redirect-patterns.d.ts +152 -0
  123. package/dist/types/core/related.d.ts +40 -0
  124. package/dist/types/core/schema.d.ts +670 -56
  125. package/dist/types/core/sources/cache.d.ts +3 -0
  126. package/dist/types/core/sources/contentful-rich-text.d.ts +11 -2
  127. package/dist/types/core/sources/contentful.d.ts +17 -1
  128. package/dist/types/core/sources/lexical.d.ts +5 -0
  129. package/dist/types/core/sources/normalize.d.ts +20 -1
  130. package/dist/types/core/sources/read.d.ts +5 -0
  131. package/dist/types/core/sources/remote.d.ts +37 -10
  132. package/dist/types/core/sources/sanity.d.ts +1 -1
  133. package/dist/types/core/types.d.ts +18 -1
  134. package/dist/types/core/variables.d.ts +42 -0
  135. package/dist/types/deploy/adapter-output.d.ts +2 -1
  136. package/dist/types/deploy/adapters/cloudflare.d.ts +7 -5
  137. package/dist/types/deploy/adapters/node.d.ts +58 -23
  138. package/dist/types/deploy/adapters/registry.d.ts +10 -0
  139. package/dist/types/deploy/adapters/types.d.ts +3 -2
  140. package/dist/types/deploy/node-headers.d.ts +13 -2
  141. package/dist/types/deploy/platforms/cloudflare.d.ts +15 -7
  142. package/dist/types/deploy/platforms/netlify.d.ts +7 -0
  143. package/dist/types/deploy/platforms/node.d.ts +3 -1
  144. package/dist/types/deploy/platforms/types.d.ts +18 -7
  145. package/dist/types/deploy/platforms/vercel.d.ts +26 -1
  146. package/dist/types/deploy/redirects.d.ts +55 -11
  147. package/dist/types/deploy/sitemap.d.ts +2 -1
  148. package/dist/types/deploy/vercel-negotiation.d.ts +51 -21
  149. package/dist/types/markdown/code-title.d.ts +17 -0
  150. package/dist/types/markdown/features.d.ts +3 -2
  151. package/dist/types/narration/build.d.ts +70 -0
  152. package/dist/types/narration/provider.d.ts +26 -0
  153. package/dist/types/narration/script.d.ts +154 -0
  154. package/dist/types/openapi/checks.d.ts +27 -0
  155. package/dist/types/openapi/graphql.d.ts +6 -0
  156. package/dist/types/openapi/model.d.ts +27 -10
  157. package/dist/types/openapi/overlay.d.ts +54 -0
  158. package/dist/types/openapi/parse.d.ts +16 -0
  159. package/dist/types/openapi/references.d.ts +30 -4
  160. package/dist/types/ratelimit/cloudflare.d.ts +39 -0
  161. package/dist/types/ratelimit/index.d.ts +27 -0
  162. package/dist/types/ratelimit/memory.d.ts +36 -0
  163. package/dist/types/ratelimit/schema.d.ts +119 -0
  164. package/dist/types/ratelimit/upstash.d.ts +46 -0
  165. package/dist/types/ratelimit/wrangler.d.ts +23 -0
  166. package/dist/types/reference/asyncapi.d.ts +10 -10
  167. package/dist/types/reference/graphql.d.ts +89 -11
  168. package/dist/types/reference/index.d.ts +1 -1
  169. package/dist/types/reference/openapi.d.ts +33 -12
  170. package/dist/types/reference/options.d.ts +82 -6
  171. package/dist/types/reference/scalar.d.ts +13 -0
  172. package/dist/types/reference/schema.d.ts +144 -36
  173. package/dist/types/search/adapters/registry.d.ts +3 -0
  174. package/dist/types/search/adapters/typesense.d.ts +14 -4
  175. package/dist/types/search/build.d.ts +3 -1
  176. package/dist/types/search/documents.d.ts +12 -1
  177. package/dist/types/search/orama-index.d.ts +13 -9
  178. package/dist/types/search/sync/algolia.d.ts +5 -2
  179. package/dist/types/search/sync/index.d.ts +3 -1
  180. package/dist/types/search/sync/typesense.d.ts +11 -7
  181. package/dist/types/seo/metatags.d.ts +20 -0
  182. package/dist/types/sources/contentful.d.ts +16 -0
  183. package/dist/types/sources/github-releases.d.ts +8 -1
  184. package/dist/types/sources/payload.d.ts +2 -0
  185. package/dist/types/sources/registry.d.ts +54 -0
  186. package/dist/types/sources/shared.d.ts +3 -0
  187. package/dist/types/sources/strapi.d.ts +2 -0
  188. package/docs/01-quickstart.mdx +4 -2
  189. package/docs/02-deployment.mdx +71 -17
  190. package/docs/03-upgrading.mdx +4 -4
  191. package/docs/04-migrating.mdx +1 -1
  192. package/docs/advanced/changelog.mdx +13 -2
  193. package/docs/advanced/skills.mdx +15 -1
  194. package/docs/cli/audit.mdx +51 -8
  195. package/docs/cli/doctor.mdx +5 -1
  196. package/docs/cli/evals.mdx +4 -4
  197. package/docs/cli/index.mdx +1 -0
  198. package/docs/cli/translate.mdx +1 -0
  199. package/docs/cli/validate.mdx +4 -3
  200. package/docs/configuration/analytics.mdx +3 -1
  201. package/docs/configuration/assistant.mdx +194 -38
  202. package/docs/configuration/consent.mdx +100 -0
  203. package/docs/configuration/customization.mdx +8 -6
  204. package/docs/configuration/export.mdx +5 -3
  205. package/docs/configuration/index.mdx +64 -5
  206. package/docs/configuration/meta.ts +3 -0
  207. package/docs/configuration/narration.mdx +119 -0
  208. package/docs/configuration/rate-limiting.mdx +90 -0
  209. package/docs/configuration/search.mdx +63 -12
  210. package/docs/configuration/theming.mdx +3 -1
  211. package/docs/content/components.mdx +108 -2
  212. package/docs/content/frontmatter.mdx +80 -2
  213. package/docs/content/i18n.mdx +39 -4
  214. package/docs/content/includes.mdx +16 -0
  215. package/docs/content/meta.mdx +1 -0
  216. package/docs/content/meta.ts +1 -0
  217. package/docs/content/navigation.mdx +32 -10
  218. package/docs/content/sources/contentful.mdx +35 -0
  219. package/docs/content/sources/custom.mdx +43 -0
  220. package/docs/content/sources/github-releases.mdx +39 -0
  221. package/docs/content/sources/index.mdx +91 -0
  222. package/docs/content/sources/meta.ts +16 -0
  223. package/docs/content/sources/notion.mdx +32 -0
  224. package/docs/content/sources/obsidian.mdx +37 -0
  225. package/docs/content/sources/payload.mdx +31 -0
  226. package/docs/content/sources/remote-mdx.mdx +44 -0
  227. package/docs/content/sources/sanity.mdx +29 -0
  228. package/docs/content/sources/strapi.mdx +32 -0
  229. package/docs/content/syntax.mdx +69 -4
  230. package/docs/content/variables.mdx +41 -0
  231. package/docs/discoverability/agent-discovery.mdx +29 -2
  232. package/docs/discoverability/index.mdx +2 -2
  233. package/docs/discoverability/json-api.mdx +2 -0
  234. package/docs/discoverability/llms-txt.mdx +2 -2
  235. package/docs/discoverability/markdown.mdx +3 -1
  236. package/docs/discoverability/mcp.mdx +4 -0
  237. package/docs/discoverability/metadata.mdx +17 -0
  238. package/docs/discoverability/sitemap-and-robots.mdx +1 -1
  239. package/docs/references/api-pages.mdx +74 -0
  240. package/docs/references/asyncapi.mdx +4 -2
  241. package/docs/references/graphql.mdx +14 -2
  242. package/docs/references/meta.ts +1 -1
  243. package/docs/references/openapi.mdx +148 -7
  244. package/docs/references/scalar.mdx +3 -1
  245. package/package.json +43 -8
  246. package/skills/blume/SKILL.md +3 -0
  247. package/skills/blume-migrate/SKILL.md +6 -6
  248. package/skills/blume-migrate/references/docusaurus.md +7 -7
  249. package/skills/blume-migrate/references/fumadocs.md +17 -13
  250. package/skills/blume-migrate/references/mintlify.md +37 -19
  251. package/skills/blume-migrate/references/nextra.md +10 -7
  252. package/skills/blume-migrate/references/starlight.md +11 -8
  253. package/skills/blume-migrate/scripts/mintlify-codemod.mjs +60 -14
  254. package/skills/blume-write-skill/SKILL.md +82 -0
  255. package/src/ai/agent-readability.ts +2 -1
  256. package/src/ai/agent-surface.ts +9 -7
  257. package/src/ai/ai-catalog.ts +6 -3
  258. package/src/ai/ask-context.ts +103 -20
  259. package/src/ai/ask-data.ts +6 -0
  260. package/src/ai/ask-limits.ts +13 -0
  261. package/src/ai/ask-tools.ts +142 -0
  262. package/src/ai/ask.ts +354 -76
  263. package/src/ai/changelog-markdown.ts +11 -6
  264. package/src/ai/component-markdown.ts +76 -1
  265. package/src/ai/cors.ts +14 -3
  266. package/src/ai/index.ts +12 -0
  267. package/src/ai/llms.ts +6 -2
  268. package/src/ai/markdown.ts +2 -1
  269. package/src/ai/mcp/data.ts +6 -0
  270. package/src/ai/mcp/query.ts +14 -6
  271. package/src/ai/openapi-components.ts +36 -6
  272. package/src/ai/relative-links.ts +4 -38
  273. package/src/ai/site-skill.ts +274 -0
  274. package/src/ai/skills.ts +35 -2
  275. package/src/ai/write-skill.ts +97 -0
  276. package/src/astro/generate.ts +210 -66
  277. package/src/astro/index.ts +1 -0
  278. package/src/astro/integration.ts +42 -4
  279. package/src/astro/markdown-negotiation.ts +3 -0
  280. package/src/astro/module-types.ts +9 -1
  281. package/src/astro/pages.ts +23 -0
  282. package/src/astro/templates.ts +406 -108
  283. package/src/astro/variables.ts +31 -0
  284. package/src/audit/catalog.ts +38 -3
  285. package/src/audit/checks/contrast.ts +183 -0
  286. package/src/audit/checks/i18n.ts +2 -3
  287. package/src/audit/checks/indexability.ts +2 -3
  288. package/src/audit/checks/links.ts +19 -13
  289. package/src/audit/checks/network.ts +110 -6
  290. package/src/audit/checks/redirects.ts +6 -4
  291. package/src/audit/checks/robots.ts +58 -2
  292. package/src/audit/checks/sitemap.ts +2 -3
  293. package/src/audit/redirects.ts +46 -15
  294. package/src/audit/run.ts +5 -0
  295. package/src/audit/types.ts +4 -1
  296. package/src/audit/url.ts +14 -5
  297. package/src/blume-modules.d.ts +12 -1
  298. package/src/captcha/client.ts +152 -0
  299. package/src/captcha/hcaptcha.ts +36 -0
  300. package/src/captcha/index.ts +28 -0
  301. package/src/captcha/schema.ts +32 -0
  302. package/src/captcha/turnstile.ts +38 -0
  303. package/src/captcha/verify.ts +71 -0
  304. package/src/cli/args.ts +51 -0
  305. package/src/cli/build-failure.ts +23 -0
  306. package/src/cli/command-meta.ts +5 -0
  307. package/src/cli/commands/audit.ts +10 -1
  308. package/src/cli/commands/dev.ts +18 -2
  309. package/src/cli/commands/doctor.ts +67 -28
  310. package/src/cli/commands/eval.ts +36 -4
  311. package/src/cli/commands/init.ts +6 -4
  312. package/src/cli/commands/skill.ts +96 -0
  313. package/src/cli/commands/validate.ts +10 -1
  314. package/src/cli/index.ts +5 -0
  315. package/src/cli/init/scaffold.ts +3 -3
  316. package/src/cli/prepare.ts +6 -3
  317. package/src/cli/required-secrets.ts +72 -30
  318. package/src/components/BlumePage.astro +2 -0
  319. package/src/components/content/ApiEndpoint.astro +18 -0
  320. package/src/components/content/ApiField.astro +79 -0
  321. package/src/components/content/ApiPlayground.astro +66 -0
  322. package/src/components/content/ApiRail.astro +19 -0
  323. package/src/components/content/Color.astro +1 -0
  324. package/src/components/content/FileTree.astro +1 -0
  325. package/src/components/content/GithubInfo.astro +1 -0
  326. package/src/components/content/Panel.astro +1 -0
  327. package/src/components/content/ParamField.astro +25 -0
  328. package/src/components/content/RequestExample.astro +17 -0
  329. package/src/components/content/ResponseExample.astro +17 -0
  330. package/src/components/content/ResponseField.astro +32 -0
  331. package/src/components/content/Step.astro +1 -0
  332. package/src/components/content/Tile.astro +1 -0
  333. package/src/components/content/Tooltip.astro +1 -0
  334. package/src/components/content/Tree.astro +1 -0
  335. package/src/components/content/TypeTable.astro +4 -1
  336. package/src/components/content/View.astro +21 -0
  337. package/src/components/content/ViewSwitcher.astro +168 -0
  338. package/src/components/content/api-field.ts +103 -0
  339. package/src/components/content/api-page.ts +248 -0
  340. package/src/components/content/mermaid-element.ts +15 -1
  341. package/src/components/content/view-switcher.ts +71 -0
  342. package/src/components/index.ts +2 -0
  343. package/src/components/islands/Assistant.astro +10 -1
  344. package/src/components/islands/assistant.tsx +104 -14
  345. package/src/components/islands/code-question.ts +62 -0
  346. package/src/components/islands/hooks.ts +136 -21
  347. package/src/components/islands/support-link.ts +68 -0
  348. package/src/components/layout/Analytics.astro +11 -2
  349. package/src/components/layout/Banner.astro +14 -10
  350. package/src/components/layout/Breadcrumbs.astro +6 -2
  351. package/src/components/layout/ConsentBanner.astro +62 -0
  352. package/src/components/layout/ConsentHead.astro +38 -0
  353. package/src/components/layout/Directory.astro +94 -0
  354. package/src/components/layout/DrawerSwitchers.astro +51 -0
  355. package/src/components/layout/Empty.astro +2 -2
  356. package/src/components/layout/Header.astro +122 -80
  357. package/src/components/layout/LanguageSwitcher.astro +48 -15
  358. package/src/components/layout/NarrationPlayer.astro +1085 -0
  359. package/src/components/layout/NavSelector.astro +33 -7
  360. package/src/components/layout/PageActions.astro +139 -24
  361. package/src/components/layout/PageFeedback.astro +54 -49
  362. package/src/components/layout/PageLayout.astro +71 -3
  363. package/src/components/layout/ReferenceLayout.astro +17 -6
  364. package/src/components/layout/RelatedPages.astro +42 -0
  365. package/src/components/layout/RootLayout.astro +318 -47
  366. package/src/components/layout/Search.astro +47 -19
  367. package/src/components/layout/SiteFooter.astro +124 -0
  368. package/src/components/layout/SiteMetatags.astro +16 -0
  369. package/src/components/layout/TableOfContents.astro +24 -17
  370. package/src/components/layout/consent/native.ts +79 -0
  371. package/src/components/layout/consent/types.ts +37 -0
  372. package/src/components/layout/footer-links.ts +53 -0
  373. package/src/components/layout/footer-socials.ts +87 -0
  374. package/src/components/layout/head-scripts.ts +6 -1
  375. package/src/components/layout/locale-redirect.ts +107 -0
  376. package/src/components/layout/nav-utils.ts +83 -0
  377. package/src/components/layout/page-feedback.ts +96 -0
  378. package/src/components/layout/search/algolia.ts +10 -1
  379. package/src/components/layout/search/analytics.ts +126 -0
  380. package/src/components/layout/search/endpoint.ts +48 -5
  381. package/src/components/layout/search/flexsearch.ts +17 -5
  382. package/src/components/layout/search/orama-cloud.ts +26 -14
  383. package/src/components/layout/search/pagefind.ts +114 -5
  384. package/src/components/layout/search/types.ts +32 -0
  385. package/src/components/layout/search/typesense.ts +19 -3
  386. package/src/components/openapi/AsyncApiOperation.astro +14 -7
  387. package/src/components/openapi/Authorization.astro +10 -0
  388. package/src/components/openapi/GraphqlOperation.astro +5 -0
  389. package/src/components/openapi/Operation.astro +184 -13
  390. package/src/components/openapi/ParametersTable.astro +8 -4
  391. package/src/components/openapi/Playground.astro +7 -1
  392. package/src/components/openapi/RequestBody.astro +24 -5
  393. package/src/components/openapi/RequestPanel.astro +59 -7
  394. package/src/components/openapi/Responses.astro +7 -4
  395. package/src/components/openapi/SchemaTable.astro +15 -3
  396. package/src/components/openapi/async-snippets.ts +65 -9
  397. package/src/components/openapi/async.ts +123 -29
  398. package/src/components/openapi/code-samples.ts +97 -0
  399. package/src/components/openapi/graphql-helpers.ts +35 -12
  400. package/src/components/openapi/helpers.ts +36 -2
  401. package/src/components/openapi/message-model.ts +54 -11
  402. package/src/components/openapi/message.ts +14 -4
  403. package/src/components/openapi/operation-model.ts +16 -7
  404. package/src/components/openapi/playground-client.ts +24 -5
  405. package/src/components/openapi/playground-schema.ts +1 -1
  406. package/src/components/openapi/schema-tree.ts +44 -14
  407. package/src/components/openapi/security.ts +16 -3
  408. package/src/components/openapi/snippets.ts +873 -10
  409. package/src/components/openapi/validate-json.ts +4 -3
  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/builtin-tags.ts +5 -0
  421. package/src/core/changelog-index.ts +31 -1
  422. package/src/core/client-address.ts +19 -0
  423. package/src/core/component-diagnostics.ts +53 -0
  424. package/src/core/component-overrides.ts +32 -0
  425. package/src/core/config-input.ts +225 -23
  426. package/src/core/data.ts +46 -4
  427. package/src/core/diagnostics.ts +8 -2
  428. package/src/core/footer.ts +26 -0
  429. package/src/core/graph.ts +39 -36
  430. package/src/core/i18n-ui.ts +84 -2
  431. package/src/core/includes.ts +21 -4
  432. package/src/core/links.ts +63 -19
  433. package/src/core/localizable.ts +26 -0
  434. package/src/core/navigation.ts +43 -12
  435. package/src/core/page-modes.ts +56 -0
  436. package/src/core/probe.ts +26 -6
  437. package/src/core/project-graph.ts +163 -16
  438. package/src/core/redirect-patterns.ts +520 -0
  439. package/src/core/related.ts +131 -0
  440. package/src/core/schema.ts +339 -66
  441. package/src/core/sources/cache.ts +6 -0
  442. package/src/core/sources/contentful-rich-text.ts +29 -6
  443. package/src/core/sources/contentful.ts +135 -25
  444. package/src/core/sources/filesystem.ts +26 -4
  445. package/src/core/sources/github-releases.ts +12 -1
  446. package/src/core/sources/lexical.ts +15 -10
  447. package/src/core/sources/mdx-remote.ts +62 -10
  448. package/src/core/sources/normalize.ts +228 -41
  449. package/src/core/sources/notion.ts +7 -1
  450. package/src/core/sources/obsidian.ts +16 -3
  451. package/src/core/sources/payload.ts +38 -15
  452. package/src/core/sources/read.ts +8 -3
  453. package/src/core/sources/remote.ts +72 -15
  454. package/src/core/sources/resolve.ts +11 -1
  455. package/src/core/sources/sanity.ts +43 -25
  456. package/src/core/sources/strapi.ts +2 -2
  457. package/src/core/types.ts +19 -1
  458. package/src/core/ui-packs/ar.ts +44 -0
  459. package/src/core/ui-packs/bg.ts +46 -0
  460. package/src/core/ui-packs/bn.ts +44 -0
  461. package/src/core/ui-packs/ca.ts +47 -0
  462. package/src/core/ui-packs/cs.ts +45 -0
  463. package/src/core/ui-packs/da.ts +46 -0
  464. package/src/core/ui-packs/de.ts +47 -0
  465. package/src/core/ui-packs/el.ts +46 -0
  466. package/src/core/ui-packs/es.ts +47 -0
  467. package/src/core/ui-packs/fa.ts +46 -0
  468. package/src/core/ui-packs/fi.ts +46 -0
  469. package/src/core/ui-packs/fr.ts +47 -0
  470. package/src/core/ui-packs/he.ts +44 -0
  471. package/src/core/ui-packs/hi.ts +45 -0
  472. package/src/core/ui-packs/hr.ts +46 -0
  473. package/src/core/ui-packs/hu.ts +45 -0
  474. package/src/core/ui-packs/id.ts +46 -0
  475. package/src/core/ui-packs/it.ts +46 -0
  476. package/src/core/ui-packs/ja.ts +46 -0
  477. package/src/core/ui-packs/ko.ts +45 -0
  478. package/src/core/ui-packs/nl.ts +47 -0
  479. package/src/core/ui-packs/no.ts +45 -0
  480. package/src/core/ui-packs/pl.ts +46 -0
  481. package/src/core/ui-packs/pt-br.ts +47 -0
  482. package/src/core/ui-packs/pt.ts +46 -0
  483. package/src/core/ui-packs/ro.ts +46 -0
  484. package/src/core/ui-packs/ru.ts +46 -0
  485. package/src/core/ui-packs/sk.ts +45 -0
  486. package/src/core/ui-packs/sr.ts +46 -0
  487. package/src/core/ui-packs/sv.ts +45 -0
  488. package/src/core/ui-packs/th.ts +44 -0
  489. package/src/core/ui-packs/tr.ts +46 -0
  490. package/src/core/ui-packs/uk.ts +46 -0
  491. package/src/core/ui-packs/vi.ts +46 -0
  492. package/src/core/ui-packs/zh-tw.ts +44 -0
  493. package/src/core/ui-packs/zh.ts +44 -0
  494. package/src/core/variables.ts +103 -0
  495. package/src/deploy/adapter-output.ts +7 -2
  496. package/src/deploy/adapters/cloudflare.ts +7 -5
  497. package/src/deploy/adapters/node.ts +47 -9
  498. package/src/deploy/adapters/types.ts +7 -3
  499. package/src/deploy/artifacts.ts +79 -9
  500. package/src/deploy/cloudflare-negotiation.ts +35 -5
  501. package/src/deploy/headers.ts +2 -1
  502. package/src/deploy/node-headers.ts +63 -9
  503. package/src/deploy/platforms/cloudflare.ts +45 -9
  504. package/src/deploy/platforms/netlify.ts +35 -2
  505. package/src/deploy/platforms/node.ts +12 -2
  506. package/src/deploy/platforms/types.ts +21 -7
  507. package/src/deploy/platforms/vercel.ts +149 -17
  508. package/src/deploy/redirects.ts +151 -24
  509. package/src/deploy/rss.ts +3 -1
  510. package/src/deploy/sitemap.ts +39 -5
  511. package/src/deploy/vercel-negotiation.ts +326 -102
  512. package/src/eval/agents.ts +18 -1
  513. package/src/eval/prompts.ts +6 -3
  514. package/src/eval/report.ts +34 -13
  515. package/src/eval/run.ts +5 -0
  516. package/src/markdown/api-rail.ts +259 -0
  517. package/src/markdown/code-title.ts +35 -9
  518. package/src/markdown/features.ts +3 -2
  519. package/src/markdown/fence-meta.ts +2 -0
  520. package/src/markdown/include.ts +11 -1
  521. package/src/markdown/index.ts +28 -5
  522. package/src/markdown/math.ts +4 -4
  523. package/src/markdown/relative-links.ts +3 -2
  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 +7 -6
  531. package/src/openapi/checks.ts +273 -0
  532. package/src/openapi/graphql-build.ts +21 -0
  533. package/src/openapi/graphql.ts +11 -0
  534. package/src/openapi/model.ts +72 -27
  535. package/src/openapi/overlay.ts +256 -0
  536. package/src/openapi/parse.ts +182 -4
  537. package/src/openapi/references.ts +64 -8
  538. package/src/openapi/render-mdx.ts +5 -1
  539. package/src/openapi/scalar.ts +40 -6
  540. package/src/openapi/source.ts +40 -15
  541. package/src/ratelimit/cloudflare.ts +54 -0
  542. package/src/ratelimit/index.ts +30 -0
  543. package/src/ratelimit/memory.ts +45 -0
  544. package/src/ratelimit/runtime.ts +247 -0
  545. package/src/ratelimit/schema.ts +28 -0
  546. package/src/ratelimit/upstash.ts +60 -0
  547. package/src/ratelimit/wrangler.ts +70 -0
  548. package/src/reference/asyncapi.ts +2 -2
  549. package/src/reference/graphql.ts +20 -3
  550. package/src/reference/index.ts +1 -0
  551. package/src/reference/openapi.ts +21 -7
  552. package/src/reference/options.ts +80 -8
  553. package/src/reference/scalar.ts +33 -8
  554. package/src/registry/eject.ts +23 -5
  555. package/src/registry/registry.ts +7 -0
  556. package/src/search/adapters/locale-scope.ts +30 -0
  557. package/src/search/adapters/typesense.ts +14 -4
  558. package/src/search/build.ts +21 -3
  559. package/src/search/documents.ts +26 -2
  560. package/src/search/orama-index.ts +191 -34
  561. package/src/search/pagefind-ranking.ts +39 -0
  562. package/src/search/source-pages.ts +29 -0
  563. package/src/search/sync/algolia.ts +111 -10
  564. package/src/search/sync/index.ts +52 -13
  565. package/src/search/sync/orama-cloud.ts +3 -0
  566. package/src/search/sync/typesense.ts +76 -26
  567. package/src/seo/metatags.ts +67 -0
  568. package/src/sources/contentful.ts +12 -0
  569. package/src/sources/github-releases.ts +7 -1
  570. package/src/sources/shared.ts +3 -0
  571. package/src/theme/chrome-icons.ts +1 -0
  572. package/src/theme/color.ts +500 -0
  573. package/src/theme/entry.ts +85 -6
  574. package/src/theme/palette.ts +121 -45
  575. package/src/translate/prompts.ts +1 -0
  576. package/src/translate/validate.ts +31 -3
  577. package/src/upgrade/upgrade.ts +4 -171
  578. package/dist/cli/chunk-1d7ve1dm.js.map +0 -12
  579. package/dist/cli/chunk-2hsdwb9n.js.map +0 -10
  580. package/dist/cli/chunk-35d4wj9f.js.map +0 -15
  581. package/dist/cli/chunk-364znk6q.js +0 -75
  582. package/dist/cli/chunk-364znk6q.js.map +0 -11
  583. package/dist/cli/chunk-5m5nmvyq.js.map +0 -13
  584. package/dist/cli/chunk-8ktnccpt.js.map +0 -10
  585. package/dist/cli/chunk-9t7a85s3.js.map +0 -10
  586. package/dist/cli/chunk-b07cmahc.js +0 -145
  587. package/dist/cli/chunk-b07cmahc.js.map +0 -11
  588. package/dist/cli/chunk-c8chx29p.js.map +0 -36
  589. package/dist/cli/chunk-e04dxsz1.js +0 -39
  590. package/dist/cli/chunk-e04dxsz1.js.map +0 -10
  591. package/dist/cli/chunk-j85vccga.js.map +0 -36
  592. package/dist/cli/chunk-p3v96n38.js.map +0 -10
  593. package/dist/cli/chunk-pehfxfta.js.map +0 -10
  594. package/dist/cli/chunk-pv29h0wf.js.map +0 -190
  595. package/dist/cli/chunk-q58y5e6a.js.map +0 -10
  596. package/dist/cli/chunk-vg9r4eb9.js.map +0 -14
  597. package/dist/cli/chunk-wrr3j9w9.js.map +0 -35
  598. package/dist/cli/chunk-yfyb25rh.js.map +0 -14
  599. package/docs/content/sources.mdx +0 -334
  600. /package/dist/cli/{chunk-tkacnehg.js.map → chunk-3v3ffbrj.js.map} +0 -0
  601. /package/dist/cli/{chunk-r9rcc4w7.js.map → chunk-8y2kmjv8.js.map} +0 -0
  602. /package/dist/cli/{chunk-2eytanqx.js.map → chunk-c887fj0s.js.map} +0 -0
  603. /package/dist/cli/{chunk-akbpwfxc.js.map → chunk-dzwehpy7.js.map} +0 -0
  604. /package/dist/cli/{chunk-2z928egk.js.map → chunk-ft9vfys6.js.map} +0 -0
  605. /package/dist/cli/{chunk-a58773jm.js.map → chunk-kma9f6sf.js.map} +0 -0
  606. /package/dist/cli/{chunk-crgn1q09.js.map → chunk-sds923fa.js.map} +0 -0
  607. /package/dist/cli/{chunk-acanzt5p.js.map → chunk-tdqhyxfz.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,99 @@ 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[] => {
203
- const wanted = ids.length > 0 ? ids : ["curl", "js", "python"];
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
+ /** `openapi()`'s default `codeSamples`, which hand-written endpoint pages always use. */
1057
+ export const DEFAULT_SAMPLE_LANGUAGES = ["curl", "js", "python"];
1058
+
1059
+ /**
1060
+ * The sample languages to render, resolved from config ids (unknown ids
1061
+ * dropped), in order. `false` and an empty list both render none.
1062
+ */
1063
+ export const sampleLanguages = (ids: string[] | false): SampleLanguage[] => {
1064
+ if (ids === false) {
1065
+ return [];
1066
+ }
204
1067
  const byId = new Map(LANGUAGES.map((entry) => [entry.id, entry]));
205
1068
  const out: SampleLanguage[] = [];
206
1069
  const seen = new Set<string>();
207
- for (const raw of wanted) {
208
- const id = ALIASES.get(raw.toLowerCase()) ?? raw.toLowerCase();
1070
+ for (const raw of ids) {
1071
+ const id = sampleLanguageId(raw);
209
1072
  const language = byId.get(id);
210
1073
  if (language && !seen.has(id)) {
211
1074
  seen.add(id);