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
@@ -46,6 +46,9 @@ export type HttpMethod = (typeof HTTP_METHODS)[number];
46
46
  /** Group used for operations that declare no tag. */
47
47
  const UNTAGGED = "Operations";
48
48
 
49
+ /** Group used for webhooks that declare no tag. */
50
+ const UNTAGGED_WEBHOOKS = "Webhooks";
51
+
49
52
  /** A lower-case letter or digit followed by a capital: `addPet`, `v2List`. */
50
53
  const CASE_BOUNDARY = /(?<before>[\p{Ll}\p{N}])(?<capital>\p{Lu})/gu;
51
54
  /** The last capital of an acronym before a capitalized word: `HTTPResponse`. */
@@ -101,6 +104,12 @@ export interface ApiOperationRef {
101
104
  deprecated: boolean;
102
105
  /** The channel the operation acts on (AsyncAPI only). */
103
106
  channelId?: string;
107
+ /**
108
+ * An OpenAPI 3.1 webhook: a request the API sends to the reader's endpoint
109
+ * rather than one it serves. `path` then holds the webhook's name, the key
110
+ * it has under the document's `webhooks`.
111
+ */
112
+ webhook?: true;
104
113
  }
105
114
 
106
115
  /**
@@ -134,8 +143,8 @@ export interface ApiSpecData {
134
143
  /** Operations keyed by {@link ApiOperationRef.key}. */
135
144
  operations: Record<string, ApiOperationRef>;
136
145
  tags: ApiTagRef[];
137
- /** Code-sample languages to render per operation. */
138
- codeSamples: string[];
146
+ /** Code-sample languages to render per operation; `false` for none. */
147
+ codeSamples: string[] | false;
139
148
  /** Whether nested schema rows start expanded. */
140
149
  expandSchemas: boolean;
141
150
  /**
@@ -301,6 +310,11 @@ export const tagSlugger = (): ((name: string) => string) => {
301
310
  };
302
311
  };
303
312
 
313
+ /** One entry of a document's `paths` or `webhooks` map. */
314
+ type PathItemEntry =
315
+ | NonNullable<ApiDocument["paths"]>[string]
316
+ | NonNullable<ApiDocument["webhooks"]>[string];
317
+
304
318
  /** An operation before the collector assigns its unique key and route. */
305
319
  type CollectedOperation = Omit<ApiOperationRef, "route" | "tagSlug">;
306
320
 
@@ -386,10 +400,10 @@ export const operationCollector = (
386
400
 
387
401
  /**
388
402
  * Flatten a 3.1 document into a route-mapped operation list and its ordered
389
- * tags. Operations inherit the first tag they declare; keys are de-duplicated so
390
- * a repeated `operationId` still yields distinct routes. `warnings` reports
391
- * anything skipped (a `$ref` path item, webhooks), so missing operations
392
- * aren't silent.
403
+ * tags, webhooks included. Operations inherit the first tag they declare; keys
404
+ * are de-duplicated so a repeated `operationId` still yields distinct routes.
405
+ * `warnings` reports anything skipped (a `$ref` path item), so missing
406
+ * operations aren't silent.
393
407
  */
394
408
  export const extractOperations = (
395
409
  document: ApiDocument,
@@ -404,16 +418,24 @@ export const extractOperations = (
404
418
  );
405
419
  const collector = operationCollector(baseRoute, tagMeta);
406
420
 
407
- for (const [path, item] of Object.entries(document.paths ?? {})) {
421
+ // A path item's operations, filed under `name`: a path for `paths`, the
422
+ // webhook's key for `webhooks`.
423
+ const addPathItem = (
424
+ name: string,
425
+ item: PathItemEntry,
426
+ webhook: boolean
427
+ ): void => {
408
428
  // A parsed spec can carry a null path item despite the type; skip it.
409
429
  if (!item) {
410
- continue;
430
+ return;
411
431
  }
412
432
  if ("$ref" in item) {
413
433
  warnings.push(
414
- `Path "${path}" is a $ref to a shared path item; referenced path items are not resolved, so its operations are missing from the reference. Inline the path item under "paths" to render it.`
434
+ webhook
435
+ ? `Webhook "${name}" is a $ref to a shared path item; referenced path items are not resolved, so it is missing from the reference. Inline the path item under "webhooks" to render it.`
436
+ : `Path "${name}" is a $ref to a shared path item; referenced path items are not resolved, so its operations are missing from the reference. Inline the path item under "paths" to render it.`
415
437
  );
416
- continue;
438
+ return;
417
439
  }
418
440
  for (const method of HTTP_METHODS) {
419
441
  const operation = item[method];
@@ -421,26 +443,35 @@ export const extractOperations = (
421
443
  continue;
422
444
  }
423
445
  const operationId = specText(operation.operationId);
424
- collector.add({
446
+ const entry: CollectedOperation = {
425
447
  deprecated: operation.deprecated ?? false,
426
448
  description: specText(operation.description) ?? "",
427
- key: operationKey(method, path, operationId),
449
+ // A webhook without an operationId is keyed by its name, which is
450
+ // already an identifier (`newPet` -> `new-pet`).
451
+ key: operationKey(
452
+ method,
453
+ name,
454
+ operationId ?? (webhook ? name : undefined)
455
+ ),
428
456
  method,
429
457
  operationId,
430
- path,
458
+ path: name,
431
459
  summary: specText(operation.summary) ?? "",
432
- tag: specText(operation.tags?.[0]) ?? UNTAGGED,
433
- });
460
+ tag:
461
+ specText(operation.tags?.[0]) ??
462
+ (webhook ? UNTAGGED_WEBHOOKS : UNTAGGED),
463
+ };
464
+ collector.add(webhook ? { ...entry, webhook: true } : entry);
434
465
  }
435
- }
466
+ };
436
467
 
437
- // OpenAPI 3.1 webhooks (requests the API sends, not ones it serves) have no
438
- // page renderer; say so rather than dropping them silently.
439
- const webhooks = Object.keys(document.webhooks ?? {});
440
- if (webhooks.length > 0) {
441
- warnings.push(
442
- `The spec declares ${webhooks.length === 1 ? "a webhook" : `${webhooks.length} webhooks`} under "webhooks" (${webhooks.join(", ")}); webhooks aren't rendered, so they are missing from the reference.`
443
- );
468
+ for (const [path, item] of Object.entries(document.paths ?? {})) {
469
+ addPathItem(path, item, false);
470
+ }
471
+ // OpenAPI 3.1 webhooks: requests the API sends rather than serves. They
472
+ // follow the paths, so an untagged webhook group sits after the endpoints.
473
+ for (const [name, item] of Object.entries(document.webhooks ?? {})) {
474
+ addPathItem(name, item, true);
444
475
  }
445
476
 
446
477
  return { ...collector.finish(), warnings };
@@ -458,7 +489,9 @@ export const operationObject = (
458
489
  // (AsyncAPI documents go to `asyncApiOperationObject`), so the spec's
459
490
  // document is the OpenAPI shape.
460
491
  const document = spec.document as ApiDocument;
461
- const item = document.paths?.[ref.path];
492
+ const item = ref.webhook
493
+ ? document.webhooks?.[ref.path]
494
+ : document.paths?.[ref.path];
462
495
  const operation = method === undefined ? undefined : item?.[method];
463
496
  return isOperation(operation) ? operation : undefined;
464
497
  };
@@ -0,0 +1,256 @@
1
+ import { exec, query } from "jsonpath-rfc9535";
2
+ import { z } from "zod";
3
+
4
+ import type { JsonValue } from "../core/adapter.ts";
5
+
6
+ /**
7
+ * OpenAPI Overlays: the OpenAPI Initiative's format for changing an API
8
+ * description without editing it — an ordered list of actions, each an RFC
9
+ * 9535 JSONPath `target` with an `update` to merge in, a `copy` of another
10
+ * node to merge in, or `remove`. A spec generated by another tool or owned by
11
+ * another team can then carry docs-only changes (better descriptions, hidden
12
+ * internal operations, a public server URL) in a file of its own.
13
+ *
14
+ * Implements Overlay Specification 1.0 and 1.1 over `jsonpath-rfc9535`, which
15
+ * passes the JSONPath compliance suite in full, as the spec requires. The
16
+ * documents here are already parsed; `parse.ts` reads the files.
17
+ */
18
+
19
+ /** An overlay document or action that doesn't follow the spec. */
20
+ export class OverlayError extends Error {
21
+ constructor(message: string) {
22
+ super(message);
23
+ this.name = "OverlayError";
24
+ }
25
+ }
26
+
27
+ /** An object node, the only kind that holds named properties. */
28
+ interface JsonObject {
29
+ [key: string]: JsonValue;
30
+ }
31
+
32
+ /** A node that holds other nodes: where a match's parent can be. */
33
+ type JsonContainer = JsonObject | JsonValue[];
34
+
35
+ const isObject = (value: JsonValue | undefined): value is JsonObject =>
36
+ typeof value === "object" && value !== null && !Array.isArray(value);
37
+
38
+ /**
39
+ * A parsed YAML or JSON mapping, as the document an overlay acts on: the
40
+ * parser's own types say nothing of JSON, but what it parses is JSON.
41
+ */
42
+ export const isJsonObject = <Value>(
43
+ value: Value
44
+ ): value is Value & JsonObject =>
45
+ typeof value === "object" && value !== null && !Array.isArray(value);
46
+
47
+ const isContainer = (value: JsonValue | undefined): value is JsonContainer =>
48
+ typeof value === "object" && value !== null;
49
+
50
+ const isIndex = (key: string | number | undefined): key is number =>
51
+ typeof key === "number";
52
+
53
+ /** An object, an array, or anything else: what update merging keys on. */
54
+ type MergeKind = "array" | "object" | "primitive";
55
+
56
+ const mergeKindOf = (value: JsonValue): MergeKind => {
57
+ if (Array.isArray(value)) {
58
+ return "array";
59
+ }
60
+ return isObject(value) ? "object" : "primitive";
61
+ };
62
+
63
+ /** A merge kind in a sentence. */
64
+ const MERGE_KIND_PHRASE: Record<MergeKind, string> = {
65
+ array: "an array",
66
+ object: "an object",
67
+ primitive: "a primitive value",
68
+ };
69
+
70
+ const actionSchema = z.looseObject({
71
+ /** A JSONPath expression selecting one node to merge into the targets (1.1). */
72
+ copy: z.string().optional(),
73
+ description: z.string().optional(),
74
+ /** Remove every target from the map or array it's in. */
75
+ remove: z.boolean().default(false),
76
+ /** An RFC 9535 JSONPath expression selecting the nodes to act on. */
77
+ target: z.string().min(1),
78
+ /** A value merged into every target. */
79
+ update: z.json().optional(),
80
+ });
81
+
82
+ /** A validated overlay action. */
83
+ type OverlayAction = z.output<typeof actionSchema>;
84
+
85
+ const overlaySchema = z.looseObject({
86
+ actions: z.array(actionSchema).min(1),
87
+ extends: z.string().optional(),
88
+ info: z.looseObject({ title: z.string(), version: z.string() }),
89
+ overlay: z.string().regex(/^1\.[01]\.\d+$/u, {
90
+ message: "must be an Overlay Specification version, 1.0.x or 1.1.x",
91
+ }),
92
+ });
93
+
94
+ /** A validated overlay document. */
95
+ export type OverlayDocument = z.output<typeof overlaySchema>;
96
+
97
+ /** Validate a parsed overlay document, naming the file in any error. */
98
+ export const overlayDocument = <Value>(
99
+ value: Value,
100
+ name: string
101
+ ): OverlayDocument => {
102
+ const parsed = overlaySchema.safeParse(value);
103
+ if (parsed.success) {
104
+ return parsed.data;
105
+ }
106
+ const [issue] = parsed.error.issues;
107
+ const at = issue?.path.length ? `\`${issue.path.join(".")}\` ` : "";
108
+ throw new OverlayError(
109
+ `${name} is not an overlay document: ${at}${issue?.message ?? "invalid"}.`
110
+ );
111
+ };
112
+
113
+ /** A matched node, where it sits, and how to reach it. */
114
+ interface Match {
115
+ key?: string | number;
116
+ parent?: JsonContainer;
117
+ value: JsonValue;
118
+ }
119
+
120
+ /** The nodes `expression` selects, each with its parent (none for the root). */
121
+ const select = (document: JsonValue, expression: string): Match[] => {
122
+ const matches: Match[] = [];
123
+ exec(document, expression, (value, path) => {
124
+ let parent: JsonValue = document;
125
+ for (const key of path.slice(0, -1)) {
126
+ // SAFETY: the path is one the query walked, so every step is a key of
127
+ // the container before it.
128
+ parent = (parent as Record<string | number, JsonValue>)[key] ?? null;
129
+ }
130
+ const key = path.at(-1);
131
+ matches.push(
132
+ key === undefined || !isContainer(parent)
133
+ ? { value }
134
+ : { key, parent, value }
135
+ );
136
+ });
137
+ return matches;
138
+ };
139
+
140
+ /**
141
+ * Merge `patch` into `target` as the spec says: objects recursively, arrays
142
+ * by concatenation, a primitive replaced by a primitive. Returns the merged
143
+ * value; any other pairing throws.
144
+ */
145
+ const merge = (target: JsonValue, patch: JsonValue, at: string): JsonValue => {
146
+ if (isObject(target) && isObject(patch)) {
147
+ for (const [key, value] of Object.entries(patch)) {
148
+ // Only recurse into the target's own keys, so a `__proto__` or
149
+ // `constructor` key can't reach a prototype.
150
+ target[key] = Object.hasOwn(target, key)
151
+ ? merge(target[key] ?? null, value, `${at}.${key}`)
152
+ : structuredClone(value);
153
+ }
154
+ return target;
155
+ }
156
+ if (Array.isArray(target) && Array.isArray(patch)) {
157
+ target.push(...structuredClone(patch));
158
+ return target;
159
+ }
160
+ if (
161
+ mergeKindOf(target) === "primitive" &&
162
+ mergeKindOf(patch) === "primitive"
163
+ ) {
164
+ return patch;
165
+ }
166
+ throw new OverlayError(
167
+ `can't merge ${MERGE_KIND_PHRASE[mergeKindOf(patch)]} into ${MERGE_KIND_PHRASE[mergeKindOf(target)]} at ${at}`
168
+ );
169
+ };
170
+
171
+ /** Remove each match from its container, later siblings first. */
172
+ const removeAll = (matches: Match[]): void => {
173
+ for (const { key, parent } of matches.toReversed()) {
174
+ if (Array.isArray(parent) && isIndex(key)) {
175
+ parent.splice(key, 1);
176
+ } else if (isObject(parent) && key !== undefined && !isIndex(key)) {
177
+ // oxlint-disable-next-line typescript/no-dynamic-delete -- an overlay target's key is only known at run time
178
+ delete parent[key];
179
+ } else {
180
+ throw new OverlayError("can't remove the document itself");
181
+ }
182
+ }
183
+ };
184
+
185
+ /**
186
+ * Merge `patch` into every match, which must all be objects, all arrays, or
187
+ * all primitives. A primitive match is replaced in its parent.
188
+ */
189
+ const mergeAll = (matches: Match[], patch: JsonValue, target: string) => {
190
+ const kinds = new Set(matches.map((match) => mergeKindOf(match.value)));
191
+ if (kinds.size > 1) {
192
+ throw new OverlayError(
193
+ `its target selects a mix of ${[...kinds].toSorted().join(", ")} values, and an update needs all one kind`
194
+ );
195
+ }
196
+ for (const { key, parent, value } of matches) {
197
+ const merged = merge(value, patch, target);
198
+ if (merged !== value && parent !== undefined && key !== undefined) {
199
+ // SAFETY: `key` came from walking `parent`, so it indexes it.
200
+ (parent as Record<string | number, JsonValue>)[key] = merged;
201
+ }
202
+ }
203
+ };
204
+
205
+ /** The single node a `copy` expression selects, cloned. */
206
+ const copied = (document: JsonValue, expression: string): JsonValue => {
207
+ const values = query(document, expression);
208
+ const [value] = values;
209
+ if (values.length !== 1 || value === undefined) {
210
+ throw new OverlayError(
211
+ `its copy expression "${expression}" selects ${values.length} nodes, and copy needs exactly one`
212
+ );
213
+ }
214
+ return structuredClone(value);
215
+ };
216
+
217
+ const applyAction = (document: JsonValue, action: OverlayAction): void => {
218
+ const matches = select(document, action.target);
219
+ if (matches.length === 0) {
220
+ return;
221
+ }
222
+ if (action.remove) {
223
+ removeAll(matches);
224
+ return;
225
+ }
226
+ const patch =
227
+ action.update ??
228
+ (action.copy === undefined ? undefined : copied(document, action.copy));
229
+ if (patch !== undefined) {
230
+ mergeAll(matches, patch, action.target);
231
+ }
232
+ };
233
+
234
+ /**
235
+ * Apply an overlay to a parsed document, in place and in action order: each
236
+ * action sees the result of the one before. A target that matches nothing
237
+ * changes nothing. Throws an {@link OverlayError} naming the overlay file and
238
+ * the action that failed.
239
+ */
240
+ export const applyOverlay = (
241
+ document: JsonValue,
242
+ overlay: OverlayDocument,
243
+ name: string
244
+ ): void => {
245
+ for (const [index, action] of overlay.actions.entries()) {
246
+ try {
247
+ applyAction(document, action);
248
+ } catch (error) {
249
+ // SAFETY: the JSONPath parser and this module throw Error instances.
250
+ const { message } = error as Error;
251
+ throw new OverlayError(
252
+ `${name} action ${index + 1} (${action.target}): ${message}`
253
+ );
254
+ }
255
+ }
256
+ };
@@ -18,6 +18,7 @@ import { normalizeAsyncApiDocument } from "./asyncapi.ts";
18
18
  import { buildGraphqlDocument } from "./graphql-build.ts";
19
19
  import type { GraphqlDocument } from "./graphql.ts";
20
20
  import type { ApiDocument } from "./model.ts";
21
+ import { applyOverlay, isJsonObject, overlayDocument } from "./overlay.ts";
21
22
  import { SpecDependencyError } from "./spec-dependency-error.ts";
22
23
 
23
24
  /**
@@ -75,6 +76,11 @@ export class InvalidSpecError extends Error {
75
76
 
76
77
  /** Where and whether to cache a remote spec's text between runs. */
77
78
  export interface SpecFetchOptions {
79
+ /**
80
+ * Overlay documents (local paths or `http(s)` URLs) applied in order to
81
+ * the parsed spec, before it's upgraded. See `overlay.ts`.
82
+ */
83
+ overlays?: readonly string[];
78
84
  /** Dir for a last-good on-disk copy of a remote spec (offline fallback). */
79
85
  cacheDir?: string;
80
86
  /**
@@ -310,13 +316,60 @@ const readSpecText = async (
310
316
  const isApiDocument = <Value>(value: Value): value is Value & ApiDocument =>
311
317
  typeof value === "object" && value !== null;
312
318
 
319
+ /**
320
+ * Read a spec and apply its overlays, in order, to the parsed document as
321
+ * written (before any upgrade: an overlay targets its spec's own version).
322
+ * The overlays are read like the spec, local or remote, cached alike.
323
+ */
324
+ const readOverlaid = async (
325
+ spec: string,
326
+ root: string,
327
+ options: SpecFetchOptions
328
+ ): Promise<{ document: ReturnType<typeof normalize>; warnings: string[] }> => {
329
+ const [read, ...overlays] = await Promise.all(
330
+ [spec, ...(options.overlays ?? [])].map((path) =>
331
+ readSpecText(path, root, options)
332
+ )
333
+ );
334
+ const document = normalize(read?.text ?? "");
335
+ for (const [index, overlay] of overlays.entries()) {
336
+ const name = options.overlays?.[index] ?? "";
337
+ const parsed = overlayDocument(normalize(overlay.text), name);
338
+ // A spec that isn't a mapping has nothing to overlay; the caller
339
+ // rejects it as it would without overlays.
340
+ if (isJsonObject(document)) {
341
+ applyOverlay(document, parsed, name);
342
+ }
343
+ }
344
+ return {
345
+ document,
346
+ warnings: [read, ...overlays].flatMap((entry) => entry?.warnings ?? []),
347
+ };
348
+ };
349
+
350
+ /**
351
+ * A spec with its overlays applied, as JSON text: what the Scalar embed
352
+ * inlines, since it can't apply overlays itself.
353
+ */
354
+ export const readOverlaidSpec = async (
355
+ spec: string,
356
+ root: string,
357
+ options: SpecFetchOptions
358
+ ): Promise<{ text: string; warnings: string[] }> => {
359
+ const { document, warnings } = await readOverlaid(spec, root, options);
360
+ return { text: JSON.stringify(document), warnings };
361
+ };
362
+
313
363
  export const parseSpec = async (
314
364
  spec: string,
315
365
  root: string,
316
366
  options: SpecFetchOptions = {}
317
367
  ): Promise<ParsedSpec> => {
318
- const { text, warnings } = await readSpecText(spec, root, options);
319
- const normalized = normalize(text);
368
+ const { document: normalized, warnings } = await readOverlaid(
369
+ spec,
370
+ root,
371
+ options
372
+ );
320
373
  const { specification } = upgrade(normalized);
321
374
  // Reject a non-mapping here so the renderer never sees a non-document.
322
375
  if (!isApiDocument(specification)) {
@@ -28,8 +28,8 @@ export type BlumeReferenceKind = Exclude<ReferenceKind, "scalar">;
28
28
 
29
29
  /** Per-adapter display options for the Blume renderer. */
30
30
  export interface ReferenceDisplay {
31
- /** Code-sample languages shown per operation. */
32
- codeSamples: string[];
31
+ /** Code-sample languages shown per operation; `false` for none. */
32
+ codeSamples: string[] | false;
33
33
  /** Whether nested schema rows start expanded. */
34
34
  expandSchemas: boolean;
35
35
  /**
@@ -67,6 +67,11 @@ export interface ReferenceSource {
67
67
  seoDescriptionSuffix: boolean;
68
68
  /** Local path or `http(s)` URL, verbatim from config. */
69
69
  spec: string;
70
+ /**
71
+ * OpenAPI Overlay documents applied to `spec` in order (`openapi()` and
72
+ * `scalar()` only).
73
+ */
74
+ overlays?: string[];
70
75
  /**
71
76
  * URL of the live GraphQL endpoint the playground and code samples target
72
77
  * (GraphQL only — a schema, unlike an OpenAPI document, names no server).
@@ -184,6 +189,7 @@ interface SourceRow {
184
189
  route?: string;
185
190
  seoDescriptionSuffix: boolean;
186
191
  spec: string;
192
+ overlays?: string[];
187
193
  }
188
194
 
189
195
  /**
@@ -255,6 +261,9 @@ const referencesFor = (
255
261
  if (source.endpoint !== undefined) {
256
262
  reference.endpoint = source.endpoint;
257
263
  }
264
+ if (source.overlays !== undefined) {
265
+ reference.overlays = source.overlays;
266
+ }
258
267
  return reference;
259
268
  });
260
269
  };
@@ -363,11 +372,18 @@ export const builtinProxyReferences = (
363
372
  ref.display.playground.proxy === true
364
373
  );
365
374
 
375
+ /**
376
+ * Whether hand-written endpoint pages (`api` frontmatter) send their Try it
377
+ * requests through the built-in proxy: `api.playground.proxy: true`.
378
+ */
379
+ export const apiPagesUseBuiltinProxy = (config: ResolvedConfig): boolean =>
380
+ config.api.playground.enabled && config.api.playground.proxy === true;
381
+
366
382
  /**
367
383
  * Whether the built-in playground CORS proxy endpoint (`/_api-proxy`) must be
368
384
  * generated: some Blume-rendered reference's playground opted into it with
369
- * `proxy: true`. Shared by the server feature gate and the generator so the
370
- * two can never disagree.
385
+ * `proxy: true`, or the hand-written endpoint pages' did. Shared by the server
386
+ * feature gate and the generator so the two can never disagree.
371
387
  */
372
388
  export const needsPlaygroundProxy = (config: ResolvedConfig): boolean =>
373
- builtinProxyReferences(config).length > 0;
389
+ builtinProxyReferences(config).length > 0 || apiPagesUseBuiltinProxy(config);
@@ -270,7 +270,8 @@ const operationDescription = (
270
270
  // method (see `extractGraphqlOperations`).
271
271
  subject = `${operation.path} ${GRAPHQL_MEMBER_PHRASES[operation.method as GraphqlMember]}`;
272
272
  } else {
273
- subject = `${operation.method.toUpperCase()} ${operation.path} endpoint`;
273
+ // A webhook is a request the API sends, named rather than pathed.
274
+ subject = `${operation.method.toUpperCase()} ${operation.path} ${operation.webhook ? "webhook" : "endpoint"}`;
274
275
  }
275
276
  const suffix = `Reference for the ${subject} in the ${apiPhrase(spec)}.`;
276
277
  // Room for the joining space and the period `asSentence` may add, so a
@@ -348,6 +349,9 @@ export const operationMdx = (
348
349
  sidebar.badge = method;
349
350
  }
350
351
  const tags = graphql ? [operation.tag] : [operation.tag, method];
352
+ if (operation.webhook) {
353
+ tags.push("Webhook");
354
+ }
351
355
  return {
352
356
  body: withDescription(
353
357
  description,
@@ -6,6 +6,7 @@ import { scalarReferenceTemplate } from "../astro/templates.ts";
6
6
  import type { ResolvedConfig } from "../core/schema.ts";
7
7
  import { trimChar } from "../core/trim.ts";
8
8
  import { resolveAccent, resolveRadius } from "../theme/palette.ts";
9
+ import { readOverlaidSpec } from "./parse.ts";
9
10
  import { resolveReferences } from "./references.ts";
10
11
  import type { ReferenceSource } from "./references.ts";
11
12
 
@@ -53,14 +54,44 @@ const themeConfiguration = (config: ResolvedConfig, override?: string) => {
53
54
  };
54
55
  };
55
56
 
57
+ /** A source's Scalar spec config, or none when its overlays failed. */
58
+ interface SpecConfiguration {
59
+ config?: { content: string } | { url: string };
60
+ warning?: string;
61
+ }
62
+
63
+ /**
64
+ * The spec with its overlays applied, inlined: Scalar can't apply overlays
65
+ * itself. A failed overlay skips the page rather than embed the spec without
66
+ * it, since an overlay is often what hides the internal operations.
67
+ */
68
+ const overlaidConfiguration = async (
69
+ ref: ReferenceSource,
70
+ root: string
71
+ ): Promise<SpecConfiguration> => {
72
+ try {
73
+ const { text, warnings } = await readOverlaidSpec(ref.spec, root, {
74
+ overlays: ref.overlays,
75
+ });
76
+ return { config: { content: text }, warning: warnings[0] };
77
+ } catch (error) {
78
+ // SAFETY: reading, parsing, and overlaying throw Error instances.
79
+ const { message } = error as Error;
80
+ return {
81
+ warning: `API reference ${ref.route} was skipped: its overlays couldn't be applied to "${ref.spec}" (${message}).`,
82
+ };
83
+ }
84
+ };
85
+
56
86
  /** Build the Scalar spec config for a source: inline `content` or remote `url`. */
57
87
  const specConfiguration = async (
58
- spec: string,
88
+ ref: ReferenceSource,
59
89
  root: string
60
- ): Promise<{
61
- config: { content: string } | { url: string };
62
- warning?: string;
63
- }> => {
90
+ ): Promise<SpecConfiguration> => {
91
+ const { spec } = ref;
92
+ if (ref.overlays?.length) {
93
+ return await overlaidConfiguration(ref, root);
94
+ }
64
95
  if (URL_SPEC.test(spec)) {
65
96
  return { config: { url: spec } };
66
97
  }
@@ -133,7 +164,7 @@ export const buildReferenceFiles = async (options: {
133
164
  const built = await Promise.all(
134
165
  accepted.map(async (ref) => ({
135
166
  ref,
136
- spec: await specConfiguration(ref.spec, root),
167
+ spec: await specConfiguration(ref, root),
137
168
  }))
138
169
  );
139
170
 
@@ -142,6 +173,9 @@ export const buildReferenceFiles = async (options: {
142
173
  if (spec.warning) {
143
174
  warnings.push(spec.warning);
144
175
  }
176
+ if (!spec.config) {
177
+ continue;
178
+ }
145
179
  const pagePath = referencePagePath(ref.route);
146
180
  // `theme` is the one option Blume maps (a named theme replaces the
147
181
  // accent/radius layering); every other `scalar()` option is forwarded
@@ -250,7 +250,7 @@ const parseReference = async (
250
250
  const { document, warnings } = await parseSpec(
251
251
  reference.spec,
252
252
  ctx.projectRoot,
253
- options
253
+ { ...options, overlays: reference.overlays }
254
254
  );
255
255
  const extracted = extractOperations(document, reference.route);
256
256
  return {