blume 2.0.3 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (455) hide show
  1. package/AGENTS.md +1 -0
  2. package/CHANGELOG.md +48 -0
  3. package/README.md +1 -1
  4. package/dist/cli/{chunk-1d7ve1dm.js → chunk-0gz36akr.js} +18 -2
  5. package/dist/cli/{chunk-1d7ve1dm.js.map → chunk-0gz36akr.js.map} +3 -3
  6. package/dist/cli/{chunk-2eytanqx.js → chunk-1n228xqb.js} +2 -2
  7. package/dist/cli/{chunk-yfyb25rh.js → chunk-2f3ndkpw.js} +24 -8
  8. package/dist/cli/chunk-2f3ndkpw.js.map +14 -0
  9. package/dist/cli/{chunk-r20tn01b.js → chunk-2f7n4qzt.js} +8 -2
  10. package/dist/cli/{chunk-r20tn01b.js.map → chunk-2f7n4qzt.js.map} +3 -3
  11. package/dist/cli/{chunk-35d4wj9f.js → chunk-2n4ps1b3.js} +7 -7
  12. package/dist/cli/{chunk-j85vccga.js → chunk-43zcf4gz.js} +97 -57
  13. package/dist/cli/{chunk-j85vccga.js.map → chunk-43zcf4gz.js.map} +6 -6
  14. package/dist/cli/{chunk-e04dxsz1.js → chunk-4ejxbzee.js} +1 -1
  15. package/dist/cli/{chunk-5m5nmvyq.js → chunk-4h8z9vv5.js} +233 -39
  16. package/dist/cli/chunk-4h8z9vv5.js.map +16 -0
  17. package/dist/cli/{chunk-5f86nr5m.js → chunk-5cg2rzgf.js} +18 -16
  18. package/dist/cli/{chunk-5f86nr5m.js.map → chunk-5cg2rzgf.js.map} +2 -2
  19. package/dist/cli/{chunk-tzmab476.js → chunk-5ffywsmb.js} +4 -4
  20. package/dist/cli/{chunk-2z928egk.js → chunk-5wj2qwn2.js} +5 -5
  21. package/dist/cli/chunk-6bm0xpgd.js +18 -0
  22. package/dist/cli/chunk-6bm0xpgd.js.map +10 -0
  23. package/dist/cli/{chunk-vg9r4eb9.js → chunk-713mngdq.js} +12 -5
  24. package/dist/cli/{chunk-vg9r4eb9.js.map → chunk-713mngdq.js.map} +3 -3
  25. package/dist/cli/{chunk-5xvm6tfj.js → chunk-9geymqns.js} +17 -15
  26. package/dist/cli/{chunk-5xvm6tfj.js.map → chunk-9geymqns.js.map} +2 -2
  27. package/dist/cli/{chunk-q58y5e6a.js → chunk-9xr4dgee.js} +26 -17
  28. package/dist/cli/chunk-9xr4dgee.js.map +10 -0
  29. package/dist/cli/{chunk-b07cmahc.js → chunk-amrb6hgh.js} +2 -2
  30. package/dist/cli/chunk-cm89scdv.js +443 -0
  31. package/dist/cli/chunk-cm89scdv.js.map +11 -0
  32. package/dist/cli/{chunk-akbpwfxc.js → chunk-cmtcnh9f.js} +2 -2
  33. package/dist/cli/chunk-cy5bm0j0.js +104 -0
  34. package/dist/cli/chunk-cy5bm0j0.js.map +11 -0
  35. package/dist/cli/{chunk-crgn1q09.js → chunk-e7vvk5ya.js} +4 -4
  36. package/dist/cli/{chunk-pehfxfta.js → chunk-g2z9x01v.js} +6 -98
  37. package/dist/cli/chunk-g2z9x01v.js.map +10 -0
  38. package/dist/cli/{chunk-tkacnehg.js → chunk-gp84za0h.js} +2 -2
  39. package/dist/cli/{chunk-wrr3j9w9.js → chunk-h2a48yhd.js} +186 -63
  40. package/dist/cli/chunk-h2a48yhd.js.map +36 -0
  41. package/dist/cli/{chunk-hm3vjy5s.js → chunk-jnwj5kjj.js} +14 -9
  42. package/dist/cli/{chunk-hm3vjy5s.js.map → chunk-jnwj5kjj.js.map} +4 -4
  43. package/dist/cli/chunk-kwrg63tm.js +31 -0
  44. package/dist/cli/chunk-kwrg63tm.js.map +10 -0
  45. package/dist/cli/{chunk-2hsdwb9n.js → chunk-nd1821sk.js} +18 -16
  46. package/dist/cli/{chunk-2hsdwb9n.js.map → chunk-nd1821sk.js.map} +2 -2
  47. package/dist/cli/{chunk-6dsbexzp.js → chunk-ntv4qrt7.js} +17 -15
  48. package/dist/cli/{chunk-6dsbexzp.js.map → chunk-ntv4qrt7.js.map} +2 -2
  49. package/dist/cli/{chunk-r9rcc4w7.js → chunk-p804f3pe.js} +7 -7
  50. package/dist/cli/{chunk-ey84smr6.js → chunk-py62fewh.js} +6 -6
  51. package/dist/cli/{chunk-ey84smr6.js.map → chunk-py62fewh.js.map} +1 -1
  52. package/dist/cli/chunk-q3wxxq39.js +126 -0
  53. package/dist/cli/chunk-q3wxxq39.js.map +11 -0
  54. package/dist/cli/{chunk-9t7a85s3.js → chunk-rekqz4qg.js} +23 -13
  55. package/dist/cli/chunk-rekqz4qg.js.map +10 -0
  56. package/dist/cli/{chunk-8ktnccpt.js → chunk-ta27feev.js} +1 -1
  57. package/dist/cli/{chunk-pv29h0wf.js → chunk-xkz5xbtv.js} +3476 -822
  58. package/dist/cli/chunk-xkz5xbtv.js.map +213 -0
  59. package/dist/cli/{chunk-a58773jm.js → chunk-y4ykbark.js} +2 -2
  60. package/dist/cli/{chunk-p3v96n38.js → chunk-y5h4r2y0.js} +4 -4
  61. package/dist/cli/{chunk-acanzt5p.js → chunk-yj17vs39.js} +6 -6
  62. package/dist/cli/{chunk-p73c0m7w.js → chunk-yxmk4pec.js} +16 -14
  63. package/dist/cli/{chunk-p73c0m7w.js.map → chunk-yxmk4pec.js.map} +2 -2
  64. package/dist/cli/{chunk-3em5wd2y.js → chunk-zccbjcj0.js} +4 -3
  65. package/dist/cli/{chunk-3em5wd2y.js.map → chunk-zccbjcj0.js.map} +2 -2
  66. package/dist/cli/{chunk-c8chx29p.js → chunk-zf748avv.js} +384 -207
  67. package/dist/cli/chunk-zf748avv.js.map +37 -0
  68. package/dist/cli/{chunk-g4hq16wv.js → chunk-zhvd6adm.js} +16 -14
  69. package/dist/cli/{chunk-g4hq16wv.js.map → chunk-zhvd6adm.js.map} +2 -2
  70. package/dist/cli/index.js +26 -20
  71. package/dist/cli/index.js.map +5 -5
  72. package/dist/types/ai/agent-surface.d.ts +8 -7
  73. package/dist/types/ai/ai-catalog.d.ts +7 -0
  74. package/dist/types/ai/ask-context.d.ts +22 -0
  75. package/dist/types/ai/ask.d.ts +271 -58
  76. package/dist/types/ai/index.d.ts +2 -2
  77. package/dist/types/ai/site-skill.d.ts +26 -0
  78. package/dist/types/ai/skills.d.ts +16 -0
  79. package/dist/types/astro/integration.d.ts +8 -0
  80. package/dist/types/captcha/hcaptcha.d.ts +27 -0
  81. package/dist/types/captcha/index.d.ts +28 -0
  82. package/dist/types/captcha/schema.d.ts +29 -0
  83. package/dist/types/captcha/turnstile.d.ts +28 -0
  84. package/dist/types/components/content/api-field.d.ts +59 -0
  85. package/dist/types/components/content/api-page.d.ts +95 -0
  86. package/dist/types/components/layout/nav-utils.d.ts +26 -0
  87. package/dist/types/components/openapi/helpers.d.ts +137 -0
  88. package/dist/types/components/openapi/security.d.ts +85 -0
  89. package/dist/types/consent/ethyca.d.ts +48 -0
  90. package/dist/types/consent/index.d.ts +37 -0
  91. package/dist/types/consent/native.d.ts +28 -0
  92. package/dist/types/consent/osano.d.ts +42 -0
  93. package/dist/types/consent/schema.d.ts +110 -0
  94. package/dist/types/core/config-input.d.ts +187 -20
  95. package/dist/types/core/data.d.ts +49 -4
  96. package/dist/types/core/footer.d.ts +8 -0
  97. package/dist/types/core/i18n-ui.d.ts +86 -0
  98. package/dist/types/core/includes.d.ts +8 -2
  99. package/dist/types/core/localizable.d.ts +7 -0
  100. package/dist/types/core/page-modes.d.ts +29 -0
  101. package/dist/types/core/redirect-patterns.d.ts +144 -0
  102. package/dist/types/core/related.d.ts +40 -0
  103. package/dist/types/core/schema.d.ts +551 -56
  104. package/dist/types/core/sources/read.d.ts +5 -0
  105. package/dist/types/core/types.d.ts +1 -1
  106. package/dist/types/core/variables.d.ts +42 -0
  107. package/dist/types/deploy/node-headers.d.ts +13 -2
  108. package/dist/types/deploy/platforms/cloudflare.d.ts +5 -0
  109. package/dist/types/deploy/platforms/netlify.d.ts +7 -0
  110. package/dist/types/deploy/platforms/vercel.d.ts +6 -0
  111. package/dist/types/deploy/redirects.d.ts +39 -11
  112. package/dist/types/deploy/vercel-negotiation.d.ts +9 -0
  113. package/dist/types/markdown/code-title.d.ts +17 -0
  114. package/dist/types/markdown/features.d.ts +3 -2
  115. package/dist/types/narration/build.d.ts +70 -0
  116. package/dist/types/narration/provider.d.ts +26 -0
  117. package/dist/types/narration/script.d.ts +154 -0
  118. package/dist/types/openapi/model.d.ts +12 -6
  119. package/dist/types/openapi/overlay.d.ts +54 -0
  120. package/dist/types/openapi/parse.d.ts +13 -0
  121. package/dist/types/openapi/references.d.ts +14 -4
  122. package/dist/types/ratelimit/cloudflare.d.ts +39 -0
  123. package/dist/types/ratelimit/index.d.ts +27 -0
  124. package/dist/types/ratelimit/memory.d.ts +36 -0
  125. package/dist/types/ratelimit/schema.d.ts +111 -0
  126. package/dist/types/ratelimit/upstash.d.ts +21 -0
  127. package/dist/types/ratelimit/wrangler.d.ts +23 -0
  128. package/dist/types/reference/asyncapi.d.ts +10 -10
  129. package/dist/types/reference/graphql.d.ts +13 -10
  130. package/dist/types/reference/openapi.d.ts +33 -12
  131. package/dist/types/reference/options.d.ts +45 -5
  132. package/dist/types/reference/scalar.d.ts +13 -0
  133. package/dist/types/reference/schema.d.ts +60 -36
  134. package/dist/types/search/documents.d.ts +12 -1
  135. package/dist/types/search/orama-index.d.ts +4 -0
  136. package/dist/types/seo/metatags.d.ts +20 -0
  137. package/docs/02-deployment.mdx +41 -5
  138. package/docs/03-upgrading.mdx +4 -4
  139. package/docs/04-migrating.mdx +1 -1
  140. package/docs/advanced/changelog.mdx +2 -2
  141. package/docs/advanced/skills.mdx +14 -0
  142. package/docs/cli/audit.mdx +21 -1
  143. package/docs/cli/index.mdx +1 -0
  144. package/docs/configuration/analytics.mdx +3 -1
  145. package/docs/configuration/assistant.mdx +188 -36
  146. package/docs/configuration/consent.mdx +100 -0
  147. package/docs/configuration/customization.mdx +4 -4
  148. package/docs/configuration/index.mdx +63 -5
  149. package/docs/configuration/meta.ts +3 -0
  150. package/docs/configuration/narration.mdx +119 -0
  151. package/docs/configuration/rate-limiting.mdx +69 -0
  152. package/docs/configuration/search.mdx +29 -1
  153. package/docs/configuration/theming.mdx +3 -1
  154. package/docs/content/components.mdx +106 -0
  155. package/docs/content/frontmatter.mdx +76 -0
  156. package/docs/content/i18n.mdx +38 -3
  157. package/docs/content/includes.mdx +16 -0
  158. package/docs/content/meta.mdx +1 -0
  159. package/docs/content/meta.ts +1 -0
  160. package/docs/content/navigation.mdx +28 -6
  161. package/docs/content/sources/contentful.mdx +32 -0
  162. package/docs/content/sources/custom.mdx +39 -0
  163. package/docs/content/sources/github-releases.mdx +31 -0
  164. package/docs/content/sources/index.mdx +91 -0
  165. package/docs/content/sources/meta.ts +16 -0
  166. package/docs/content/sources/notion.mdx +32 -0
  167. package/docs/content/sources/obsidian.mdx +37 -0
  168. package/docs/content/sources/payload.mdx +30 -0
  169. package/docs/content/sources/remote-mdx.mdx +40 -0
  170. package/docs/content/sources/sanity.mdx +29 -0
  171. package/docs/content/sources/strapi.mdx +31 -0
  172. package/docs/content/syntax.mdx +67 -2
  173. package/docs/content/variables.mdx +41 -0
  174. package/docs/discoverability/agent-discovery.mdx +29 -2
  175. package/docs/discoverability/index.mdx +1 -1
  176. package/docs/discoverability/llms-txt.mdx +1 -1
  177. package/docs/discoverability/metadata.mdx +17 -0
  178. package/docs/references/api-pages.mdx +74 -0
  179. package/docs/references/asyncapi.mdx +1 -1
  180. package/docs/references/graphql.mdx +2 -2
  181. package/docs/references/meta.ts +1 -1
  182. package/docs/references/openapi.mdx +102 -3
  183. package/docs/references/scalar.mdx +3 -1
  184. package/package.json +36 -1
  185. package/skills/blume/SKILL.md +3 -0
  186. package/skills/blume-migrate/SKILL.md +5 -5
  187. package/skills/blume-migrate/references/docusaurus.md +3 -3
  188. package/skills/blume-migrate/references/mintlify.md +37 -19
  189. package/skills/blume-migrate/references/nextra.md +2 -2
  190. package/skills/blume-migrate/references/starlight.md +2 -1
  191. package/skills/blume-migrate/scripts/mintlify-codemod.mjs +60 -14
  192. package/skills/blume-write-skill/SKILL.md +82 -0
  193. package/src/ai/agent-readability.ts +2 -1
  194. package/src/ai/agent-surface.ts +9 -7
  195. package/src/ai/ai-catalog.ts +6 -3
  196. package/src/ai/ask-context.ts +73 -20
  197. package/src/ai/ask-data.ts +6 -0
  198. package/src/ai/ask-tools.ts +142 -0
  199. package/src/ai/ask.ts +354 -76
  200. package/src/ai/component-markdown.ts +75 -0
  201. package/src/ai/cors.ts +2 -0
  202. package/src/ai/index.ts +12 -0
  203. package/src/ai/llms.ts +6 -2
  204. package/src/ai/markdown.ts +2 -1
  205. package/src/ai/mcp/data.ts +6 -0
  206. package/src/ai/openapi-components.ts +9 -3
  207. package/src/ai/site-skill.ts +274 -0
  208. package/src/ai/skills.ts +35 -2
  209. package/src/ai/write-skill.ts +97 -0
  210. package/src/astro/generate.ts +132 -18
  211. package/src/astro/index.ts +1 -0
  212. package/src/astro/integration.ts +35 -0
  213. package/src/astro/module-types.ts +6 -0
  214. package/src/astro/templates.ts +283 -30
  215. package/src/astro/variables.ts +31 -0
  216. package/src/audit/catalog.ts +19 -0
  217. package/src/audit/checks/contrast.ts +183 -0
  218. package/src/audit/checks/i18n.ts +2 -3
  219. package/src/audit/checks/indexability.ts +2 -3
  220. package/src/audit/checks/links.ts +5 -12
  221. package/src/audit/checks/sitemap.ts +2 -3
  222. package/src/audit/redirects.ts +46 -15
  223. package/src/audit/run.ts +2 -0
  224. package/src/audit/types.ts +2 -1
  225. package/src/blume-modules.d.ts +7 -0
  226. package/src/captcha/client.ts +152 -0
  227. package/src/captcha/hcaptcha.ts +36 -0
  228. package/src/captcha/index.ts +28 -0
  229. package/src/captcha/schema.ts +32 -0
  230. package/src/captcha/turnstile.ts +38 -0
  231. package/src/captcha/verify.ts +71 -0
  232. package/src/cli/command-meta.ts +5 -0
  233. package/src/cli/commands/doctor.ts +22 -16
  234. package/src/cli/commands/skill.ts +96 -0
  235. package/src/cli/index.ts +5 -0
  236. package/src/cli/required-secrets.ts +71 -29
  237. package/src/components/BlumePage.astro +2 -0
  238. package/src/components/content/ApiEndpoint.astro +18 -0
  239. package/src/components/content/ApiField.astro +79 -0
  240. package/src/components/content/ApiPlayground.astro +62 -0
  241. package/src/components/content/ApiRail.astro +19 -0
  242. package/src/components/content/Color.astro +1 -0
  243. package/src/components/content/FileTree.astro +1 -0
  244. package/src/components/content/GithubInfo.astro +1 -0
  245. package/src/components/content/Panel.astro +1 -0
  246. package/src/components/content/ParamField.astro +25 -0
  247. package/src/components/content/RequestExample.astro +17 -0
  248. package/src/components/content/ResponseExample.astro +17 -0
  249. package/src/components/content/ResponseField.astro +32 -0
  250. package/src/components/content/Step.astro +1 -0
  251. package/src/components/content/Tile.astro +1 -0
  252. package/src/components/content/Tooltip.astro +1 -0
  253. package/src/components/content/Tree.astro +1 -0
  254. package/src/components/content/TypeTable.astro +4 -1
  255. package/src/components/content/View.astro +21 -0
  256. package/src/components/content/ViewSwitcher.astro +168 -0
  257. package/src/components/content/api-field.ts +100 -0
  258. package/src/components/content/api-page.ts +224 -0
  259. package/src/components/content/view-switcher.ts +71 -0
  260. package/src/components/index.ts +2 -0
  261. package/src/components/islands/Assistant.astro +10 -1
  262. package/src/components/islands/assistant.tsx +104 -14
  263. package/src/components/islands/code-question.ts +62 -0
  264. package/src/components/islands/hooks.ts +110 -21
  265. package/src/components/islands/support-link.ts +68 -0
  266. package/src/components/layout/Analytics.astro +11 -2
  267. package/src/components/layout/Banner.astro +14 -10
  268. package/src/components/layout/Breadcrumbs.astro +6 -2
  269. package/src/components/layout/ConsentBanner.astro +62 -0
  270. package/src/components/layout/ConsentHead.astro +38 -0
  271. package/src/components/layout/Directory.astro +94 -0
  272. package/src/components/layout/DrawerSwitchers.astro +51 -0
  273. package/src/components/layout/Empty.astro +2 -2
  274. package/src/components/layout/Header.astro +122 -80
  275. package/src/components/layout/LanguageSwitcher.astro +48 -15
  276. package/src/components/layout/NarrationPlayer.astro +1085 -0
  277. package/src/components/layout/NavSelector.astro +33 -7
  278. package/src/components/layout/PageFeedback.astro +54 -49
  279. package/src/components/layout/PageLayout.astro +71 -3
  280. package/src/components/layout/ReferenceLayout.astro +17 -6
  281. package/src/components/layout/RelatedPages.astro +42 -0
  282. package/src/components/layout/RootLayout.astro +308 -45
  283. package/src/components/layout/Search.astro +31 -17
  284. package/src/components/layout/SiteFooter.astro +124 -0
  285. package/src/components/layout/SiteMetatags.astro +16 -0
  286. package/src/components/layout/consent/native.ts +79 -0
  287. package/src/components/layout/consent/types.ts +37 -0
  288. package/src/components/layout/footer-links.ts +53 -0
  289. package/src/components/layout/footer-socials.ts +87 -0
  290. package/src/components/layout/locale-redirect.ts +107 -0
  291. package/src/components/layout/nav-utils.ts +83 -0
  292. package/src/components/layout/page-feedback.ts +96 -0
  293. package/src/components/layout/search/analytics.ts +98 -0
  294. package/src/components/layout/search/flexsearch.ts +17 -5
  295. package/src/components/layout/search/orama-cloud.ts +26 -14
  296. package/src/components/layout/search/types.ts +20 -0
  297. package/src/components/layout/search/typesense.ts +5 -1
  298. package/src/components/openapi/Operation.astro +161 -13
  299. package/src/components/openapi/RequestBody.astro +6 -4
  300. package/src/components/openapi/RequestPanel.astro +59 -7
  301. package/src/components/openapi/Responses.astro +6 -4
  302. package/src/components/openapi/async-snippets.ts +4 -4
  303. package/src/components/openapi/code-samples.ts +97 -0
  304. package/src/components/openapi/security.ts +6 -3
  305. package/src/components/openapi/snippets.ts +869 -8
  306. package/src/components/props.ts +6 -0
  307. package/src/consent/client.ts +95 -0
  308. package/src/consent/clients.ts +19 -0
  309. package/src/consent/ethyca.ts +71 -0
  310. package/src/consent/head.ts +24 -0
  311. package/src/consent/index.ts +37 -0
  312. package/src/consent/init.ts +29 -0
  313. package/src/consent/native.ts +36 -0
  314. package/src/consent/osano.ts +59 -0
  315. package/src/consent/schema.ts +30 -0
  316. package/src/core/builtin-tags.ts +5 -0
  317. package/src/core/client-address.ts +19 -0
  318. package/src/core/config-input.ts +194 -20
  319. package/src/core/data.ts +44 -4
  320. package/src/core/diagnostics.ts +3 -2
  321. package/src/core/footer.ts +26 -0
  322. package/src/core/graph.ts +39 -36
  323. package/src/core/i18n-ui.ts +82 -2
  324. package/src/core/includes.ts +20 -3
  325. package/src/core/links.ts +16 -2
  326. package/src/core/localizable.ts +26 -0
  327. package/src/core/navigation.ts +43 -12
  328. package/src/core/page-modes.ts +56 -0
  329. package/src/core/project-graph.ts +111 -5
  330. package/src/core/redirect-patterns.ts +491 -0
  331. package/src/core/related.ts +131 -0
  332. package/src/core/schema.ts +318 -64
  333. package/src/core/sources/normalize.ts +2 -1
  334. package/src/core/sources/read.ts +8 -3
  335. package/src/core/types.ts +1 -1
  336. package/src/core/ui-packs/ar.ts +43 -0
  337. package/src/core/ui-packs/bg.ts +45 -0
  338. package/src/core/ui-packs/bn.ts +43 -0
  339. package/src/core/ui-packs/ca.ts +46 -0
  340. package/src/core/ui-packs/cs.ts +44 -0
  341. package/src/core/ui-packs/da.ts +45 -0
  342. package/src/core/ui-packs/de.ts +46 -0
  343. package/src/core/ui-packs/el.ts +45 -0
  344. package/src/core/ui-packs/es.ts +46 -0
  345. package/src/core/ui-packs/fa.ts +45 -0
  346. package/src/core/ui-packs/fi.ts +45 -0
  347. package/src/core/ui-packs/fr.ts +46 -0
  348. package/src/core/ui-packs/he.ts +43 -0
  349. package/src/core/ui-packs/hi.ts +44 -0
  350. package/src/core/ui-packs/hr.ts +45 -0
  351. package/src/core/ui-packs/hu.ts +44 -0
  352. package/src/core/ui-packs/id.ts +45 -0
  353. package/src/core/ui-packs/it.ts +45 -0
  354. package/src/core/ui-packs/ja.ts +45 -0
  355. package/src/core/ui-packs/ko.ts +44 -0
  356. package/src/core/ui-packs/nl.ts +46 -0
  357. package/src/core/ui-packs/no.ts +44 -0
  358. package/src/core/ui-packs/pl.ts +45 -0
  359. package/src/core/ui-packs/pt-br.ts +46 -0
  360. package/src/core/ui-packs/pt.ts +45 -0
  361. package/src/core/ui-packs/ro.ts +45 -0
  362. package/src/core/ui-packs/ru.ts +45 -0
  363. package/src/core/ui-packs/sk.ts +44 -0
  364. package/src/core/ui-packs/sr.ts +45 -0
  365. package/src/core/ui-packs/sv.ts +44 -0
  366. package/src/core/ui-packs/th.ts +43 -0
  367. package/src/core/ui-packs/tr.ts +45 -0
  368. package/src/core/ui-packs/uk.ts +45 -0
  369. package/src/core/ui-packs/vi.ts +45 -0
  370. package/src/core/ui-packs/zh-tw.ts +43 -0
  371. package/src/core/ui-packs/zh.ts +43 -0
  372. package/src/core/variables.ts +103 -0
  373. package/src/deploy/artifacts.ts +77 -8
  374. package/src/deploy/cloudflare-negotiation.ts +35 -5
  375. package/src/deploy/headers.ts +2 -1
  376. package/src/deploy/node-headers.ts +63 -9
  377. package/src/deploy/platforms/cloudflare.ts +32 -0
  378. package/src/deploy/platforms/netlify.ts +33 -1
  379. package/src/deploy/platforms/vercel.ts +37 -2
  380. package/src/deploy/redirects.ts +91 -24
  381. package/src/deploy/vercel-negotiation.ts +36 -0
  382. package/src/markdown/api-rail.ts +259 -0
  383. package/src/markdown/code-title.ts +35 -9
  384. package/src/markdown/features.ts +3 -2
  385. package/src/markdown/fence-meta.ts +2 -0
  386. package/src/markdown/include.ts +11 -1
  387. package/src/markdown/index.ts +28 -5
  388. package/src/markdown/math.ts +4 -4
  389. package/src/markdown/variables.ts +56 -0
  390. package/src/markdown/views.ts +82 -0
  391. package/src/narration/build.ts +412 -0
  392. package/src/narration/player.ts +249 -0
  393. package/src/narration/provider.ts +29 -0
  394. package/src/narration/script.ts +592 -0
  395. package/src/og/card.ts +7 -6
  396. package/src/openapi/model.ts +57 -24
  397. package/src/openapi/overlay.ts +256 -0
  398. package/src/openapi/parse.ts +55 -2
  399. package/src/openapi/references.ts +21 -5
  400. package/src/openapi/render-mdx.ts +5 -1
  401. package/src/openapi/scalar.ts +40 -6
  402. package/src/openapi/source.ts +1 -1
  403. package/src/ratelimit/cloudflare.ts +54 -0
  404. package/src/ratelimit/index.ts +30 -0
  405. package/src/ratelimit/memory.ts +45 -0
  406. package/src/ratelimit/runtime.ts +229 -0
  407. package/src/ratelimit/schema.ts +28 -0
  408. package/src/ratelimit/upstash.ts +30 -0
  409. package/src/ratelimit/wrangler.ts +70 -0
  410. package/src/reference/asyncapi.ts +2 -2
  411. package/src/reference/graphql.ts +5 -2
  412. package/src/reference/openapi.ts +21 -7
  413. package/src/reference/options.ts +51 -7
  414. package/src/reference/scalar.ts +33 -8
  415. package/src/registry/eject.ts +20 -4
  416. package/src/registry/registry.ts +7 -0
  417. package/src/search/documents.ts +26 -2
  418. package/src/search/orama-index.ts +28 -3
  419. package/src/search/pagefind-ranking.ts +39 -0
  420. package/src/search/sync/algolia.ts +15 -4
  421. package/src/search/sync/orama-cloud.ts +3 -0
  422. package/src/search/sync/typesense.ts +5 -0
  423. package/src/seo/metatags.ts +67 -0
  424. package/src/theme/chrome-icons.ts +1 -0
  425. package/src/theme/color.ts +500 -0
  426. package/src/theme/entry.ts +56 -3
  427. package/src/theme/palette.ts +121 -45
  428. package/src/translate/prompts.ts +1 -0
  429. package/src/translate/validate.ts +8 -2
  430. package/src/upgrade/upgrade.ts +4 -171
  431. package/dist/cli/chunk-364znk6q.js +0 -75
  432. package/dist/cli/chunk-364znk6q.js.map +0 -11
  433. package/dist/cli/chunk-5m5nmvyq.js.map +0 -13
  434. package/dist/cli/chunk-9t7a85s3.js.map +0 -10
  435. package/dist/cli/chunk-c8chx29p.js.map +0 -36
  436. package/dist/cli/chunk-pehfxfta.js.map +0 -10
  437. package/dist/cli/chunk-pv29h0wf.js.map +0 -190
  438. package/dist/cli/chunk-q58y5e6a.js.map +0 -10
  439. package/dist/cli/chunk-wrr3j9w9.js.map +0 -35
  440. package/dist/cli/chunk-yfyb25rh.js.map +0 -14
  441. package/docs/content/sources.mdx +0 -334
  442. /package/dist/cli/{chunk-2eytanqx.js.map → chunk-1n228xqb.js.map} +0 -0
  443. /package/dist/cli/{chunk-35d4wj9f.js.map → chunk-2n4ps1b3.js.map} +0 -0
  444. /package/dist/cli/{chunk-e04dxsz1.js.map → chunk-4ejxbzee.js.map} +0 -0
  445. /package/dist/cli/{chunk-tzmab476.js.map → chunk-5ffywsmb.js.map} +0 -0
  446. /package/dist/cli/{chunk-2z928egk.js.map → chunk-5wj2qwn2.js.map} +0 -0
  447. /package/dist/cli/{chunk-b07cmahc.js.map → chunk-amrb6hgh.js.map} +0 -0
  448. /package/dist/cli/{chunk-akbpwfxc.js.map → chunk-cmtcnh9f.js.map} +0 -0
  449. /package/dist/cli/{chunk-crgn1q09.js.map → chunk-e7vvk5ya.js.map} +0 -0
  450. /package/dist/cli/{chunk-tkacnehg.js.map → chunk-gp84za0h.js.map} +0 -0
  451. /package/dist/cli/{chunk-r9rcc4w7.js.map → chunk-p804f3pe.js.map} +0 -0
  452. /package/dist/cli/{chunk-8ktnccpt.js.map → chunk-ta27feev.js.map} +0 -0
  453. /package/dist/cli/{chunk-a58773jm.js.map → chunk-y4ykbark.js.map} +0 -0
  454. /package/dist/cli/{chunk-p3v96n38.js.map → chunk-y5h4r2y0.js.map} +0 -0
  455. /package/dist/cli/{chunk-acanzt5p.js.map → chunk-yj17vs39.js.map} +0 -0
@@ -0,0 +1,17 @@
1
+ ---
2
+ // `<RequestExample>`: code blocks showing a request, one tab per titled
3
+ // fence (or a language menu with `dropdown`). On a wide screen it pins to the
4
+ // column beside the page (see `ApiRail.astro`), above any `<ResponseExample>`.
5
+ import CodeGroup from "./CodeGroup.astro";
6
+
7
+ interface Props {
8
+ /** A language menu in place of tabs. */
9
+ dropdown?: boolean;
10
+ }
11
+
12
+ const { dropdown = false } = Astro.props;
13
+ ---
14
+
15
+ <CodeGroup dropdown={dropdown}>
16
+ <slot />
17
+ </CodeGroup>
@@ -0,0 +1,17 @@
1
+ ---
2
+ // `<ResponseExample>`: code blocks showing a response, one tab per titled
3
+ // fence (or a language menu with `dropdown`). On a wide screen it pins to the
4
+ // column beside the page (see `ApiRail.astro`), below any `<RequestExample>`.
5
+ import CodeGroup from "./CodeGroup.astro";
6
+
7
+ interface Props {
8
+ /** A language menu in place of tabs. */
9
+ dropdown?: boolean;
10
+ }
11
+
12
+ const { dropdown = false } = Astro.props;
13
+ ---
14
+
15
+ <CodeGroup dropdown={dropdown}>
16
+ <slot />
17
+ </CodeGroup>
@@ -0,0 +1,32 @@
1
+ ---
2
+ // `<ResponseField name="id" type="string" required>`: a field of the
3
+ // response, with optional `pre` and `post` labels around its name and its
4
+ // description in the slot.
5
+ import ApiField from "./ApiField.astro";
6
+ import { fieldLabels } from "./api-field.ts";
7
+ import type { ResponseFieldProps } from "./api-field.ts";
8
+
9
+ type Props = ResponseFieldProps;
10
+
11
+ const {
12
+ default: value,
13
+ deprecated,
14
+ name,
15
+ post,
16
+ pre,
17
+ required,
18
+ type,
19
+ } = Astro.props;
20
+ ---
21
+
22
+ <ApiField
23
+ default={value}
24
+ deprecated={deprecated}
25
+ name={name}
26
+ post={fieldLabels(post)}
27
+ pre={fieldLabels(pre)}
28
+ required={required}
29
+ type={type}
30
+ >
31
+ <slot />
32
+ </ApiField>
@@ -17,6 +17,7 @@ const canRenderIcon =
17
17
 
18
18
  <div
19
19
  class="relative [counter-increment:blume-step]"
20
+ data-blume-step
20
21
  >
21
22
  <span class:list={[markerClass, !canRenderIcon && "before:[content:counter(blume-step)]"]}>
22
23
  {canRenderIcon && <Icon name={icon} size={14} />}
@@ -17,6 +17,7 @@ const previewPattern =
17
17
  >
18
18
  <div
19
19
  class="flex aspect-[16/9] min-h-32 items-center justify-center bg-muted/40 p-4 text-muted-foreground [&_img]:max-h-full [&_img]:max-w-full [&_svg]:max-h-full [&_svg]:max-w-full"
20
+ data-blume-narration="skip"
20
21
  data-blume-tile-preview
21
22
  style={previewPattern}
22
23
  >
@@ -39,6 +39,7 @@ const tooltipId = tooltipIdFor(Astro.locals, [
39
39
  />
40
40
  <span
41
41
  class="absolute bottom-full left-1/2 z-20 mb-2 w-max max-w-[min(20rem,calc(100vw-1rem))] -translate-x-1/2 rounded-blume border border-border bg-background p-3 text-start font-sans text-foreground text-sm shadow-lg"
42
+ data-blume-narration="skip"
42
43
  data-blume-tooltip-panel
43
44
  id={tooltipId}
44
45
  role="tooltip"
@@ -3,6 +3,7 @@
3
3
 
4
4
  <blume-tree
5
5
  class="not-prose my-6 block rounded-blume border border-border bg-background p-3 text-sm"
6
+ data-blume-narration="skip"
6
7
  data-blume-tree
7
8
  role="tree"
8
9
  >
@@ -37,7 +37,10 @@ const columns = "grid grid-cols-[1fr_2fr_auto] items-center gap-4";
37
37
 
38
38
  {
39
39
  entries.length > 0 && (
40
- <div class="not-prose my-6 rounded-blume border border-border bg-muted/30 p-1.5">
40
+ <div
41
+ class="not-prose my-6 rounded-blume border border-border bg-muted/30 p-1.5"
42
+ data-blume-narration="skip"
43
+ >
41
44
  <div class={`${columns} px-3 py-2 font-medium text-muted-foreground text-xs`}>
42
45
  <span>{strings.prop}</span>
43
46
  <span>{strings.type}</span>
@@ -0,0 +1,21 @@
1
+ ---
2
+ /**
3
+ * One view of a page written for several (`<View title="Python">`), as
4
+ * Mintlify writes them. Each distinct title is an option of the page's view
5
+ * picker, and blocks sharing a title show and hide together, so a page can
6
+ * alternate between prose everyone reads and view-specific sections. The
7
+ * picker and the hiding live in `ViewSwitcher.astro`; `icon` shows beside
8
+ * the view's name there.
9
+ */
10
+ interface Props {
11
+ class?: string;
12
+ icon?: string;
13
+ title: string;
14
+ }
15
+
16
+ const { class: className, title } = Astro.props;
17
+ ---
18
+
19
+ <div class={className} data-blume-view={title}>
20
+ <slot />
21
+ </div>
@@ -0,0 +1,168 @@
1
+ ---
2
+ import type { PageView } from "../../markdown/views.ts";
3
+ import Icon from "../Icon.astro";
4
+ import { VIEW_INIT_SCRIPT, viewStyle } from "./view-switcher.ts";
5
+
6
+ /**
7
+ * The picker for a page written in `<View>` blocks, rendered above the
8
+ * content with the page's views (collected by `markdown/views.ts`). The
9
+ * style hides every other view's blocks and table of contents entries, and
10
+ * the inline script applies a linked or remembered choice before the views
11
+ * paint (see `view-switcher.ts`).
12
+ */
13
+ interface Props {
14
+ /** The select's accessible name (`content.selectView`). */
15
+ label: string;
16
+ views: PageView[];
17
+ }
18
+
19
+ const { label, views } = Astro.props;
20
+ const titles = views.map((view) => view.title);
21
+ ---
22
+
23
+ <blume-view-switcher
24
+ class="not-prose mt-4 mb-8 flex items-center gap-2"
25
+ data-views={JSON.stringify(titles)}
26
+ >
27
+ <style is:inline set:html={viewStyle(titles)} />
28
+ {
29
+ views.map(
30
+ (view) =>
31
+ view.icon && (
32
+ <span
33
+ class="inline-flex shrink-0 text-muted-foreground"
34
+ data-blume-view={view.title}
35
+ >
36
+ <Icon name={view.icon} size={16} />
37
+ </span>
38
+ )
39
+ )
40
+ }
41
+ <select
42
+ aria-label={label}
43
+ class="cursor-pointer rounded-blume border border-border bg-background px-3 py-1.5 font-medium text-foreground text-sm"
44
+ >
45
+ {views.map((view) => <option value={view.title}>{view.title}</option>)}
46
+ </select>
47
+ <script is:inline set:html={VIEW_INIT_SCRIPT} />
48
+ </blume-view-switcher>
49
+
50
+ <script>
51
+ import {
52
+ chooseView,
53
+ VIEW_PARAM,
54
+ VIEW_STORAGE_KEY,
55
+ } from "./view-switcher.ts";
56
+
57
+ const HEADINGS = "h2[id], h3[id], h4[id], h5[id], h6[id]";
58
+
59
+ const stored = (): string | null => {
60
+ try {
61
+ return localStorage.getItem(VIEW_STORAGE_KEY);
62
+ } catch {
63
+ return null;
64
+ }
65
+ };
66
+
67
+ /** The view holding the element a `#hash` link points at, if any. */
68
+ const linkedView = (): string | undefined => {
69
+ const id = decodeURIComponent(location.hash.slice(1));
70
+ const target = id ? document.getElementById(id) : null;
71
+ return (
72
+ target?.closest<HTMLElement>("article [data-blume-view]")?.dataset
73
+ .blumeView ?? undefined
74
+ );
75
+ };
76
+
77
+ /**
78
+ * Tag each table of contents entry with the view its heading is in, so the
79
+ * page's view style hides it with the view.
80
+ */
81
+ const tagTableOfContents = () => {
82
+ const views = new Map<string, string>();
83
+ for (const view of document.querySelectorAll<HTMLElement>(
84
+ "article [data-blume-view]"
85
+ )) {
86
+ for (const heading of view.querySelectorAll(HEADINGS)) {
87
+ views.set(heading.id, view.dataset.blumeView ?? "");
88
+ }
89
+ }
90
+ for (const link of document.querySelectorAll<HTMLAnchorElement>(
91
+ 'blume-toc a[href^="#"]'
92
+ )) {
93
+ const id = decodeURIComponent(link.getAttribute("href")?.slice(1) ?? "");
94
+ const item = link.closest("li");
95
+ const title = views.get(id);
96
+ if (item && title) {
97
+ item.dataset.blumeView = title;
98
+ }
99
+ }
100
+ };
101
+
102
+ class BlumeViewSwitcher extends HTMLElement {
103
+ #titles: string[] = [];
104
+
105
+ connectedCallback() {
106
+ try {
107
+ this.#titles = JSON.parse(this.dataset.views ?? "[]");
108
+ } catch {
109
+ this.#titles = [];
110
+ }
111
+ tagTableOfContents();
112
+ this.#show(
113
+ chooseView(this.#titles, [
114
+ linkedView(),
115
+ new URL(location.href).searchParams.get(VIEW_PARAM),
116
+ stored(),
117
+ ])
118
+ );
119
+ this.querySelector("select")?.addEventListener("change", this.#onPick);
120
+ window.addEventListener("hashchange", this.#onHash);
121
+ }
122
+
123
+ disconnectedCallback() {
124
+ this.querySelector("select")?.removeEventListener("change", this.#onPick);
125
+ window.removeEventListener("hashchange", this.#onHash);
126
+ }
127
+
128
+ #show(title: string | undefined) {
129
+ if (title === undefined) {
130
+ return;
131
+ }
132
+ document.body.dataset.blumeView = title;
133
+ const select = this.querySelector("select");
134
+ if (select) {
135
+ select.value = title;
136
+ }
137
+ }
138
+
139
+ #onPick = (event: Event) => {
140
+ // SAFETY: the listener is only added to this picker's <select>.
141
+ const { value } = event.currentTarget as HTMLSelectElement;
142
+ this.#show(value);
143
+ try {
144
+ localStorage.setItem(VIEW_STORAGE_KEY, value);
145
+ } catch {
146
+ // Storage can be off; the choice still holds for this page.
147
+ }
148
+ // replaceState: the link stays shareable without a history entry per
149
+ // pick, and the client router needs the entry's state kept.
150
+ const url = new URL(location.href);
151
+ url.searchParams.set(VIEW_PARAM, value);
152
+ history.replaceState(history.state, "", url);
153
+ };
154
+
155
+ // An in-page link to a heading in another view switches to that view.
156
+ #onHash = () => {
157
+ const view = linkedView();
158
+ if (view && view !== document.body.dataset.blumeView) {
159
+ this.#show(view);
160
+ document.getElementById(decodeURIComponent(location.hash.slice(1)))?.scrollIntoView();
161
+ }
162
+ };
163
+ }
164
+
165
+ if (!customElements.get("blume-view-switcher")) {
166
+ customElements.define("blume-view-switcher", BlumeViewSwitcher);
167
+ }
168
+ </script>
@@ -0,0 +1,100 @@
1
+ /**
2
+ * The props `<ParamField>` and `<ResponseField>` take, in Mintlify's shape: a
3
+ * parameter names its location with the attribute that carries its name
4
+ * (`<ParamField query="limit">`), and both take `type`, `required`,
5
+ * `deprecated`, and `default`; a response field adds `pre` and `post` labels
6
+ * around its name. Pure, so the Markdown downlevel and the playground can
7
+ * read fields the way the components render them.
8
+ */
9
+
10
+ /** Where a `<ParamField>` parameter goes, in the order the attributes are checked. */
11
+ export const PARAM_LOCATIONS = ["path", "query", "header", "body"] as const;
12
+
13
+ /** A parameter's location. */
14
+ export type ParamLocation = (typeof PARAM_LOCATIONS)[number];
15
+
16
+ /** A literal value as MDX passes it: a string attribute, or an expression's data. */
17
+ export type FieldValue =
18
+ | string
19
+ | number
20
+ | boolean
21
+ | null
22
+ | undefined
23
+ | FieldValue[]
24
+ | { [key: string]: FieldValue };
25
+
26
+ /** A boolean prop: MDX shorthand (`required`) is `true`, `required="true"` a string. */
27
+ export type FlagProp = boolean | string;
28
+
29
+ /** What both field components take. */
30
+ export interface FieldProps {
31
+ /** The value used when none is given. */
32
+ default?: FieldValue;
33
+ deprecated?: FlagProp;
34
+ required?: FlagProp;
35
+ /** Free text: `string`, `integer`, `string[]`, `object`. */
36
+ type?: string;
37
+ }
38
+
39
+ /** `<ParamField>`: a request parameter, named by its location attribute. */
40
+ export interface ParamFieldProps extends FieldProps {
41
+ body?: string;
42
+ header?: string;
43
+ /** The name, for a field with no location attribute. */
44
+ name?: string;
45
+ path?: string;
46
+ /** Placeholder text for the parameter's playground input. */
47
+ placeholder?: string;
48
+ query?: string;
49
+ }
50
+
51
+ /** `<ResponseField>`: a field of the response. */
52
+ export interface ResponseFieldProps extends FieldProps {
53
+ name: string;
54
+ /** Labels after the name. */
55
+ post?: string | string[];
56
+ /** Labels before the name. */
57
+ pre?: string | string[];
58
+ }
59
+
60
+ /** Whether a flag prop is on. */
61
+ export const isOn = (value?: FlagProp): boolean =>
62
+ value === true || value === "true";
63
+
64
+ /** Where a parameter goes, and what it's called. */
65
+ export interface ParamFieldName {
66
+ location?: ParamLocation;
67
+ name?: string;
68
+ }
69
+
70
+ /** A parameter's location and name: the first location attribute set, else `name`. */
71
+ export const paramField = (props: ParamFieldProps): ParamFieldName => {
72
+ for (const location of PARAM_LOCATIONS) {
73
+ const name = props[location];
74
+ if (name !== undefined) {
75
+ return { location, name };
76
+ }
77
+ }
78
+ return { name: props.name };
79
+ };
80
+
81
+ const isText = (value: FieldValue): value is string =>
82
+ typeof value === "string";
83
+
84
+ /** A default value as shown: text as written, anything else as JSON; `null` when there is none. */
85
+ export const defaultLabel = (value?: FieldValue): string | null => {
86
+ if (value === undefined || value === null || value === "") {
87
+ return null;
88
+ }
89
+ return isText(value) ? value : JSON.stringify(value);
90
+ };
91
+
92
+ /** `pre`/`post` labels as a list, whether one was written or several. */
93
+ export const fieldLabels = (value?: string | string[]): string[] => {
94
+ if (value === undefined) {
95
+ return [];
96
+ }
97
+ return (Array.isArray(value) ? value : [value]).filter(
98
+ (label) => label !== ""
99
+ );
100
+ };
@@ -0,0 +1,224 @@
1
+ import type { ParameterLike, SchemaLike } from "../openapi/helpers.ts";
2
+ import { resolveSecurity } from "../openapi/security.ts";
3
+ import type { OperationSecurity } from "../openapi/security.ts";
4
+ import type { FieldValue, ParamLocation } from "./api-field.ts";
5
+
6
+ /**
7
+ * A hand-written API page's endpoint, as its `api` frontmatter and its
8
+ * `<ParamField>`s describe it, turned into the OpenAPI-shaped pieces the
9
+ * operation playground and code samples are built from: a page with
10
+ * `api: "POST /v1/users"` gets the same Try it panel and request samples as
11
+ * a spec'd operation, with no spec. Mintlify's frontmatter, so migrated pages
12
+ * keep it as written.
13
+ */
14
+
15
+ /** How a hand-written endpoint authenticates (`authMethod`, `api.auth.method`). */
16
+ export const AUTH_METHODS = ["bearer", "basic", "key", "none"] as const;
17
+ export type AuthMethod = (typeof AUTH_METHODS)[number];
18
+
19
+ /** What a page's playground shows: all of it, the samples only, or none. */
20
+ export const PLAYGROUND_MODES = ["interactive", "simple", "none"] as const;
21
+ export type PlaygroundMode = (typeof PLAYGROUND_MODES)[number];
22
+
23
+ /** `api` frontmatter: an HTTP method, then a path or a full URL. */
24
+ export const API_ENDPOINT =
25
+ /^(?<method>GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS|TRACE)\s+(?<target>\S+)$/iu;
26
+
27
+ /** The site's default auth for hand-written endpoints (`api.auth`). */
28
+ export interface EndpointAuth {
29
+ method: AuthMethod;
30
+ /** The header an API key goes in. */
31
+ name?: string;
32
+ }
33
+
34
+ /** One `<ParamField>`, as the page's plugin collected it. */
35
+ export interface EndpointField {
36
+ default?: FieldValue;
37
+ deprecated?: boolean;
38
+ /** Body fields nested in this one's `<Expandable>`. */
39
+ fields?: EndpointField[];
40
+ location: ParamLocation;
41
+ name: string;
42
+ placeholder?: string;
43
+ required?: boolean;
44
+ type?: string;
45
+ }
46
+
47
+ /** What the page's plugin hands the endpoint components. */
48
+ export interface EndpointSpec {
49
+ authMethod?: AuthMethod;
50
+ fields: EndpointField[];
51
+ method: string;
52
+ playground?: PlaygroundMode;
53
+ /** The page has a `<RequestExample>`, which stands in for generated samples. */
54
+ requestExample: boolean;
55
+ /** The `api` target as written: a path, or a full URL. */
56
+ target: string;
57
+ }
58
+
59
+ /** The method and target of an `api` value, or `null` when it isn't one. */
60
+ export const parseApiEndpoint = (
61
+ value: string
62
+ ): { method: string; target: string } | null => {
63
+ const groups = API_ENDPOINT.exec(value.trim())?.groups;
64
+ return groups?.method && groups.target
65
+ ? { method: groups.method.toUpperCase(), target: groups.target }
66
+ : null;
67
+ };
68
+
69
+ const ABSOLUTE_URL = /^(?<origin>https?:\/\/[^/?#]+)(?<rest>.*)$/iu;
70
+
71
+ /** An endpoint's target, split the way the operation model takes it. */
72
+ export interface EndpointTarget {
73
+ path: string;
74
+ servers: { url: string }[];
75
+ }
76
+
77
+ /**
78
+ * The target split into a server and a path: a full URL is its own server,
79
+ * and a path joins the site's `api.server`, or stays relative to the docs
80
+ * when there is none.
81
+ */
82
+ export const endpointTarget = (
83
+ target: string,
84
+ server?: string
85
+ ): EndpointTarget => {
86
+ const absolute = ABSOLUTE_URL.exec(target)?.groups;
87
+ if (absolute?.origin) {
88
+ return {
89
+ path: absolute.rest || "/",
90
+ servers: [{ url: absolute.origin }],
91
+ };
92
+ }
93
+ return {
94
+ path: target.startsWith("/") ? target : `/${target}`,
95
+ servers: server ? [{ url: server.replace(/\/+$/u, "") }] : [],
96
+ };
97
+ };
98
+
99
+ const PRIMITIVES = new Set([
100
+ "string",
101
+ "number",
102
+ "integer",
103
+ "boolean",
104
+ "object",
105
+ ]);
106
+
107
+ /**
108
+ * A field's JSON schema from its `type`: `string[]` an array of strings, a
109
+ * field with nested fields an object with those properties, and anything
110
+ * that isn't a JSON type (`enum<string>`, `Date`) a string, which is what it
111
+ * goes over the wire as. A default and placeholder carry over, the
112
+ * placeholder as the example the samples show.
113
+ */
114
+ export const fieldSchema = (field: EndpointField): SchemaLike => {
115
+ const type = field.type?.trim().toLowerCase() ?? "";
116
+ if (type.endsWith("[]")) {
117
+ return {
118
+ items: fieldSchema({
119
+ ...field,
120
+ default: undefined,
121
+ type: type.slice(0, -2),
122
+ }),
123
+ type: "array",
124
+ };
125
+ }
126
+ const nested = field.fields ?? [];
127
+ const schema: SchemaLike =
128
+ nested.length > 0
129
+ ? {
130
+ properties: Object.fromEntries(
131
+ nested.map((child) => [child.name, fieldSchema(child)])
132
+ ),
133
+ type: "object",
134
+ }
135
+ : { type: PRIMITIVES.has(type) ? type : "string" };
136
+ const required = nested
137
+ .filter((child) => child.required)
138
+ .map((child) => child.name);
139
+ if (required.length > 0) {
140
+ schema.required = required;
141
+ }
142
+ if (field.default !== undefined) {
143
+ schema.default = field.default;
144
+ }
145
+ if (field.placeholder) {
146
+ schema.example = field.placeholder;
147
+ }
148
+ if (field.deprecated) {
149
+ schema.deprecated = true;
150
+ }
151
+ return schema;
152
+ };
153
+
154
+ /** The path, query, and header fields as OpenAPI parameters. */
155
+ export const endpointParameters = (fields: EndpointField[]): ParameterLike[] =>
156
+ fields.flatMap((field) =>
157
+ field.location === "body"
158
+ ? []
159
+ : [
160
+ {
161
+ deprecated: field.deprecated === true,
162
+ in: field.location,
163
+ name: field.name,
164
+ // A path parameter is always required.
165
+ required: field.location === "path" || field.required === true,
166
+ schema: fieldSchema(field),
167
+ },
168
+ ]
169
+ );
170
+
171
+ /** The body fields as a JSON request body, or `undefined` when there are none. */
172
+ export const endpointBody = (fields: EndpointField[]) => {
173
+ const body = fields.filter((field) => field.location === "body");
174
+ return body.length > 0
175
+ ? {
176
+ content: {
177
+ "application/json": {
178
+ schema: fieldSchema({
179
+ fields: body,
180
+ location: "body",
181
+ name: "",
182
+ type: "object",
183
+ }),
184
+ },
185
+ },
186
+ }
187
+ : undefined;
188
+ };
189
+
190
+ /**
191
+ * The security an endpoint enforces: its page's `authMethod`, else the
192
+ * site's `api.auth`, else none. An API key goes in the configured header,
193
+ * `x-api-key` when none is named.
194
+ */
195
+ export const endpointSecurity = (
196
+ authMethod?: AuthMethod,
197
+ siteAuth?: EndpointAuth
198
+ ): OperationSecurity => {
199
+ const method = authMethod ?? siteAuth?.method ?? "none";
200
+ if (method === "none") {
201
+ return { alternatives: [], optional: false };
202
+ }
203
+ if (method === "key") {
204
+ return resolveSecurity([{ apiKey: [] }], {
205
+ apiKey: {
206
+ in: "header",
207
+ name: siteAuth?.name ?? "x-api-key",
208
+ type: "apiKey",
209
+ },
210
+ });
211
+ }
212
+ return resolveSecurity([{ [method]: [] }], {
213
+ [method]: { scheme: method, type: "http" },
214
+ });
215
+ };
216
+
217
+ /**
218
+ * The endpoint spec the page's plugin wrote into the `spec` attribute of
219
+ * `<ApiEndpoint>` and `<ApiPlayground>`.
220
+ */
221
+ export const readEndpointSpec = (json: string): EndpointSpec =>
222
+ // SAFETY: the attribute is `JSON.stringify` of an `EndpointSpec`, written
223
+ // by `markdown/api-rail.ts` at build; authors never write it.
224
+ JSON.parse(json) as EndpointSpec;