blume 2.0.2 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (601) hide show
  1. package/AGENTS.md +1 -0
  2. package/CHANGELOG.md +113 -0
  3. package/README.md +1 -1
  4. package/dist/cli/{chunk-6k4ftwze.js → chunk-0gz36akr.js} +18 -2
  5. package/dist/cli/{chunk-6k4ftwze.js.map → chunk-0gz36akr.js.map} +3 -3
  6. package/dist/cli/{chunk-zp79m0ts.js → chunk-1n228xqb.js} +2 -2
  7. package/dist/cli/{chunk-ddndchfr.js → chunk-2f3ndkpw.js} +57 -29
  8. package/dist/cli/chunk-2f3ndkpw.js.map +14 -0
  9. package/dist/cli/{chunk-fsmrqk8a.js → chunk-2f7n4qzt.js} +8 -2
  10. package/dist/cli/{chunk-fsmrqk8a.js.map → chunk-2f7n4qzt.js.map} +3 -3
  11. package/dist/cli/{chunk-ppzjqwx2.js → chunk-2n4ps1b3.js} +17 -18
  12. package/dist/cli/{chunk-ppzjqwx2.js.map → chunk-2n4ps1b3.js.map} +3 -3
  13. package/dist/cli/{chunk-5r8g91qn.js → chunk-43zcf4gz.js} +398 -159
  14. package/dist/cli/chunk-43zcf4gz.js.map +36 -0
  15. package/dist/cli/chunk-4ejxbzee.js +39 -0
  16. package/dist/cli/chunk-4ejxbzee.js.map +10 -0
  17. package/dist/cli/{chunk-hr8ne106.js → chunk-4h8z9vv5.js} +296 -77
  18. package/dist/cli/chunk-4h8z9vv5.js.map +16 -0
  19. package/dist/cli/{chunk-k7pj68a8.js → chunk-5cg2rzgf.js} +18 -16
  20. package/dist/cli/{chunk-k7pj68a8.js.map → chunk-5cg2rzgf.js.map} +2 -2
  21. package/dist/cli/{chunk-h2ez8dzb.js → chunk-5ffywsmb.js} +4 -4
  22. package/dist/cli/{chunk-g698a744.js → chunk-5wj2qwn2.js} +5 -5
  23. package/dist/cli/chunk-6bm0xpgd.js +18 -0
  24. package/dist/cli/chunk-6bm0xpgd.js.map +10 -0
  25. package/dist/cli/{chunk-3yce002v.js → chunk-713mngdq.js} +15 -8
  26. package/dist/cli/chunk-713mngdq.js.map +14 -0
  27. package/dist/cli/{chunk-91ws1n6j.js → chunk-9geymqns.js} +17 -15
  28. package/dist/cli/{chunk-91ws1n6j.js.map → chunk-9geymqns.js.map} +2 -2
  29. package/dist/cli/{chunk-6dtt0zfn.js → chunk-9xr4dgee.js} +27 -18
  30. package/dist/cli/chunk-9xr4dgee.js.map +10 -0
  31. package/dist/cli/{chunk-sqw4ekg1.js → chunk-amrb6hgh.js} +2 -2
  32. package/dist/cli/chunk-cm89scdv.js +443 -0
  33. package/dist/cli/chunk-cm89scdv.js.map +11 -0
  34. package/dist/cli/{chunk-wdrt2k2v.js → chunk-cmtcnh9f.js} +90 -26
  35. package/dist/cli/chunk-cmtcnh9f.js.map +10 -0
  36. package/dist/cli/chunk-cy5bm0j0.js +104 -0
  37. package/dist/cli/chunk-cy5bm0j0.js.map +11 -0
  38. package/dist/cli/{chunk-7mbqtmgb.js → chunk-e7vvk5ya.js} +21 -12
  39. package/dist/cli/chunk-e7vvk5ya.js.map +10 -0
  40. package/dist/cli/{chunk-qkb5a8sa.js → chunk-g2z9x01v.js} +6 -98
  41. package/dist/cli/chunk-g2z9x01v.js.map +10 -0
  42. package/dist/cli/{chunk-273ygyr4.js → chunk-ga0pf4aj.js} +7 -7
  43. package/dist/cli/{chunk-273ygyr4.js.map → chunk-ga0pf4aj.js.map} +3 -3
  44. package/dist/cli/{chunk-h7k3nq3v.js → chunk-gp84za0h.js} +2 -2
  45. package/dist/cli/{chunk-hqp2ajnh.js → chunk-h2a48yhd.js} +195 -73
  46. package/dist/cli/chunk-h2a48yhd.js.map +36 -0
  47. package/dist/cli/{chunk-pbg5a4s3.js → chunk-jnwj5kjj.js} +71 -47
  48. package/dist/cli/chunk-jnwj5kjj.js.map +19 -0
  49. package/dist/cli/chunk-kwrg63tm.js +31 -0
  50. package/dist/cli/chunk-kwrg63tm.js.map +10 -0
  51. package/dist/cli/{chunk-8g8ytmgx.js → chunk-nd1821sk.js} +21 -19
  52. package/dist/cli/{chunk-8g8ytmgx.js.map → chunk-nd1821sk.js.map} +2 -2
  53. package/dist/cli/{chunk-w4bxdvsa.js → chunk-ntv4qrt7.js} +17 -15
  54. package/dist/cli/{chunk-w4bxdvsa.js.map → chunk-ntv4qrt7.js.map} +2 -2
  55. package/dist/cli/{chunk-bfwp9vp6.js → chunk-p804f3pe.js} +7 -7
  56. package/dist/cli/{chunk-4e9b9ra6.js → chunk-py62fewh.js} +6 -6
  57. package/dist/cli/{chunk-4e9b9ra6.js.map → chunk-py62fewh.js.map} +1 -1
  58. package/dist/cli/chunk-q3wxxq39.js +126 -0
  59. package/dist/cli/chunk-q3wxxq39.js.map +11 -0
  60. package/dist/cli/{chunk-j85scx15.js → chunk-rekqz4qg.js} +142 -46
  61. package/dist/cli/chunk-rekqz4qg.js.map +10 -0
  62. package/dist/cli/{chunk-00gs3wqs.js → chunk-ta27feev.js} +1 -1
  63. package/dist/cli/{chunk-v6ya5kcb.js → chunk-xkz5xbtv.js} +4031 -1132
  64. package/dist/cli/chunk-xkz5xbtv.js.map +213 -0
  65. package/dist/cli/{chunk-bbnwccaz.js → chunk-y4ykbark.js} +2 -2
  66. package/dist/cli/{chunk-n1yg3tj3.js → chunk-y5h4r2y0.js} +6 -6
  67. package/dist/cli/{chunk-n1yg3tj3.js.map → chunk-y5h4r2y0.js.map} +3 -3
  68. package/dist/cli/{chunk-nfcyttvj.js → chunk-yj17vs39.js} +9 -9
  69. package/dist/cli/{chunk-nfcyttvj.js.map → chunk-yj17vs39.js.map} +1 -1
  70. package/dist/cli/{chunk-7vtckvaw.js → chunk-yxmk4pec.js} +16 -14
  71. package/dist/cli/{chunk-7vtckvaw.js.map → chunk-yxmk4pec.js.map} +2 -2
  72. package/dist/cli/{chunk-gs7r695n.js → chunk-zccbjcj0.js} +22 -8
  73. package/dist/cli/{chunk-gs7r695n.js.map → chunk-zccbjcj0.js.map} +3 -3
  74. package/dist/cli/{chunk-xh43dwgw.js → chunk-zf748avv.js} +415 -225
  75. package/dist/cli/chunk-zf748avv.js.map +37 -0
  76. package/dist/cli/{chunk-d1v5rhy0.js → chunk-zhvd6adm.js} +16 -14
  77. package/dist/cli/{chunk-d1v5rhy0.js.map → chunk-zhvd6adm.js.map} +2 -2
  78. package/dist/cli/index.js +28 -20
  79. package/dist/cli/index.js.map +5 -5
  80. package/dist/types/ai/agent-surface.d.ts +8 -7
  81. package/dist/types/ai/ai-catalog.d.ts +7 -0
  82. package/dist/types/ai/ask-context.d.ts +22 -0
  83. package/dist/types/ai/ask.d.ts +271 -58
  84. package/dist/types/ai/index.d.ts +2 -2
  85. package/dist/types/ai/link-headers.d.ts +8 -1
  86. package/dist/types/ai/openapi-components.d.ts +5 -2
  87. package/dist/types/ai/relative-links.d.ts +9 -7
  88. package/dist/types/ai/site-skill.d.ts +26 -0
  89. package/dist/types/ai/skills.d.ts +20 -1
  90. package/dist/types/ai/tar.d.ts +1 -3
  91. package/dist/types/analytics/index.d.ts +2 -0
  92. package/dist/types/analytics/one-dollar-stats.d.ts +48 -0
  93. package/dist/types/analytics/schema.d.ts +14 -0
  94. package/dist/types/astro/integration.d.ts +11 -2
  95. package/dist/types/captcha/hcaptcha.d.ts +27 -0
  96. package/dist/types/captcha/index.d.ts +28 -0
  97. package/dist/types/captcha/schema.d.ts +29 -0
  98. package/dist/types/captcha/turnstile.d.ts +28 -0
  99. package/dist/types/components/content/api-field.d.ts +59 -0
  100. package/dist/types/components/content/api-page.d.ts +95 -0
  101. package/dist/types/components/layout/nav-utils.d.ts +26 -0
  102. package/dist/types/components/openapi/helpers.d.ts +137 -0
  103. package/dist/types/components/openapi/security.d.ts +85 -0
  104. package/dist/types/consent/ethyca.d.ts +48 -0
  105. package/dist/types/consent/index.d.ts +37 -0
  106. package/dist/types/consent/native.d.ts +28 -0
  107. package/dist/types/consent/osano.d.ts +42 -0
  108. package/dist/types/consent/schema.d.ts +110 -0
  109. package/dist/types/core/base-path.d.ts +21 -7
  110. package/dist/types/core/config-input.d.ts +196 -27
  111. package/dist/types/core/data.d.ts +49 -4
  112. package/dist/types/core/directive-diagnostics.d.ts +12 -0
  113. package/dist/types/core/footer.d.ts +8 -0
  114. package/dist/types/core/heading-markers.d.ts +5 -7
  115. package/dist/types/core/i18n-ui.d.ts +86 -0
  116. package/dist/types/core/i18n.d.ts +2 -0
  117. package/dist/types/core/includes.d.ts +8 -2
  118. package/dist/types/core/last-modified.d.ts +10 -0
  119. package/dist/types/core/localizable.d.ts +7 -0
  120. package/dist/types/core/meta.d.ts +8 -0
  121. package/dist/types/core/page-modes.d.ts +29 -0
  122. package/dist/types/core/redirect-patterns.d.ts +144 -0
  123. package/dist/types/core/related.d.ts +40 -0
  124. package/dist/types/core/schema.d.ts +558 -56
  125. package/dist/types/core/sources/lower.d.ts +29 -16
  126. package/dist/types/core/sources/normalize.d.ts +13 -1
  127. package/dist/types/core/sources/read.d.ts +5 -0
  128. package/dist/types/core/sources/watch.d.ts +12 -5
  129. package/dist/types/core/standard-schema.d.ts +5 -0
  130. package/dist/types/core/types.d.ts +1 -1
  131. package/dist/types/core/variables.d.ts +42 -0
  132. package/dist/types/deploy/artifacts.d.ts +6 -4
  133. package/dist/types/deploy/headers.d.ts +5 -0
  134. package/dist/types/deploy/node-headers.d.ts +13 -2
  135. package/dist/types/deploy/platforms/cloudflare.d.ts +5 -0
  136. package/dist/types/deploy/platforms/netlify.d.ts +7 -0
  137. package/dist/types/deploy/platforms/types.d.ts +8 -0
  138. package/dist/types/deploy/platforms/vercel.d.ts +6 -0
  139. package/dist/types/deploy/redirects.d.ts +61 -22
  140. package/dist/types/deploy/vercel-negotiation.d.ts +9 -0
  141. package/dist/types/markdown/code-title.d.ts +17 -0
  142. package/dist/types/markdown/directives.d.ts +62 -0
  143. package/dist/types/markdown/features.d.ts +24 -2
  144. package/dist/types/markdown/mdast.d.ts +63 -0
  145. package/dist/types/narration/build.d.ts +70 -0
  146. package/dist/types/narration/provider.d.ts +26 -0
  147. package/dist/types/narration/script.d.ts +154 -0
  148. package/dist/types/openapi/asyncapi.d.ts +4 -2
  149. package/dist/types/openapi/model.d.ts +12 -6
  150. package/dist/types/openapi/overlay.d.ts +54 -0
  151. package/dist/types/openapi/parse.d.ts +13 -0
  152. package/dist/types/openapi/references.d.ts +14 -4
  153. package/dist/types/ratelimit/cloudflare.d.ts +39 -0
  154. package/dist/types/ratelimit/index.d.ts +27 -0
  155. package/dist/types/ratelimit/memory.d.ts +36 -0
  156. package/dist/types/ratelimit/schema.d.ts +111 -0
  157. package/dist/types/ratelimit/upstash.d.ts +21 -0
  158. package/dist/types/ratelimit/wrangler.d.ts +23 -0
  159. package/dist/types/reference/asyncapi.d.ts +10 -10
  160. package/dist/types/reference/graphql.d.ts +13 -10
  161. package/dist/types/reference/openapi.d.ts +33 -12
  162. package/dist/types/reference/options.d.ts +45 -5
  163. package/dist/types/reference/scalar.d.ts +13 -0
  164. package/dist/types/reference/schema.d.ts +60 -36
  165. package/dist/types/search/documents.d.ts +12 -1
  166. package/dist/types/search/orama-index.d.ts +4 -0
  167. package/dist/types/seo/metatags.d.ts +20 -0
  168. package/docs/02-deployment.mdx +44 -6
  169. package/docs/03-upgrading.mdx +4 -4
  170. package/docs/04-migrating.mdx +1 -1
  171. package/docs/08-faq.mdx +1 -1
  172. package/docs/advanced/changelog.mdx +2 -2
  173. package/docs/advanced/custom-pages.mdx +3 -3
  174. package/docs/advanced/skills.mdx +14 -0
  175. package/docs/cli/audit.mdx +38 -2
  176. package/docs/cli/evals.mdx +3 -3
  177. package/docs/cli/index.mdx +1 -0
  178. package/docs/cli/translate.mdx +3 -3
  179. package/docs/cli/version.mdx +1 -1
  180. package/docs/configuration/analytics.mdx +22 -1
  181. package/docs/configuration/assistant.mdx +188 -36
  182. package/docs/configuration/consent.mdx +100 -0
  183. package/docs/configuration/customization.mdx +6 -4
  184. package/docs/configuration/index.mdx +63 -5
  185. package/docs/configuration/meta.ts +3 -0
  186. package/docs/configuration/narration.mdx +119 -0
  187. package/docs/configuration/rate-limiting.mdx +69 -0
  188. package/docs/configuration/search.mdx +30 -2
  189. package/docs/configuration/theming.mdx +3 -1
  190. package/docs/content/components.mdx +107 -1
  191. package/docs/content/frontmatter.mdx +77 -1
  192. package/docs/content/i18n.mdx +40 -3
  193. package/docs/content/includes.mdx +16 -0
  194. package/docs/content/index.mdx +4 -2
  195. package/docs/content/islands.mdx +1 -1
  196. package/docs/content/meta.mdx +4 -1
  197. package/docs/content/meta.ts +1 -0
  198. package/docs/content/navigation.mdx +28 -6
  199. package/docs/content/sources/contentful.mdx +32 -0
  200. package/docs/content/sources/custom.mdx +39 -0
  201. package/docs/content/sources/github-releases.mdx +31 -0
  202. package/docs/content/sources/index.mdx +91 -0
  203. package/docs/content/sources/meta.ts +16 -0
  204. package/docs/content/sources/notion.mdx +32 -0
  205. package/docs/content/sources/obsidian.mdx +37 -0
  206. package/docs/content/sources/payload.mdx +30 -0
  207. package/docs/content/sources/remote-mdx.mdx +40 -0
  208. package/docs/content/sources/sanity.mdx +29 -0
  209. package/docs/content/sources/strapi.mdx +31 -0
  210. package/docs/content/syntax.mdx +81 -2
  211. package/docs/content/variables.mdx +41 -0
  212. package/docs/content/versioning.mdx +1 -1
  213. package/docs/discoverability/agent-discovery.mdx +30 -3
  214. package/docs/discoverability/index.mdx +3 -2
  215. package/docs/discoverability/llms-txt.mdx +1 -1
  216. package/docs/discoverability/markdown.mdx +2 -2
  217. package/docs/discoverability/metadata.mdx +17 -0
  218. package/docs/discoverability/open-graph.mdx +5 -3
  219. package/docs/discoverability/rss.mdx +1 -1
  220. package/docs/references/api-pages.mdx +74 -0
  221. package/docs/references/asyncapi.mdx +2 -2
  222. package/docs/references/graphql.mdx +3 -3
  223. package/docs/references/meta.ts +1 -1
  224. package/docs/references/openapi.mdx +106 -5
  225. package/docs/references/scalar.mdx +3 -1
  226. package/package.json +36 -1
  227. package/skills/blume/SKILL.md +3 -0
  228. package/skills/blume-migrate/SKILL.md +5 -5
  229. package/skills/blume-migrate/references/docusaurus.md +3 -3
  230. package/skills/blume-migrate/references/fumadocs.md +1 -1
  231. package/skills/blume-migrate/references/mintlify.md +37 -19
  232. package/skills/blume-migrate/references/nextra.md +3 -3
  233. package/skills/blume-migrate/references/starlight.md +2 -1
  234. package/skills/blume-migrate/scripts/mintlify-codemod.mjs +67 -16
  235. package/skills/blume-write-skill/SKILL.md +82 -0
  236. package/src/ai/agent-readability.ts +4 -3
  237. package/src/ai/agent-surface.ts +9 -7
  238. package/src/ai/ai-catalog.ts +12 -5
  239. package/src/ai/api/handlers.ts +2 -2
  240. package/src/ai/api/spec.ts +2 -2
  241. package/src/ai/api-catalog.ts +6 -2
  242. package/src/ai/ask-context.ts +73 -20
  243. package/src/ai/ask-data.ts +6 -0
  244. package/src/ai/ask-tools.ts +142 -0
  245. package/src/ai/ask.ts +354 -76
  246. package/src/ai/changelog-markdown.ts +2 -2
  247. package/src/ai/component-markdown.ts +75 -0
  248. package/src/ai/cors.ts +2 -0
  249. package/src/ai/index.ts +12 -0
  250. package/src/ai/link-headers.ts +14 -5
  251. package/src/ai/llms.ts +24 -9
  252. package/src/ai/markdown.ts +2 -1
  253. package/src/ai/mcp/data.ts +6 -0
  254. package/src/ai/mcp/discovery.ts +2 -2
  255. package/src/ai/mcp/query.ts +2 -2
  256. package/src/ai/mcp/server.ts +2 -2
  257. package/src/ai/openapi-components.ts +31 -9
  258. package/src/ai/relative-links.ts +42 -26
  259. package/src/ai/serializers.ts +2 -1
  260. package/src/ai/site-skill.ts +274 -0
  261. package/src/ai/skills.ts +49 -3
  262. package/src/ai/tar.ts +139 -12
  263. package/src/ai/write-skill.ts +97 -0
  264. package/src/analytics/head.ts +4 -0
  265. package/src/analytics/index.ts +5 -0
  266. package/src/analytics/one-dollar-stats.ts +86 -0
  267. package/src/analytics/schema.ts +2 -0
  268. package/src/astro/generate.ts +133 -19
  269. package/src/astro/include-hmr.ts +31 -12
  270. package/src/astro/include-refresh.ts +19 -8
  271. package/src/astro/index.ts +1 -0
  272. package/src/astro/integration.ts +143 -14
  273. package/src/astro/module-types.ts +6 -0
  274. package/src/astro/runtime-modules.ts +28 -13
  275. package/src/astro/templates.ts +364 -67
  276. package/src/astro/variables.ts +31 -0
  277. package/src/audit/catalog.ts +19 -0
  278. package/src/audit/checks/contrast.ts +183 -0
  279. package/src/audit/checks/i18n.ts +2 -3
  280. package/src/audit/checks/indexability.ts +2 -3
  281. package/src/audit/checks/links.ts +13 -13
  282. package/src/audit/checks/sitemap.ts +2 -3
  283. package/src/audit/graph.ts +3 -1
  284. package/src/audit/redirects.ts +46 -15
  285. package/src/audit/run.ts +14 -9
  286. package/src/audit/snapshot.ts +5 -0
  287. package/src/audit/types.ts +2 -1
  288. package/src/blume-modules.d.ts +7 -0
  289. package/src/captcha/client.ts +152 -0
  290. package/src/captcha/hcaptcha.ts +36 -0
  291. package/src/captcha/index.ts +28 -0
  292. package/src/captcha/schema.ts +32 -0
  293. package/src/captcha/turnstile.ts +38 -0
  294. package/src/captcha/verify.ts +71 -0
  295. package/src/cli/args.ts +32 -0
  296. package/src/cli/command-meta.ts +5 -0
  297. package/src/cli/commands/audit.ts +4 -1
  298. package/src/cli/commands/doctor.ts +25 -17
  299. package/src/cli/commands/eval.ts +19 -11
  300. package/src/cli/commands/skill.ts +96 -0
  301. package/src/cli/commands/translate.ts +9 -12
  302. package/src/cli/commands/validate.ts +3 -1
  303. package/src/cli/dev-lock.ts +157 -37
  304. package/src/cli/index.ts +5 -0
  305. package/src/cli/report-format.ts +11 -6
  306. package/src/cli/required-secrets.ts +71 -29
  307. package/src/components/BlumePage.astro +2 -0
  308. package/src/components/colors.ts +19 -0
  309. package/src/components/content/ApiEndpoint.astro +18 -0
  310. package/src/components/content/ApiField.astro +79 -0
  311. package/src/components/content/ApiPlayground.astro +62 -0
  312. package/src/components/content/ApiRail.astro +19 -0
  313. package/src/components/content/Badge.astro +5 -3
  314. package/src/components/content/Color.astro +1 -0
  315. package/src/components/content/Component.astro +2 -2
  316. package/src/components/content/FileTree.astro +1 -0
  317. package/src/components/content/GithubInfo.astro +1 -0
  318. package/src/components/content/Panel.astro +1 -0
  319. package/src/components/content/ParamField.astro +25 -0
  320. package/src/components/content/RequestExample.astro +17 -0
  321. package/src/components/content/ResponseExample.astro +17 -0
  322. package/src/components/content/ResponseField.astro +32 -0
  323. package/src/components/content/Step.astro +1 -0
  324. package/src/components/content/Tab.astro +0 -1
  325. package/src/components/content/Tabs.astro +4 -0
  326. package/src/components/content/Tile.astro +1 -0
  327. package/src/components/content/Tooltip.astro +1 -0
  328. package/src/components/content/Tree.astro +1 -0
  329. package/src/components/content/TypeTable.astro +4 -1
  330. package/src/components/content/View.astro +21 -0
  331. package/src/components/content/ViewSwitcher.astro +168 -0
  332. package/src/components/content/api-field.ts +100 -0
  333. package/src/components/content/api-page.ts +224 -0
  334. package/src/components/content/badge-color.ts +5 -2
  335. package/src/components/content/base-href.ts +13 -36
  336. package/src/components/content/view-switcher.ts +71 -0
  337. package/src/components/index.ts +2 -0
  338. package/src/components/islands/Assistant.astro +10 -1
  339. package/src/components/islands/assistant.tsx +125 -18
  340. package/src/components/islands/base-path.ts +47 -10
  341. package/src/components/islands/code-question.ts +62 -0
  342. package/src/components/islands/hooks.ts +145 -40
  343. package/src/components/islands/support-link.ts +68 -0
  344. package/src/components/islands/webmcp.ts +4 -2
  345. package/src/components/layout/Analytics.astro +11 -2
  346. package/src/components/layout/Banner.astro +14 -10
  347. package/src/components/layout/Breadcrumbs.astro +11 -4
  348. package/src/components/layout/ConsentBanner.astro +62 -0
  349. package/src/components/layout/ConsentHead.astro +38 -0
  350. package/src/components/layout/Directory.astro +94 -0
  351. package/src/components/layout/DiscoveryLinks.astro +9 -5
  352. package/src/components/layout/DrawerSwitchers.astro +51 -0
  353. package/src/components/layout/Empty.astro +2 -2
  354. package/src/components/layout/Header.astro +124 -82
  355. package/src/components/layout/LanguageSwitcher.astro +50 -17
  356. package/src/components/layout/NarrationPlayer.astro +1085 -0
  357. package/src/components/layout/NavSelector.astro +35 -9
  358. package/src/components/layout/NavTabMenu.astro +3 -3
  359. package/src/components/layout/NavTree.astro +16 -6
  360. package/src/components/layout/PageActions.astro +4 -3
  361. package/src/components/layout/PageFeedback.astro +54 -49
  362. package/src/components/layout/PageLayout.astro +78 -9
  363. package/src/components/layout/Pagination.astro +3 -3
  364. package/src/components/layout/ReferenceLayout.astro +17 -6
  365. package/src/components/layout/RelatedPages.astro +42 -0
  366. package/src/components/layout/RootLayout.astro +313 -50
  367. package/src/components/layout/Search.astro +64 -30
  368. package/src/components/layout/SiteFooter.astro +124 -0
  369. package/src/components/layout/SiteMetatags.astro +16 -0
  370. package/src/components/layout/VersionBanner.astro +2 -2
  371. package/src/components/layout/analytics-client.ts +12 -0
  372. package/src/components/layout/consent/native.ts +79 -0
  373. package/src/components/layout/consent/types.ts +37 -0
  374. package/src/components/layout/footer-links.ts +53 -0
  375. package/src/components/layout/footer-socials.ts +87 -0
  376. package/src/components/layout/locale-redirect.ts +107 -0
  377. package/src/components/layout/nav-utils.ts +83 -0
  378. package/src/components/layout/page-feedback.ts +96 -0
  379. package/src/components/layout/search/analytics.ts +98 -0
  380. package/src/components/layout/search/flexsearch.ts +17 -5
  381. package/src/components/layout/search/orama-cloud.ts +26 -14
  382. package/src/components/layout/search/types.ts +20 -0
  383. package/src/components/layout/search/typesense.ts +5 -1
  384. package/src/components/layout/toc-active.ts +41 -0
  385. package/src/components/layout/toc-element.ts +8 -14
  386. package/src/components/openapi/ApiTagOperations.astro +2 -2
  387. package/src/components/openapi/AsyncApiOperation.astro +7 -4
  388. package/src/components/openapi/GraphqlChip.astro +2 -2
  389. package/src/components/openapi/Operation.astro +163 -13
  390. package/src/components/openapi/Playground.astro +4 -4
  391. package/src/components/openapi/RequestBody.astro +6 -4
  392. package/src/components/openapi/RequestPanel.astro +63 -8
  393. package/src/components/openapi/Responses.astro +6 -4
  394. package/src/components/openapi/SchemaTable.astro +7 -0
  395. package/src/components/openapi/async-snippets.ts +4 -4
  396. package/src/components/openapi/async.ts +38 -6
  397. package/src/components/openapi/code-samples.ts +97 -0
  398. package/src/components/openapi/helpers.ts +19 -9
  399. package/src/components/openapi/message-composer.ts +6 -1
  400. package/src/components/openapi/message.ts +17 -2
  401. package/src/components/openapi/operation-model.ts +81 -11
  402. package/src/components/openapi/panel.ts +4 -2
  403. package/src/components/openapi/param-style.ts +181 -0
  404. package/src/components/openapi/playground-client.ts +73 -16
  405. package/src/components/openapi/request.ts +190 -26
  406. package/src/components/openapi/schema-tree.ts +30 -24
  407. package/src/components/openapi/security.ts +6 -3
  408. package/src/components/openapi/snippets.ts +953 -13
  409. package/src/components/openapi/ws-client.ts +18 -2
  410. package/src/components/props.ts +6 -0
  411. package/src/consent/client.ts +95 -0
  412. package/src/consent/clients.ts +19 -0
  413. package/src/consent/ethyca.ts +71 -0
  414. package/src/consent/head.ts +24 -0
  415. package/src/consent/index.ts +37 -0
  416. package/src/consent/init.ts +29 -0
  417. package/src/consent/native.ts +36 -0
  418. package/src/consent/osano.ts +59 -0
  419. package/src/consent/schema.ts +30 -0
  420. package/src/core/base-path.ts +62 -16
  421. package/src/core/builtin-tags.ts +5 -0
  422. package/src/core/client-address.ts +19 -0
  423. package/src/core/config-input.ts +203 -27
  424. package/src/core/data.ts +44 -4
  425. package/src/core/diagnostics.ts +5 -2
  426. package/src/core/directive-diagnostics.ts +99 -0
  427. package/src/core/footer.ts +26 -0
  428. package/src/core/frontmatter.ts +21 -18
  429. package/src/core/graph.ts +39 -36
  430. package/src/core/heading-markers.ts +5 -18
  431. package/src/core/i18n-ui.ts +82 -2
  432. package/src/core/i18n.ts +31 -3
  433. package/src/core/includes.ts +20 -3
  434. package/src/core/last-modified.ts +25 -3
  435. package/src/core/links.ts +16 -2
  436. package/src/core/locale-links.ts +5 -1
  437. package/src/core/localizable.ts +26 -0
  438. package/src/core/meta.ts +33 -19
  439. package/src/core/navigation.ts +71 -20
  440. package/src/core/page-modes.ts +56 -0
  441. package/src/core/project-graph.ts +138 -10
  442. package/src/core/redirect-patterns.ts +491 -0
  443. package/src/core/related.ts +131 -0
  444. package/src/core/schema.ts +342 -68
  445. package/src/core/sources/contentful-rich-text.ts +27 -20
  446. package/src/core/sources/filesystem.ts +20 -2
  447. package/src/core/sources/github-releases.ts +25 -6
  448. package/src/core/sources/lexical.ts +23 -18
  449. package/src/core/sources/lower.ts +201 -34
  450. package/src/core/sources/mdx-remote.ts +51 -16
  451. package/src/core/sources/normalize.ts +327 -91
  452. package/src/core/sources/notion.ts +52 -25
  453. package/src/core/sources/obsidian.ts +23 -6
  454. package/src/core/sources/portable-text.ts +38 -21
  455. package/src/core/sources/read.ts +8 -3
  456. package/src/core/sources/strapi-blocks.ts +20 -16
  457. package/src/core/sources/watch.ts +20 -7
  458. package/src/core/standard-schema.ts +10 -6
  459. package/src/core/types.ts +1 -1
  460. package/src/core/ui-packs/ar.ts +43 -0
  461. package/src/core/ui-packs/bg.ts +45 -0
  462. package/src/core/ui-packs/bn.ts +43 -0
  463. package/src/core/ui-packs/ca.ts +46 -0
  464. package/src/core/ui-packs/cs.ts +44 -0
  465. package/src/core/ui-packs/da.ts +45 -0
  466. package/src/core/ui-packs/de.ts +46 -0
  467. package/src/core/ui-packs/el.ts +45 -0
  468. package/src/core/ui-packs/es.ts +46 -0
  469. package/src/core/ui-packs/fa.ts +45 -0
  470. package/src/core/ui-packs/fi.ts +45 -0
  471. package/src/core/ui-packs/fr.ts +46 -0
  472. package/src/core/ui-packs/he.ts +43 -0
  473. package/src/core/ui-packs/hi.ts +44 -0
  474. package/src/core/ui-packs/hr.ts +45 -0
  475. package/src/core/ui-packs/hu.ts +44 -0
  476. package/src/core/ui-packs/id.ts +45 -0
  477. package/src/core/ui-packs/it.ts +45 -0
  478. package/src/core/ui-packs/ja.ts +45 -0
  479. package/src/core/ui-packs/ko.ts +44 -0
  480. package/src/core/ui-packs/nl.ts +46 -0
  481. package/src/core/ui-packs/no.ts +44 -0
  482. package/src/core/ui-packs/pl.ts +45 -0
  483. package/src/core/ui-packs/pt-br.ts +46 -0
  484. package/src/core/ui-packs/pt.ts +45 -0
  485. package/src/core/ui-packs/ro.ts +45 -0
  486. package/src/core/ui-packs/ru.ts +45 -0
  487. package/src/core/ui-packs/sk.ts +44 -0
  488. package/src/core/ui-packs/sr.ts +45 -0
  489. package/src/core/ui-packs/sv.ts +44 -0
  490. package/src/core/ui-packs/th.ts +43 -0
  491. package/src/core/ui-packs/tr.ts +45 -0
  492. package/src/core/ui-packs/uk.ts +45 -0
  493. package/src/core/ui-packs/vi.ts +45 -0
  494. package/src/core/ui-packs/zh-tw.ts +43 -0
  495. package/src/core/ui-packs/zh.ts +43 -0
  496. package/src/core/variables.ts +103 -0
  497. package/src/core/version-cut.ts +52 -15
  498. package/src/deploy/artifacts.ts +104 -14
  499. package/src/deploy/cloudflare-negotiation.ts +39 -17
  500. package/src/deploy/headers.ts +10 -5
  501. package/src/deploy/node-headers.ts +64 -10
  502. package/src/deploy/platforms/cloudflare.ts +44 -3
  503. package/src/deploy/platforms/netlify.ts +34 -1
  504. package/src/deploy/platforms/node.ts +1 -0
  505. package/src/deploy/platforms/static.ts +1 -0
  506. package/src/deploy/platforms/types.ts +8 -0
  507. package/src/deploy/platforms/vercel.ts +38 -2
  508. package/src/deploy/redirects.ts +139 -36
  509. package/src/deploy/robots.ts +2 -2
  510. package/src/deploy/rss.ts +4 -3
  511. package/src/deploy/sitemap.ts +7 -5
  512. package/src/deploy/vercel-negotiation.ts +36 -0
  513. package/src/markdown/api-rail.ts +259 -0
  514. package/src/markdown/base-links.ts +34 -36
  515. package/src/markdown/code-title.ts +35 -9
  516. package/src/markdown/directives.ts +242 -36
  517. package/src/markdown/features.ts +20 -2
  518. package/src/markdown/fence-meta.ts +2 -0
  519. package/src/markdown/include.ts +11 -1
  520. package/src/markdown/index.ts +35 -11
  521. package/src/markdown/math.ts +4 -4
  522. package/src/markdown/mdast.ts +5 -2
  523. package/src/markdown/relative-links.ts +12 -4
  524. package/src/markdown/variables.ts +56 -0
  525. package/src/markdown/views.ts +82 -0
  526. package/src/narration/build.ts +412 -0
  527. package/src/narration/player.ts +249 -0
  528. package/src/narration/provider.ts +29 -0
  529. package/src/narration/script.ts +592 -0
  530. package/src/og/card.ts +136 -11
  531. package/src/og/derive.ts +41 -31
  532. package/src/og/index.ts +1 -0
  533. package/src/openapi/asyncapi.ts +4 -2
  534. package/src/openapi/model.ts +77 -36
  535. package/src/openapi/overlay.ts +256 -0
  536. package/src/openapi/parse.ts +55 -2
  537. package/src/openapi/proxy.ts +63 -10
  538. package/src/openapi/references.ts +21 -5
  539. package/src/openapi/render-mdx.ts +46 -3
  540. package/src/openapi/scalar.ts +40 -6
  541. package/src/openapi/source.ts +1 -1
  542. package/src/ratelimit/cloudflare.ts +54 -0
  543. package/src/ratelimit/index.ts +30 -0
  544. package/src/ratelimit/memory.ts +45 -0
  545. package/src/ratelimit/runtime.ts +229 -0
  546. package/src/ratelimit/schema.ts +28 -0
  547. package/src/ratelimit/upstash.ts +30 -0
  548. package/src/ratelimit/wrangler.ts +70 -0
  549. package/src/reference/asyncapi.ts +2 -2
  550. package/src/reference/graphql.ts +5 -2
  551. package/src/reference/openapi.ts +21 -7
  552. package/src/reference/options.ts +51 -7
  553. package/src/reference/scalar.ts +33 -8
  554. package/src/registry/eject.ts +201 -28
  555. package/src/registry/registry.ts +7 -0
  556. package/src/search/adapters/version-scope.ts +30 -0
  557. package/src/search/documents.ts +93 -28
  558. package/src/search/orama-index.ts +28 -3
  559. package/src/search/pagefind-ranking.ts +39 -0
  560. package/src/search/popular.ts +2 -1
  561. package/src/search/sync/algolia.ts +15 -4
  562. package/src/search/sync/orama-cloud.ts +3 -0
  563. package/src/search/sync/typesense.ts +5 -0
  564. package/src/seo/jsonld.ts +7 -3
  565. package/src/seo/metatags.ts +67 -0
  566. package/src/theme/chrome-icons.ts +1 -0
  567. package/src/theme/color.ts +500 -0
  568. package/src/theme/entry.ts +56 -3
  569. package/src/theme/palette.ts +121 -45
  570. package/src/translate/meta.ts +68 -24
  571. package/src/translate/prompts.ts +1 -0
  572. package/src/translate/run.ts +3 -3
  573. package/src/translate/validate.ts +12 -3
  574. package/src/translate/work-list.ts +56 -17
  575. package/src/upgrade/upgrade.ts +4 -171
  576. package/dist/cli/chunk-3yce002v.js.map +0 -14
  577. package/dist/cli/chunk-5r8g91qn.js.map +0 -34
  578. package/dist/cli/chunk-6dtt0zfn.js.map +0 -10
  579. package/dist/cli/chunk-7mbqtmgb.js.map +0 -10
  580. package/dist/cli/chunk-ddndchfr.js.map +0 -14
  581. package/dist/cli/chunk-esh98wmb.js +0 -23
  582. package/dist/cli/chunk-esh98wmb.js.map +0 -10
  583. package/dist/cli/chunk-hqp2ajnh.js.map +0 -35
  584. package/dist/cli/chunk-hr8ne106.js.map +0 -13
  585. package/dist/cli/chunk-j85scx15.js.map +0 -10
  586. package/dist/cli/chunk-mqc662a6.js +0 -75
  587. package/dist/cli/chunk-mqc662a6.js.map +0 -11
  588. package/dist/cli/chunk-pbg5a4s3.js.map +0 -19
  589. package/dist/cli/chunk-qkb5a8sa.js.map +0 -10
  590. package/dist/cli/chunk-v6ya5kcb.js.map +0 -189
  591. package/dist/cli/chunk-wdrt2k2v.js.map +0 -10
  592. package/dist/cli/chunk-xh43dwgw.js.map +0 -36
  593. package/docs/content/sources.mdx +0 -338
  594. /package/dist/cli/{chunk-zp79m0ts.js.map → chunk-1n228xqb.js.map} +0 -0
  595. /package/dist/cli/{chunk-h2ez8dzb.js.map → chunk-5ffywsmb.js.map} +0 -0
  596. /package/dist/cli/{chunk-g698a744.js.map → chunk-5wj2qwn2.js.map} +0 -0
  597. /package/dist/cli/{chunk-sqw4ekg1.js.map → chunk-amrb6hgh.js.map} +0 -0
  598. /package/dist/cli/{chunk-h7k3nq3v.js.map → chunk-gp84za0h.js.map} +0 -0
  599. /package/dist/cli/{chunk-bfwp9vp6.js.map → chunk-p804f3pe.js.map} +0 -0
  600. /package/dist/cli/{chunk-00gs3wqs.js.map → chunk-ta27feev.js.map} +0 -0
  601. /package/dist/cli/{chunk-bbnwccaz.js.map → chunk-y4ykbark.js.map} +0 -0
@@ -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)) {
@@ -12,25 +12,52 @@
12
12
  * network and stays safe to bundle into the generated endpoint file.
13
13
  */
14
14
 
15
+ import { PROXY_HEADERS_HEADER } from "../components/openapi/request.ts";
15
16
  import { readCappedBody } from "../core/request-body.ts";
16
17
 
17
18
  /**
18
- * Request headers never forwarded upstream: hop-by-hop headers describe this
19
- * connection (not the upstream one), `host`/`origin`/`referer` would leak or
20
- * misattribute the docs site, and `cookie` would forward reader credentials
21
- * to an arbitrary target. `accept-encoding`/`content-length` are recomputed
22
- * by the runtime's own fetch.
19
+ * Request headers never forwarded upstream, even when the playground names
20
+ * them: hop-by-hop headers describe this connection (not the upstream one),
21
+ * `host`/`origin`/`referer` would leak or misattribute the docs site, `cookie`
22
+ * would forward reader credentials to an arbitrary target, and the rest are
23
+ * set or rewritten by the platform in front of the docs server — the
24
+ * reader's address, the edge's own identity — whatever the browser sent.
25
+ * `accept-encoding`/`content-length` are recomputed by the runtime's own
26
+ * fetch.
23
27
  */
24
28
  const REQUEST_DROP = {
25
29
  "accept-encoding": true,
30
+ "cdn-loop": true,
26
31
  connection: true,
27
32
  "content-length": true,
28
33
  cookie: true,
34
+ forwarded: true,
29
35
  host: true,
36
+ "keep-alive": true,
30
37
  origin: true,
38
+ "proxy-authorization": true,
31
39
  referer: true,
40
+ te: true,
41
+ trailer: true,
42
+ "transfer-encoding": true,
43
+ "true-client-ip": true,
44
+ upgrade: true,
45
+ via: true,
46
+ "x-client-ip": true,
47
+ "x-real-ip": true,
32
48
  } satisfies Record<string, true>;
33
49
 
50
+ /**
51
+ * Platform header families, dropped like {@link REQUEST_DROP}: Cloudflare's
52
+ * (`cf-connecting-ip`, the Access `cf-access-jwt-assertion`), the
53
+ * `x-forwarded-*` set, Vercel's (`x-vercel-oidc-token`), Netlify's, Fly's,
54
+ * and AWS load balancers' (`x-amzn-oidc-data`).
55
+ */
56
+ const PLATFORM_HEADER = /^(?:cf-|x-forwarded-|x-vercel-|x-nf-|fly-|x-amzn-)/u;
57
+
58
+ /** The {@link PROXY_HEADERS_HEADER} name as `Headers` iterates it. */
59
+ const DECLARED = PROXY_HEADERS_HEADER.toLowerCase();
60
+
34
61
  /**
35
62
  * Upstream response headers never returned to the browser: the runtime's
36
63
  * fetch already decoded the body (so `content-encoding`/`content-length` no
@@ -99,20 +126,45 @@ const DOCUMENT_TYPE =
99
126
  const badRequest = (error: string): Response =>
100
127
  Response.json({ error }, { status: 400 });
101
128
 
102
- /** Copy headers, skipping the given denylist (names are already lowercase). */
129
+ /**
130
+ * Copy headers, skipping the given denylist and any name `keep` rejects
131
+ * (names are already lowercase).
132
+ */
103
133
  const filterHeaders = (
104
134
  source: Headers,
105
- drop: Record<string, true>
135
+ drop: Record<string, true>,
136
+ keep: (name: string) => boolean = () => true
106
137
  ): Headers => {
107
138
  const headers = new Headers();
108
139
  for (const [name, value] of source) {
109
- if (!drop[name]) {
140
+ if (!drop[name] && keep(name)) {
110
141
  headers.set(name, value);
111
142
  }
112
143
  }
113
144
  return headers;
114
145
  };
115
146
 
147
+ /**
148
+ * The headers to send upstream: only those the playground set itself, which
149
+ * it names in {@link PROXY_HEADERS_HEADER}. Forwarding everything else minus
150
+ * a denylist would hand the documented API whatever the browser and the
151
+ * platform attach on their own — the HTTP Basic credentials of a docs site
152
+ * behind a password (a same-origin fetch with no `Authorization` of its own
153
+ * carries them), a Cloudflare Access assertion, a Vercel OIDC token.
154
+ */
155
+ const forwardedHeaders = (source: Headers): Headers => {
156
+ const named = new Set(
157
+ (source.get(DECLARED) ?? "")
158
+ .split(",")
159
+ .map((name) => name.trim().toLowerCase())
160
+ );
161
+ return filterHeaders(
162
+ source,
163
+ REQUEST_DROP,
164
+ (name) => named.has(name) && !PLATFORM_HEADER.test(name)
165
+ );
166
+ };
167
+
116
168
  /** A 403 for a target no configured spec declares as one of its servers. */
117
169
  const forbidden = (origin: string): Response =>
118
170
  Response.json(
@@ -186,7 +238,8 @@ const followUpstream = async (args: {
186
238
  /**
187
239
  * Build the `/_api-proxy` fetch handler. The client sends its REAL method,
188
240
  * headers, and body to `?url=<encodeURIComponent(target)>`; the handler
189
- * forwards them (minus {@link REQUEST_DROP}) and mirrors the upstream response
241
+ * forwards the method, the body, and the headers the client names (see
242
+ * {@link forwardedHeaders}), and mirrors the upstream response
190
243
  * (minus {@link RESPONSE_DROP}) with an `x-blume-proxy` marker. An unreachable
191
244
  * upstream is a 502 with a JSON `error`.
192
245
  *
@@ -252,7 +305,7 @@ export const createPlaygroundProxyHandler = (
252
305
  allowed,
253
306
  body,
254
307
  fetchImpl,
255
- headers: filterHeaders(request.headers, REQUEST_DROP),
308
+ headers: forwardedHeaders(request.headers),
256
309
  hop: 0,
257
310
  method: request.method,
258
311
  signal: AbortSignal.timeout(timeoutMs),
@@ -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);
@@ -126,6 +126,45 @@ const mdxSafe = (text: string): string => {
126
126
  return out + escapeProse(text.slice(cursor));
127
127
  };
128
128
 
129
+ // A fence opener: up to three columns of indentation, then a run of three or
130
+ // more backticks or tildes.
131
+ const FENCE_OPEN = /^ {0,3}(?<fence>`{3,}|~{3,})/u;
132
+
133
+ /**
134
+ * Close a fenced code block the text leaves open. An unclosed fence runs to
135
+ * the end of the document, so the component appended after a description
136
+ * would render as code, and the page would lose its parameters, responses,
137
+ * and playground. Only the last top-level block can run that far — a fence in
138
+ * a quote or list closes with its container — and it's read from the same
139
+ * `<`-masked parse as `mdxSafe`, since the emitted MDX has no HTML blocks to
140
+ * hide a fence in.
141
+ */
142
+ const closeOpenFence = (text: string): string => {
143
+ const last = fromMarkdown(text.replaceAll("<", HTML_MASK)).children.at(-1);
144
+ if (last?.type !== "code") {
145
+ return text;
146
+ }
147
+ // fromMarkdown always stamps positions; 0 is an unreachable guard.
148
+ const block = text.slice(last.position?.start.offset ?? 0);
149
+ const fence = FENCE_OPEN.exec(block)?.groups?.fence;
150
+ // No fence: indented code, which MDX reads as a paragraph.
151
+ if (!fence) {
152
+ return text;
153
+ }
154
+ const lines = block.split("\n");
155
+ const closer = new RegExp(
156
+ `^ {0,3}${fence[0]}{${fence.length},}[ \\t\\r]*$`,
157
+ "u"
158
+ );
159
+ return lines.length > 1 && closer.test(lines.at(-1) ?? "")
160
+ ? text
161
+ : `${text}\n${fence}`;
162
+ };
163
+
164
+ /** Spec prose as MDX that is safe to follow with a component. */
165
+ const descriptionMdx = (text: string): string =>
166
+ mdxSafe(closeOpenFence(text.trim()));
167
+
129
168
  /**
130
169
  * Frontmatter emitted for one operation or overview page. Boolean flags are
131
170
  * assigned only when set, so absent keys stay absent in the staged MDX.
@@ -231,7 +270,8 @@ const operationDescription = (
231
270
  // method (see `extractGraphqlOperations`).
232
271
  subject = `${operation.path} ${GRAPHQL_MEMBER_PHRASES[operation.method as GraphqlMember]}`;
233
272
  } else {
234
- 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"}`;
235
275
  }
236
276
  const suffix = `Reference for the ${subject} in the ${apiPhrase(spec)}.`;
237
277
  // Room for the joining space and the period `asSentence` may add, so a
@@ -249,7 +289,7 @@ const operationDescription = (
249
289
  /** Prepend a markdown description (if any) above a component invocation. */
250
290
  const withDescription = (description: string, component: string): string =>
251
291
  description.trim()
252
- ? `${mdxSafe(description.trim())}\n\n${component}`
292
+ ? `${descriptionMdx(description)}\n\n${component}`
253
293
  : component;
254
294
 
255
295
  export const operationMdx = (
@@ -309,6 +349,9 @@ export const operationMdx = (
309
349
  sidebar.badge = method;
310
350
  }
311
351
  const tags = graphql ? [operation.tag] : [operation.tag, method];
352
+ if (operation.webhook) {
353
+ tags.push("Webhook");
354
+ }
312
355
  return {
313
356
  body: withDescription(
314
357
  description,
@@ -365,7 +408,7 @@ export const overviewMdx = (
365
408
  continue;
366
409
  }
367
410
  const description = tag.description.trim()
368
- ? [mdxSafe(tag.description.trim())]
411
+ ? [descriptionMdx(tag.description)]
369
412
  : [];
370
413
  tagSections.push(
371
414
  [
@@ -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