blume 2.0.1 → 2.0.3

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 (492) hide show
  1. package/CHANGELOG.md +147 -0
  2. package/dist/cli/{chunk-qwsrynx5.js → chunk-1d7ve1dm.js} +38 -19
  3. package/dist/cli/{chunk-qwsrynx5.js.map → chunk-1d7ve1dm.js.map} +4 -4
  4. package/dist/cli/{chunk-s6jhgk0q.js → chunk-2eytanqx.js} +2 -2
  5. package/dist/cli/{chunk-kdp5q7ke.js → chunk-2hsdwb9n.js} +18 -19
  6. package/dist/cli/{chunk-kdp5q7ke.js.map → chunk-2hsdwb9n.js.map} +2 -2
  7. package/dist/cli/{chunk-hdpx1tax.js → chunk-2z928egk.js} +5 -5
  8. package/dist/cli/{chunk-jwyddg7y.js → chunk-35d4wj9f.js} +30 -24
  9. package/dist/cli/chunk-35d4wj9f.js.map +15 -0
  10. package/dist/cli/{chunk-ah61y8py.js → chunk-364znk6q.js} +2 -2
  11. package/dist/cli/{chunk-jts8mvcz.js → chunk-3em5wd2y.js} +25 -6
  12. package/dist/cli/{chunk-jts8mvcz.js.map → chunk-3em5wd2y.js.map} +3 -3
  13. package/dist/cli/{chunk-s1p84fyh.js → chunk-5f86nr5m.js} +63 -22
  14. package/dist/cli/chunk-5f86nr5m.js.map +11 -0
  15. package/dist/cli/{chunk-2hn4b8z7.js → chunk-5m5nmvyq.js} +170 -76
  16. package/dist/cli/chunk-5m5nmvyq.js.map +13 -0
  17. package/dist/cli/{chunk-epjnccmv.js → chunk-5xvm6tfj.js} +18 -15
  18. package/dist/cli/chunk-5xvm6tfj.js.map +10 -0
  19. package/dist/cli/{chunk-fz5wtpmh.js → chunk-6dsbexzp.js} +18 -15
  20. package/dist/cli/chunk-6dsbexzp.js.map +10 -0
  21. package/dist/cli/{chunk-vtk4a6dg.js → chunk-8ktnccpt.js} +1 -1
  22. package/dist/cli/{chunk-6hsn950k.js → chunk-9t7a85s3.js} +178 -48
  23. package/dist/cli/chunk-9t7a85s3.js.map +10 -0
  24. package/dist/cli/{chunk-fxypxtvm.js → chunk-a58773jm.js} +2 -2
  25. package/dist/cli/{chunk-mb2919y2.js → chunk-acanzt5p.js} +17 -6
  26. package/dist/cli/chunk-acanzt5p.js.map +10 -0
  27. package/dist/cli/{chunk-27g6wdth.js → chunk-akbpwfxc.js} +90 -26
  28. package/dist/cli/chunk-akbpwfxc.js.map +10 -0
  29. package/dist/cli/{chunk-wm7js3j9.js → chunk-b07cmahc.js} +2 -2
  30. package/dist/cli/{chunk-wgm7m9qk.js → chunk-c8chx29p.js} +179 -51
  31. package/dist/cli/chunk-c8chx29p.js.map +36 -0
  32. package/dist/cli/{chunk-qs4q5p4e.js → chunk-crgn1q09.js} +32 -14
  33. package/dist/cli/chunk-crgn1q09.js.map +10 -0
  34. package/dist/cli/chunk-e04dxsz1.js +39 -0
  35. package/dist/cli/chunk-e04dxsz1.js.map +10 -0
  36. package/dist/cli/{chunk-yt5n7ppj.js → chunk-ey84smr6.js} +17 -7
  37. package/dist/cli/chunk-ey84smr6.js.map +10 -0
  38. package/dist/cli/{chunk-f2z5v128.js → chunk-g4hq16wv.js} +14 -15
  39. package/dist/cli/{chunk-f2z5v128.js.map → chunk-g4hq16wv.js.map} +2 -2
  40. package/dist/cli/{chunk-fa25z98p.js → chunk-ga0pf4aj.js} +9 -8
  41. package/dist/cli/chunk-ga0pf4aj.js.map +11 -0
  42. package/dist/cli/{chunk-kpf8rrjc.js → chunk-hm3vjy5s.js} +109 -58
  43. package/dist/cli/chunk-hm3vjy5s.js.map +19 -0
  44. package/dist/cli/{chunk-fs23ddbb.js → chunk-j85vccga.js} +625 -750
  45. package/dist/cli/chunk-j85vccga.js.map +36 -0
  46. package/dist/cli/{chunk-ch6g3ar0.js → chunk-p3v96n38.js} +6 -6
  47. package/dist/cli/{chunk-ch6g3ar0.js.map → chunk-p3v96n38.js.map} +3 -3
  48. package/dist/cli/{chunk-q5163e60.js → chunk-p73c0m7w.js} +21 -19
  49. package/dist/cli/chunk-p73c0m7w.js.map +11 -0
  50. package/dist/cli/{chunk-dh8cwk36.js → chunk-pehfxfta.js} +24 -9
  51. package/dist/cli/chunk-pehfxfta.js.map +10 -0
  52. package/dist/cli/{chunk-qkqwkpte.js → chunk-pv29h0wf.js} +3688 -1313
  53. package/dist/cli/chunk-pv29h0wf.js.map +190 -0
  54. package/dist/cli/{chunk-6vm74dry.js → chunk-q58y5e6a.js} +10 -10
  55. package/dist/cli/{chunk-6vm74dry.js.map → chunk-q58y5e6a.js.map} +3 -3
  56. package/dist/cli/{chunk-zxcczpyx.js → chunk-r20tn01b.js} +1 -1
  57. package/dist/cli/{chunk-m3vmjgmq.js → chunk-r9rcc4w7.js} +16 -8
  58. package/dist/cli/chunk-r9rcc4w7.js.map +10 -0
  59. package/dist/cli/{chunk-5shv93fd.js → chunk-tkacnehg.js} +2 -2
  60. package/dist/cli/{chunk-zxh4d9vy.js → chunk-tzmab476.js} +4 -4
  61. package/dist/cli/{chunk-yw7dm696.js → chunk-vg9r4eb9.js} +24 -11
  62. package/dist/cli/chunk-vg9r4eb9.js.map +14 -0
  63. package/dist/cli/{chunk-79jhk4py.js → chunk-wrr3j9w9.js} +260 -132
  64. package/dist/cli/chunk-wrr3j9w9.js.map +35 -0
  65. package/dist/cli/{chunk-6crbhc3x.js → chunk-yfyb25rh.js} +54 -27
  66. package/dist/cli/chunk-yfyb25rh.js.map +14 -0
  67. package/dist/cli/index.js +162 -35
  68. package/dist/cli/index.js.map +4 -4
  69. package/dist/types/ai/agent-surface.d.ts +32 -0
  70. package/dist/types/ai/api-catalog.d.ts +7 -1
  71. package/dist/types/ai/ask-context.d.ts +7 -0
  72. package/dist/types/ai/component-markdown.d.ts +4 -4
  73. package/dist/types/ai/link-headers.d.ts +8 -1
  74. package/dist/types/ai/openapi-components.d.ts +5 -2
  75. package/dist/types/ai/relative-links.d.ts +11 -4
  76. package/dist/types/ai/skills.d.ts +4 -1
  77. package/dist/types/ai/static-expression.d.ts +28 -0
  78. package/dist/types/ai/tar.d.ts +1 -3
  79. package/dist/types/analytics/databuddy.d.ts +43 -0
  80. package/dist/types/analytics/index.d.ts +4 -0
  81. package/dist/types/analytics/one-dollar-stats.d.ts +48 -0
  82. package/dist/types/analytics/schema.d.ts +28 -0
  83. package/dist/types/astro/integration.d.ts +3 -2
  84. package/dist/types/cli/env.d.ts +5 -0
  85. package/dist/types/cli/init/scaffold.d.ts +19 -3
  86. package/dist/types/cli/init/starter-spec.d.ts +11 -0
  87. package/dist/types/core/base-path.d.ts +24 -1
  88. package/dist/types/core/config-input.d.ts +13 -11
  89. package/dist/types/core/config.d.ts +2 -2
  90. package/dist/types/core/directive-diagnostics.d.ts +12 -0
  91. package/dist/types/core/graph.d.ts +2 -0
  92. package/dist/types/core/heading-markers.d.ts +5 -7
  93. package/dist/types/core/i18n-ui.d.ts +31 -0
  94. package/dist/types/core/i18n.d.ts +9 -1
  95. package/dist/types/core/last-modified.d.ts +10 -0
  96. package/dist/types/core/links.d.ts +3 -1
  97. package/dist/types/core/load-module.d.ts +10 -0
  98. package/dist/types/core/locale-links.d.ts +12 -2
  99. package/dist/types/core/meta.d.ts +13 -1
  100. package/dist/types/core/nav-diagnostics.d.ts +10 -0
  101. package/dist/types/core/navigation.d.ts +38 -0
  102. package/dist/types/core/ordering-prefix.d.ts +4 -0
  103. package/dist/types/core/safe-links.d.ts +3 -1
  104. package/dist/types/core/schema.d.ts +59 -13
  105. package/dist/types/core/sources/github-releases.d.ts +5 -0
  106. package/dist/types/core/sources/lower.d.ts +38 -13
  107. package/dist/types/core/sources/normalize.d.ts +23 -2
  108. package/dist/types/core/sources/remote.d.ts +11 -1
  109. package/dist/types/core/sources/resolve.d.ts +12 -0
  110. package/dist/types/core/sources/types.d.ts +31 -0
  111. package/dist/types/core/sources/watch.d.ts +12 -5
  112. package/dist/types/core/standard-schema.d.ts +5 -0
  113. package/dist/types/core/types.d.ts +9 -0
  114. package/dist/types/deploy/adapters/node.d.ts +5 -2
  115. package/dist/types/deploy/adapters/types.d.ts +7 -0
  116. package/dist/types/deploy/artifacts.d.ts +6 -4
  117. package/dist/types/deploy/cloudflare-negotiation.d.ts +3 -2
  118. package/dist/types/deploy/headers.d.ts +36 -7
  119. package/dist/types/deploy/node-headers.d.ts +43 -8
  120. package/dist/types/deploy/platforms/netlify.d.ts +27 -2
  121. package/dist/types/deploy/platforms/node.d.ts +6 -5
  122. package/dist/types/deploy/platforms/types.d.ts +15 -0
  123. package/dist/types/deploy/platforms/vercel.d.ts +3 -2
  124. package/dist/types/deploy/redirects.d.ts +31 -15
  125. package/dist/types/deploy/vercel-negotiation.d.ts +3 -2
  126. package/dist/types/markdown/directives.d.ts +62 -0
  127. package/dist/types/markdown/features.d.ts +21 -0
  128. package/dist/types/markdown/mdast.d.ts +63 -0
  129. package/dist/types/openapi/asyncapi.d.ts +4 -2
  130. package/dist/types/openapi/model.d.ts +20 -6
  131. package/dist/types/search/sync/algolia.d.ts +3 -1
  132. package/docs/01-quickstart.mdx +3 -2
  133. package/docs/02-deployment.mdx +20 -9
  134. package/docs/08-faq.mdx +10 -3
  135. package/docs/advanced/changelog.mdx +1 -1
  136. package/docs/advanced/custom-pages.mdx +7 -5
  137. package/docs/cli/audit.mdx +19 -3
  138. package/docs/cli/doctor.mdx +2 -2
  139. package/docs/cli/evals.mdx +4 -4
  140. package/docs/cli/index.mdx +4 -1
  141. package/docs/cli/translate.mdx +4 -4
  142. package/docs/cli/version.mdx +1 -1
  143. package/docs/configuration/analytics.mdx +42 -2
  144. package/docs/configuration/assistant.mdx +1 -1
  145. package/docs/configuration/customization.mdx +6 -3
  146. package/docs/configuration/index.mdx +5 -3
  147. package/docs/configuration/search.mdx +2 -2
  148. package/docs/content/components.mdx +2 -2
  149. package/docs/content/frontmatter.mdx +5 -1
  150. package/docs/content/i18n.mdx +3 -1
  151. package/docs/content/index.mdx +8 -4
  152. package/docs/content/islands.mdx +1 -1
  153. package/docs/content/meta.mdx +7 -3
  154. package/docs/content/navigation.mdx +29 -4
  155. package/docs/content/sources.mdx +18 -16
  156. package/docs/content/syntax.mdx +14 -0
  157. package/docs/content/versioning.mdx +2 -1
  158. package/docs/discoverability/agent-discovery.mdx +22 -8
  159. package/docs/discoverability/index.mdx +4 -3
  160. package/docs/discoverability/llms-txt.mdx +2 -5
  161. package/docs/discoverability/markdown.mdx +6 -4
  162. package/docs/discoverability/mcp.mdx +4 -0
  163. package/docs/discoverability/metadata.mdx +3 -2
  164. package/docs/discoverability/open-graph.mdx +8 -4
  165. package/docs/discoverability/rss.mdx +4 -2
  166. package/docs/references/asyncapi.mdx +2 -2
  167. package/docs/references/graphql.mdx +2 -2
  168. package/docs/references/openapi.mdx +6 -4
  169. package/package.json +1 -1
  170. package/skills/blume-migrate/SKILL.md +6 -6
  171. package/skills/blume-migrate/references/docusaurus.md +6 -6
  172. package/skills/blume-migrate/references/fumadocs.md +1 -1
  173. package/skills/blume-migrate/references/mintlify.md +3 -3
  174. package/skills/blume-migrate/references/monorepo.md +1 -1
  175. package/skills/blume-migrate/references/nextra.md +2 -2
  176. package/skills/blume-migrate/references/starlight.md +5 -5
  177. package/skills/blume-migrate/scripts/mintlify-codemod.mjs +9 -4
  178. package/skills/blume-update-docs/references/audit-checklist.md +1 -1
  179. package/src/ai/agent-readability.ts +2 -2
  180. package/src/ai/agent-surface.ts +56 -0
  181. package/src/ai/ai-catalog.ts +6 -2
  182. package/src/ai/api/handlers.ts +2 -2
  183. package/src/ai/api/spec.ts +17 -4
  184. package/src/ai/api-catalog.ts +13 -3
  185. package/src/ai/ask-context.ts +14 -2
  186. package/src/ai/ask-data.ts +26 -12
  187. package/src/ai/changelog-markdown.ts +2 -2
  188. package/src/ai/component-markdown.ts +43 -37
  189. package/src/ai/link-headers.ts +22 -8
  190. package/src/ai/llms.ts +22 -10
  191. package/src/ai/markdown.ts +23 -11
  192. package/src/ai/mcp/discovery.ts +2 -2
  193. package/src/ai/mcp/query.ts +2 -2
  194. package/src/ai/mcp/server.ts +80 -9
  195. package/src/ai/openapi-components.ts +22 -6
  196. package/src/ai/relative-links.ts +99 -15
  197. package/src/ai/serializers.ts +2 -1
  198. package/src/ai/skills.ts +14 -1
  199. package/src/ai/static-expression.ts +416 -0
  200. package/src/ai/tar.ts +139 -12
  201. package/src/ai/visibility.ts +45 -14
  202. package/src/analytics/databuddy.ts +67 -0
  203. package/src/analytics/head.ts +8 -0
  204. package/src/analytics/index.ts +7 -0
  205. package/src/analytics/one-dollar-stats.ts +86 -0
  206. package/src/analytics/posthog.ts +21 -3
  207. package/src/analytics/schema.ts +4 -0
  208. package/src/astro/generate.ts +72 -28
  209. package/src/astro/include-hmr.ts +31 -12
  210. package/src/astro/include-refresh.ts +19 -8
  211. package/src/astro/integration.ts +108 -14
  212. package/src/astro/runtime-modules.ts +28 -13
  213. package/src/astro/templates.ts +139 -57
  214. package/src/audit/catalog.ts +2 -2
  215. package/src/audit/checks/assets.ts +25 -5
  216. package/src/audit/checks/content.ts +20 -2
  217. package/src/audit/checks/i18n.ts +46 -8
  218. package/src/audit/checks/indexability.ts +39 -19
  219. package/src/audit/checks/links.ts +22 -1
  220. package/src/audit/checks/llms.ts +6 -3
  221. package/src/audit/checks/network.ts +3 -1
  222. package/src/audit/checks/og-image.ts +10 -0
  223. package/src/audit/checks/robots.ts +6 -1
  224. package/src/audit/checks/sitemap.ts +56 -31
  225. package/src/audit/checks/social.ts +21 -2
  226. package/src/audit/crawl.ts +88 -15
  227. package/src/audit/graph.ts +3 -1
  228. package/src/audit/report.ts +54 -17
  229. package/src/audit/run.ts +13 -9
  230. package/src/audit/snapshot.ts +5 -0
  231. package/src/audit/types.ts +18 -0
  232. package/src/audit/url.ts +37 -6
  233. package/src/cli/args.ts +32 -0
  234. package/src/cli/build-failure.ts +50 -0
  235. package/src/cli/commands/audit.ts +11 -1
  236. package/src/cli/commands/build.ts +19 -5
  237. package/src/cli/commands/check.ts +2 -0
  238. package/src/cli/commands/doctor.ts +3 -1
  239. package/src/cli/commands/eval.ts +20 -18
  240. package/src/cli/commands/init.ts +15 -5
  241. package/src/cli/commands/preview.ts +15 -0
  242. package/src/cli/commands/sync.ts +2 -0
  243. package/src/cli/commands/translate.ts +18 -18
  244. package/src/cli/commands/upgrade.ts +11 -0
  245. package/src/cli/commands/validate.ts +3 -1
  246. package/src/cli/dev-lock.ts +157 -37
  247. package/src/cli/eject-scripts.ts +32 -7
  248. package/src/cli/env.ts +12 -1
  249. package/src/cli/init/scaffold.ts +117 -11
  250. package/src/cli/init/starter-spec.ts +235 -0
  251. package/src/cli/report-format.ts +11 -6
  252. package/src/components/colors.ts +19 -0
  253. package/src/components/content/AccordionItem.astro +26 -22
  254. package/src/components/content/Badge.astro +7 -12
  255. package/src/components/content/Card.astro +2 -2
  256. package/src/components/content/Component.astro +11 -5
  257. package/src/components/content/Expandable.astro +5 -1
  258. package/src/components/content/Frame.astro +2 -7
  259. package/src/components/content/GithubInfo.astro +12 -2
  260. package/src/components/content/Prompt.astro +2 -7
  261. package/src/components/content/Tab.astro +0 -1
  262. package/src/components/content/Tabs.astro +58 -5
  263. package/src/components/content/Tile.astro +1 -1
  264. package/src/components/content/Tooltip.astro +69 -7
  265. package/src/components/content/Tree.astro +7 -2
  266. package/src/components/content/TypeTable.astro +10 -5
  267. package/src/components/content/Update.astro +8 -2
  268. package/src/components/content/auto-type-table.ts +4 -1
  269. package/src/components/content/badge-color.ts +20 -0
  270. package/src/components/content/base-href.ts +14 -22
  271. package/src/components/content/inline-markdown.ts +27 -7
  272. package/src/components/copy-feedback.ts +35 -8
  273. package/src/components/islands/assistant.tsx +37 -6
  274. package/src/components/islands/base-path.ts +47 -10
  275. package/src/components/islands/hooks.ts +42 -21
  276. package/src/components/islands/webmcp.ts +16 -10
  277. package/src/components/layout/Banner.astro +23 -4
  278. package/src/components/layout/Breadcrumbs.astro +5 -2
  279. package/src/components/layout/DiscoveryLinks.astro +9 -5
  280. package/src/components/layout/Header.astro +23 -10
  281. package/src/components/layout/LanguageSwitcher.astro +2 -2
  282. package/src/components/layout/Logo.astro +5 -0
  283. package/src/components/layout/NavSelector.astro +8 -3
  284. package/src/components/layout/NavTabMenu.astro +133 -0
  285. package/src/components/layout/NavTree.astro +31 -12
  286. package/src/components/layout/NavTreeCache.astro +5 -2
  287. package/src/components/layout/NavTreeScript.astro +45 -5
  288. package/src/components/layout/PageActions.astro +4 -3
  289. package/src/components/layout/PageLayout.astro +28 -13
  290. package/src/components/layout/Pagination.astro +3 -3
  291. package/src/components/layout/ReferenceLayout.astro +4 -1
  292. package/src/components/layout/RootLayout.astro +25 -12
  293. package/src/components/layout/Search.astro +38 -14
  294. package/src/components/layout/VersionBanner.astro +2 -2
  295. package/src/components/layout/analytics-client.ts +14 -0
  296. package/src/components/layout/toc-active.ts +41 -0
  297. package/src/components/layout/toc-element.ts +8 -14
  298. package/src/components/openapi/ApiTagOperations.astro +2 -2
  299. package/src/components/openapi/AsyncApiOperation.astro +7 -4
  300. package/src/components/openapi/GraphqlChip.astro +2 -2
  301. package/src/components/openapi/GraphqlType.astro +11 -3
  302. package/src/components/openapi/MessageComposer.astro +1 -1
  303. package/src/components/openapi/Operation.astro +23 -5
  304. package/src/components/openapi/PanelTabs.astro +4 -1
  305. package/src/components/openapi/Playground.astro +8 -2
  306. package/src/components/openapi/RequestPanel.astro +13 -6
  307. package/src/components/openapi/SchemaProperty.astro +11 -33
  308. package/src/components/openapi/SchemaTable.astro +34 -52
  309. package/src/components/openapi/async.ts +38 -6
  310. package/src/components/openapi/helpers.ts +100 -12
  311. package/src/components/openapi/message-composer.ts +14 -1
  312. package/src/components/openapi/message-model.ts +12 -2
  313. package/src/components/openapi/message.ts +21 -3
  314. package/src/components/openapi/operation-model.ts +119 -17
  315. package/src/components/openapi/panel.ts +31 -5
  316. package/src/components/openapi/param-style.ts +181 -0
  317. package/src/components/openapi/playground-client.ts +110 -23
  318. package/src/components/openapi/playground-schema.ts +25 -6
  319. package/src/components/openapi/request.ts +192 -26
  320. package/src/components/openapi/schema-tree.ts +209 -0
  321. package/src/components/openapi/snippets.ts +113 -19
  322. package/src/components/openapi/validate-json.ts +1 -1
  323. package/src/components/openapi/ws-client.ts +18 -2
  324. package/src/core/base-path.ts +69 -8
  325. package/src/core/config-input.ts +13 -11
  326. package/src/core/config.ts +18 -4
  327. package/src/core/diagnostics.ts +219 -30
  328. package/src/core/directive-diagnostics.ts +99 -0
  329. package/src/core/frontmatter.ts +21 -18
  330. package/src/core/graph.ts +120 -11
  331. package/src/core/heading-markers.ts +5 -18
  332. package/src/core/i18n-ui.ts +39 -2
  333. package/src/core/i18n.ts +44 -5
  334. package/src/core/last-modified.ts +25 -3
  335. package/src/core/links.ts +7 -4
  336. package/src/core/load-module.ts +20 -0
  337. package/src/core/locale-links.ts +22 -18
  338. package/src/core/manifest.ts +3 -2
  339. package/src/core/meta.ts +89 -12
  340. package/src/core/nav-diagnostics.ts +56 -1
  341. package/src/core/navigation.ts +272 -83
  342. package/src/core/ordering-prefix.ts +27 -0
  343. package/src/core/project-graph.ts +50 -6
  344. package/src/core/safe-href.ts +53 -1
  345. package/src/core/safe-links.ts +11 -2
  346. package/src/core/schema.ts +94 -18
  347. package/src/core/sources/assets.ts +83 -41
  348. package/src/core/sources/contentful-rich-text.ts +28 -20
  349. package/src/core/sources/contentful.ts +25 -12
  350. package/src/core/sources/filesystem.ts +25 -3
  351. package/src/core/sources/github-releases.ts +131 -11
  352. package/src/core/sources/lexical.ts +30 -20
  353. package/src/core/sources/lower.ts +276 -56
  354. package/src/core/sources/mdx-remote.ts +52 -16
  355. package/src/core/sources/normalize.ts +456 -119
  356. package/src/core/sources/notion.ts +77 -34
  357. package/src/core/sources/obsidian.ts +40 -9
  358. package/src/core/sources/payload.ts +1 -0
  359. package/src/core/sources/portable-text.ts +85 -49
  360. package/src/core/sources/remote.ts +18 -2
  361. package/src/core/sources/resolve.ts +52 -33
  362. package/src/core/sources/sanity.ts +1 -0
  363. package/src/core/sources/strapi-blocks.ts +28 -19
  364. package/src/core/sources/strapi.ts +1 -0
  365. package/src/core/sources/types.ts +31 -0
  366. package/src/core/sources/watch.ts +20 -7
  367. package/src/core/standard-schema.ts +10 -6
  368. package/src/core/types.ts +9 -0
  369. package/src/core/ui-packs/ar.ts +13 -0
  370. package/src/core/ui-packs/bg.ts +13 -0
  371. package/src/core/ui-packs/bn.ts +13 -0
  372. package/src/core/ui-packs/ca.ts +13 -0
  373. package/src/core/ui-packs/cs.ts +13 -0
  374. package/src/core/ui-packs/da.ts +13 -0
  375. package/src/core/ui-packs/de.ts +13 -0
  376. package/src/core/ui-packs/el.ts +13 -0
  377. package/src/core/ui-packs/es.ts +13 -0
  378. package/src/core/ui-packs/fa.ts +13 -0
  379. package/src/core/ui-packs/fi.ts +13 -0
  380. package/src/core/ui-packs/fr.ts +13 -0
  381. package/src/core/ui-packs/he.ts +13 -0
  382. package/src/core/ui-packs/hi.ts +13 -0
  383. package/src/core/ui-packs/hr.ts +13 -0
  384. package/src/core/ui-packs/hu.ts +13 -0
  385. package/src/core/ui-packs/id.ts +13 -0
  386. package/src/core/ui-packs/it.ts +13 -0
  387. package/src/core/ui-packs/ja.ts +13 -0
  388. package/src/core/ui-packs/ko.ts +13 -0
  389. package/src/core/ui-packs/nl.ts +13 -0
  390. package/src/core/ui-packs/no.ts +13 -0
  391. package/src/core/ui-packs/pl.ts +13 -0
  392. package/src/core/ui-packs/pt-br.ts +13 -0
  393. package/src/core/ui-packs/pt.ts +13 -0
  394. package/src/core/ui-packs/ro.ts +13 -0
  395. package/src/core/ui-packs/ru.ts +13 -0
  396. package/src/core/ui-packs/sk.ts +13 -0
  397. package/src/core/ui-packs/sr.ts +13 -0
  398. package/src/core/ui-packs/sv.ts +13 -0
  399. package/src/core/ui-packs/th.ts +13 -0
  400. package/src/core/ui-packs/tr.ts +13 -0
  401. package/src/core/ui-packs/uk.ts +13 -0
  402. package/src/core/ui-packs/vi.ts +13 -0
  403. package/src/core/ui-packs/zh-tw.ts +13 -0
  404. package/src/core/ui-packs/zh.ts +13 -0
  405. package/src/core/version-cut.ts +69 -17
  406. package/src/core/versions.ts +4 -1
  407. package/src/deploy/adapters/node.ts +5 -2
  408. package/src/deploy/adapters/registry.ts +2 -1
  409. package/src/deploy/adapters/types.ts +13 -1
  410. package/src/deploy/artifacts.ts +52 -11
  411. package/src/deploy/cloudflare-negotiation.ts +27 -16
  412. package/src/deploy/headers.ts +75 -55
  413. package/src/deploy/node-headers.ts +148 -27
  414. package/src/deploy/platforms/cloudflare.ts +13 -3
  415. package/src/deploy/platforms/netlify.ts +83 -5
  416. package/src/deploy/platforms/node.ts +8 -5
  417. package/src/deploy/platforms/static.ts +2 -0
  418. package/src/deploy/platforms/types.ts +15 -0
  419. package/src/deploy/platforms/vercel.ts +10 -3
  420. package/src/deploy/redirects.ts +68 -21
  421. package/src/deploy/robots.ts +2 -2
  422. package/src/deploy/rss.ts +4 -3
  423. package/src/deploy/sitemap.ts +7 -5
  424. package/src/deploy/vercel-negotiation.ts +35 -3
  425. package/src/eval/agents.ts +10 -2
  426. package/src/eval/run.ts +25 -0
  427. package/src/markdown/base-links.ts +74 -29
  428. package/src/markdown/directives.ts +242 -36
  429. package/src/markdown/features.ts +17 -0
  430. package/src/markdown/index.ts +13 -9
  431. package/src/markdown/mdast.ts +5 -2
  432. package/src/markdown/relative-links.ts +15 -27
  433. package/src/markdown/route-snapshot.ts +37 -0
  434. package/src/og/card.ts +149 -9
  435. package/src/og/derive.ts +156 -5
  436. package/src/og/index.ts +1 -0
  437. package/src/openapi/asyncapi.ts +4 -2
  438. package/src/openapi/graphql-build.ts +28 -2
  439. package/src/openapi/model.ts +99 -27
  440. package/src/openapi/proxy.ts +63 -10
  441. package/src/openapi/render-mdx.ts +51 -3
  442. package/src/registry/eject.ts +297 -43
  443. package/src/search/adapters/version-scope.ts +30 -0
  444. package/src/search/documents.ts +89 -27
  445. package/src/search/popular.ts +2 -1
  446. package/src/search/sync/algolia.ts +36 -2
  447. package/src/seo/jsonld.ts +7 -3
  448. package/src/sources/registry.ts +5 -0
  449. package/src/theme/entry.ts +11 -4
  450. package/src/translate/agents.ts +6 -1
  451. package/src/translate/ledger.ts +26 -3
  452. package/src/translate/meta.ts +76 -24
  453. package/src/translate/run.ts +14 -8
  454. package/src/translate/validate.ts +14 -2
  455. package/src/translate/work-list.ts +91 -19
  456. package/src/upgrade/upgrade.ts +36 -4
  457. package/dist/cli/chunk-27g6wdth.js.map +0 -10
  458. package/dist/cli/chunk-2hn4b8z7.js.map +0 -12
  459. package/dist/cli/chunk-6crbhc3x.js.map +0 -14
  460. package/dist/cli/chunk-6hsn950k.js.map +0 -10
  461. package/dist/cli/chunk-79jhk4py.js.map +0 -35
  462. package/dist/cli/chunk-82bbrxdn.js +0 -51
  463. package/dist/cli/chunk-82bbrxdn.js.map +0 -10
  464. package/dist/cli/chunk-abh8yjkn.js +0 -31
  465. package/dist/cli/chunk-abh8yjkn.js.map +0 -10
  466. package/dist/cli/chunk-ce574jw2.js +0 -23
  467. package/dist/cli/chunk-ce574jw2.js.map +0 -10
  468. package/dist/cli/chunk-dh8cwk36.js.map +0 -10
  469. package/dist/cli/chunk-epjnccmv.js.map +0 -10
  470. package/dist/cli/chunk-fa25z98p.js.map +0 -11
  471. package/dist/cli/chunk-fs23ddbb.js.map +0 -35
  472. package/dist/cli/chunk-fz5wtpmh.js.map +0 -10
  473. package/dist/cli/chunk-jwyddg7y.js.map +0 -15
  474. package/dist/cli/chunk-kpf8rrjc.js.map +0 -19
  475. package/dist/cli/chunk-m3vmjgmq.js.map +0 -10
  476. package/dist/cli/chunk-mb2919y2.js.map +0 -10
  477. package/dist/cli/chunk-q5163e60.js.map +0 -11
  478. package/dist/cli/chunk-qkqwkpte.js.map +0 -182
  479. package/dist/cli/chunk-qs4q5p4e.js.map +0 -10
  480. package/dist/cli/chunk-s1p84fyh.js.map +0 -10
  481. package/dist/cli/chunk-wgm7m9qk.js.map +0 -36
  482. package/dist/cli/chunk-yt5n7ppj.js.map +0 -10
  483. package/dist/cli/chunk-yw7dm696.js.map +0 -14
  484. /package/dist/cli/{chunk-s6jhgk0q.js.map → chunk-2eytanqx.js.map} +0 -0
  485. /package/dist/cli/{chunk-hdpx1tax.js.map → chunk-2z928egk.js.map} +0 -0
  486. /package/dist/cli/{chunk-ah61y8py.js.map → chunk-364znk6q.js.map} +0 -0
  487. /package/dist/cli/{chunk-vtk4a6dg.js.map → chunk-8ktnccpt.js.map} +0 -0
  488. /package/dist/cli/{chunk-fxypxtvm.js.map → chunk-a58773jm.js.map} +0 -0
  489. /package/dist/cli/{chunk-wm7js3j9.js.map → chunk-b07cmahc.js.map} +0 -0
  490. /package/dist/cli/{chunk-zxcczpyx.js.map → chunk-r20tn01b.js.map} +0 -0
  491. /package/dist/cli/{chunk-5shv93fd.js.map → chunk-tkacnehg.js.map} +0 -0
  492. /package/dist/cli/{chunk-zxh4d9vy.js.map → chunk-tzmab476.js.map} +0 -0
@@ -1,17 +1,26 @@
1
+ import { withServerDefaults } from "../../openapi/model.ts";
1
2
  import {
3
+ declaredExample,
2
4
  exampleValue,
3
5
  objectProperties,
4
6
  resolveSchema,
5
7
  toJson,
6
8
  } from "./helpers.ts";
7
- import type { ParameterLike, SchemaLike, SpecValue } from "./helpers.ts";
9
+ import type {
10
+ ComponentsLike,
11
+ ParameterLike,
12
+ SchemaLike,
13
+ SpecValue,
14
+ } from "./helpers.ts";
8
15
  import {
9
16
  declaredTypes,
10
17
  inputValue,
11
18
  scalarType,
12
19
  validationSchema,
13
20
  } from "./playground-schema.ts";
21
+ import { bodyEncoding } from "./request.ts";
14
22
  import type {
23
+ ParamSerialization,
15
24
  PlaygroundAuthInput,
16
25
  PlaygroundBody,
17
26
  PlaygroundBodyField,
@@ -30,9 +39,31 @@ import type { OperationSecurity, ResolvedScheme } from "./security.ts";
30
39
 
31
40
  interface MediaTypeLike {
32
41
  schema?: SchemaLike;
33
- example?: unknown;
42
+ example?: SpecValue;
43
+ examples?: SpecValue;
44
+ encoding?: SpecValue;
45
+ }
46
+
47
+ /** A server entry: its URL template and the variables that fill it. */
48
+ export interface ServerLike {
49
+ url?: string;
50
+ variables?: SpecValue;
34
51
  }
35
52
 
53
+ /**
54
+ * The servers an operation is sent to: its own `servers` when it declares
55
+ * any, else its path item's, else the document's — each level overrides the
56
+ * one above it, as the OpenAPI spec defines.
57
+ */
58
+ export const effectiveServers = (
59
+ operation: ServerLike[] | undefined,
60
+ pathItem: ServerLike[] | undefined,
61
+ document: ServerLike[] | undefined
62
+ ): ServerLike[] =>
63
+ [operation, pathItem, document].find(
64
+ (servers) => Array.isArray(servers) && servers.length > 0
65
+ ) ?? [];
66
+
36
67
  /** Primitive types the flat-body fields UI can edit directly. */
37
68
  const PRIMITIVE_TYPES = {
38
69
  boolean: true,
@@ -47,6 +78,45 @@ const isExampleObject = (
47
78
  ): value is Record<string, SpecValue> =>
48
79
  typeof value === "object" && value !== null && !Array.isArray(value);
49
80
 
81
+ const isString = (value: SpecValue): value is string =>
82
+ typeof value === "string";
83
+
84
+ const isBoolean = (value: SpecValue): value is boolean =>
85
+ typeof value === "boolean";
86
+
87
+ /**
88
+ * The `style`/`explode` a parameter or form-body encoding declares, kept only
89
+ * when declared so the embedded model stays small; the request builder
90
+ * applies the OpenAPI defaults for the rest.
91
+ */
92
+ const serialization = (node: Record<string, SpecValue>): ParamSerialization => {
93
+ const rule: ParamSerialization = {};
94
+ if (isString(node.style)) {
95
+ rule.style = node.style;
96
+ }
97
+ if (isBoolean(node.explode)) {
98
+ rule.explode = node.explode;
99
+ }
100
+ return rule;
101
+ };
102
+
103
+ /** A form body's declared per-field `encoding`, when it names any style. */
104
+ const formEncoding = (
105
+ encoding: SpecValue
106
+ ): PlaygroundBody["encoding"] | undefined => {
107
+ if (!isExampleObject(encoding)) {
108
+ return undefined;
109
+ }
110
+ const fields: NonNullable<PlaygroundBody["encoding"]> = {};
111
+ for (const [name, entry] of Object.entries(encoding)) {
112
+ const rule = isExampleObject(entry) ? serialization(entry) : {};
113
+ if (Object.keys(rule).length > 0) {
114
+ fields[name] = rule;
115
+ }
116
+ }
117
+ return Object.keys(fields).length > 0 ? fields : undefined;
118
+ };
119
+
50
120
  /**
51
121
  * Playground inputs for the operation's parameters. Cookie params are skipped
52
122
  * (not supported in v1 — browsers won't let a page set arbitrary cookies).
@@ -57,7 +127,8 @@ const isExampleObject = (
57
127
  */
58
128
  const modelParams = (
59
129
  parameters: ParameterLike[],
60
- schemas: Record<string, SchemaLike>
130
+ schemas: Record<string, SchemaLike>,
131
+ components: ComponentsLike | undefined
61
132
  ): PlaygroundParam[] => {
62
133
  const params: PlaygroundParam[] = [];
63
134
  for (const param of parameters) {
@@ -78,8 +149,12 @@ const modelParams = (
78
149
  required,
79
150
  type: scalarType(param.schema, schemas),
80
151
  value: required
81
- ? inputValue(param.example ?? exampleValue(param.schema, schemas))
152
+ ? inputValue(
153
+ declaredExample(param, components) ??
154
+ exampleValue(param.schema, schemas)
155
+ )
82
156
  : "",
157
+ ...serialization(param),
83
158
  });
84
159
  }
85
160
  return params;
@@ -127,7 +202,12 @@ const bodyFields = (
127
202
  description: propertySchema.description,
128
203
  enum: propertySchema.enum?.map(String),
129
204
  name,
130
- required: required.has(name),
205
+ // A `readOnly` property's `required` binds responses only: a request
206
+ // shouldn't send the server-generated field at all.
207
+ required:
208
+ required.has(name) &&
209
+ property.readOnly !== true &&
210
+ propertySchema.readOnly !== true,
131
211
  type,
132
212
  value: inputValue(defaults?.[name]),
133
213
  });
@@ -135,26 +215,42 @@ const bodyFields = (
135
215
  return fields;
136
216
  };
137
217
 
138
- /** The playground's body editor state, when the operation takes a request body. */
218
+ /**
219
+ * The playground's body editor state, when the operation takes a request body.
220
+ * JSON, form-urlencoded, and multipart bodies are edited as JSON (typed fields
221
+ * when flat) and serialized for their media type on send; any other media
222
+ * type is raw text, prefilled with a string example as written.
223
+ */
139
224
  const modelBody = (
140
225
  requestBody: { content?: Record<string, MediaTypeLike> } | undefined,
141
- schemas: Record<string, SchemaLike>
226
+ schemas: Record<string, SchemaLike>,
227
+ components: ComponentsLike | undefined
142
228
  ): PlaygroundBody | undefined => {
143
229
  const media = jsonContentType(requestBody?.content);
144
230
  if (!media) {
145
231
  return undefined;
146
232
  }
147
233
  const [contentType, mediaType] = media;
148
- // SAFETY: `example` comes from the parsed spec document (YAML/JSON), whose
149
- // values are exactly the JSON-shaped tree `SpecValue` models.
150
234
  const exampleData =
151
- (mediaType.example as SpecValue) ?? exampleValue(mediaType.schema, schemas);
152
- return {
235
+ declaredExample(mediaType, components) ??
236
+ exampleValue(mediaType.schema, schemas);
237
+ const encoding = bodyEncoding(contentType);
238
+ const raw = encoding === "raw";
239
+ const body: PlaygroundBody = {
153
240
  contentType,
154
- example: toJson(exampleData) ?? "",
155
- fields: bodyFields(mediaType.schema, schemas, exampleData),
241
+ example:
242
+ raw && isString(exampleData) ? exampleData : (toJson(exampleData) ?? ""),
243
+ fields: raw
244
+ ? undefined
245
+ : bodyFields(mediaType.schema, schemas, exampleData),
156
246
  schema: validationSchema(mediaType.schema, schemas),
157
247
  };
248
+ const fieldEncoding =
249
+ encoding === "form" ? formEncoding(mediaType.encoding) : undefined;
250
+ if (fieldEncoding) {
251
+ body.encoding = fieldEncoding;
252
+ }
253
+ return body;
158
254
  };
159
255
 
160
256
  const AUTHORIZATION_HEADER = { in: "header", name: "Authorization" } as const;
@@ -241,8 +337,11 @@ export const operationModel = (args: {
241
337
  /** Pre-merged/resolved (`mergeParameters` output). */
242
338
  parameters: ParameterLike[];
243
339
  requestBody?: { content?: Record<string, MediaTypeLike> };
244
- servers: { url?: string }[];
340
+ /** The operation's effective servers (`effectiveServers` output). */
341
+ servers: ServerLike[];
245
342
  schemas: Record<string, SchemaLike>;
343
+ /** The document's `components`, which `$ref`'d examples resolve against. */
344
+ components?: ComponentsLike;
246
345
  security: OperationSecurity;
247
346
  }): PlaygroundModel => ({
248
347
  // First alternative only — the spec's preferred way to authorize, matching
@@ -252,9 +351,12 @@ export const operationModel = (args: {
252
351
  return input ? [input] : [];
253
352
  }),
254
353
  authOptional: args.security.optional,
255
- body: modelBody(args.requestBody, args.schemas),
354
+ body: modelBody(args.requestBody, args.schemas, args.components),
256
355
  method: args.method.toUpperCase(),
257
- params: modelParams(args.parameters, args.schemas),
356
+ params: modelParams(args.parameters, args.schemas, args.components),
258
357
  path: args.path,
259
- servers: args.servers.map((server) => server.url ?? ""),
358
+ // Variables resolve to their defaults: the samples and Send need a real URL.
359
+ servers: args.servers.map((server) =>
360
+ withServerDefaults(server.url ?? "", server.variables)
361
+ ),
260
362
  });
@@ -18,23 +18,49 @@ class BlumePanelTabs extends HTMLElement {
18
18
 
19
19
  const activate = (key: string): void => {
20
20
  for (const tab of tabs) {
21
- tab.setAttribute(
22
- "aria-selected",
23
- tab.dataset.panelTab === key ? "true" : "false"
24
- );
21
+ const selected = tab.dataset.panelTab === key;
22
+ tab.setAttribute("aria-selected", selected ? "true" : "false");
23
+ // Roving tabindex: Tab reaches the selected tab only, the arrow keys
24
+ // move between the rest.
25
+ tab.tabIndex = selected ? 0 : -1;
25
26
  }
26
27
  for (const panel of panels) {
27
28
  panel.classList.toggle("hidden", panel.dataset.panel !== key);
28
29
  }
29
30
  };
30
31
 
31
- for (const tab of tabs) {
32
+ for (const [index, tab] of tabs.entries()) {
32
33
  tab.addEventListener("click", () => {
33
34
  const key = tab.dataset.panelTab;
34
35
  if (key) {
35
36
  activate(key);
36
37
  }
37
38
  });
39
+ // The WAI-ARIA tabs keyboard pattern, matching `<Tabs>`: arrows move and
40
+ // wrap, Home and End jump to the ends, and focus follows selection.
41
+ tab.addEventListener("keydown", (event) => {
42
+ const last = tabs.length - 1;
43
+ // The strip runs right to left under dir="rtl", so the arrows swap.
44
+ const rtl = getComputedStyle(this).direction === "rtl";
45
+ let next: number | undefined;
46
+ if (event.key === (rtl ? "ArrowLeft" : "ArrowRight")) {
47
+ next = index === last ? 0 : index + 1;
48
+ } else if (event.key === (rtl ? "ArrowRight" : "ArrowLeft")) {
49
+ next = index === 0 ? last : index - 1;
50
+ } else if (event.key === "Home") {
51
+ next = 0;
52
+ } else if (event.key === "End") {
53
+ next = last;
54
+ }
55
+ const target = next === undefined ? undefined : tabs[next];
56
+ const key = target?.dataset.panelTab;
57
+ if (!(target && key)) {
58
+ return;
59
+ }
60
+ event.preventDefault();
61
+ activate(key);
62
+ target.focus();
63
+ });
38
64
  }
39
65
 
40
66
  if (copy) {
@@ -0,0 +1,181 @@
1
+ /**
2
+ * OpenAPI 3 parameter serialization — `style` and `explode` — for the
3
+ * playground's one request builder (`request.ts`). An array or object value
4
+ * has a different wire shape per style: `tags: ["dog", "cat"]` is
5
+ * `tags=dog&tags=cat` in a query (form, exploded), `dog,cat` in a path
6
+ * (simple), and `filter: { color: "red" }` is `filter[color]=red` as a
7
+ * deepObject. Ships in the client bundle with `request.ts`, so it stays
8
+ * dependency-free.
9
+ */
10
+
11
+ /** Parsed JSON: everything `JSON.parse` can produce. */
12
+ export type JsonValue =
13
+ | string
14
+ | number
15
+ | boolean
16
+ | null
17
+ | JsonValue[]
18
+ | { [key: string]: JsonValue };
19
+
20
+ /** A value lowered for serialization: a scalar, a list's items, or an object's entries. */
21
+ export type StyledValue =
22
+ | { kind: "scalar"; text: string }
23
+ | { kind: "list"; items: string[] }
24
+ | { kind: "object"; entries: [string, string][] };
25
+
26
+ // `typeof` checks live in named predicates (the form the oxlint anti-slop
27
+ // config sanctions).
28
+ const isString = (value: JsonValue): value is string =>
29
+ typeof value === "string";
30
+
31
+ /** Whether a parsed value is a plain JSON object. */
32
+ export const isJsonObject = (
33
+ value: JsonValue
34
+ ): value is { [key: string]: JsonValue } =>
35
+ typeof value === "object" && value !== null && !Array.isArray(value);
36
+
37
+ /** One member as wire text: a string verbatim, anything else as its JSON. */
38
+ export const memberText = (value: JsonValue): string =>
39
+ isString(value) ? value : JSON.stringify(value);
40
+
41
+ /** A parsed value, lowered: arrays to items, objects to entries, the rest to text. */
42
+ export const styledValue = (value: JsonValue): StyledValue => {
43
+ if (Array.isArray(value)) {
44
+ return { items: value.map(memberText), kind: "list" };
45
+ }
46
+ if (isJsonObject(value)) {
47
+ return {
48
+ entries: Object.entries(value).map(([key, member]) => [
49
+ key,
50
+ memberText(member),
51
+ ]),
52
+ kind: "object",
53
+ };
54
+ }
55
+ return { kind: "scalar", text: memberText(value) };
56
+ };
57
+
58
+ /**
59
+ * A playground input's text as a styled value. Only an array- or object-typed
60
+ * parameter reads its text as JSON — its prefill is the example's JSON
61
+ * (`["dog","cat"]`) — and only when the text parses to that shape; anything
62
+ * else is sent as the one scalar it reads as.
63
+ */
64
+ export const parseStyledValue = (text: string, type: string): StyledValue => {
65
+ if (type === "array" || type === "object") {
66
+ let parsed: JsonValue;
67
+ try {
68
+ parsed = JSON.parse(text);
69
+ } catch {
70
+ return { kind: "scalar", text };
71
+ }
72
+ if (type === "array" ? Array.isArray(parsed) : isJsonObject(parsed)) {
73
+ return styledValue(parsed);
74
+ }
75
+ }
76
+ return { kind: "scalar", text };
77
+ };
78
+
79
+ /** The OpenAPI 3 default style: `form` in a query or cookie, `simple` elsewhere. */
80
+ export const defaultStyle = (location: string): string =>
81
+ location === "query" || location === "cookie" ? "form" : "simple";
82
+
83
+ /** `explode` when the spec leaves it out: true for `form`, false for the rest. */
84
+ export const defaultExplode = (style: string): boolean => style === "form";
85
+
86
+ /**
87
+ * A list or object joined into one value: exploded object entries as
88
+ * `key=value` separated by `separator`, exploded items by `separator`, and
89
+ * the unexploded forms comma-separated (`a,b`, `key,value,key2,value2`).
90
+ */
91
+ const joined = (
92
+ value: StyledValue,
93
+ explode: boolean,
94
+ encode: (text: string) => string,
95
+ separator: string
96
+ ): string => {
97
+ if (value.kind === "scalar") {
98
+ return encode(value.text);
99
+ }
100
+ if (value.kind === "list") {
101
+ return value.items.map(encode).join(explode ? separator : ",");
102
+ }
103
+ return explode
104
+ ? value.entries
105
+ .map(([key, member]) => `${encode(key)}=${encode(member)}`)
106
+ .join(separator)
107
+ : value.entries.flat().map(encode).join(",");
108
+ };
109
+
110
+ /**
111
+ * A query parameter, or an `application/x-www-form-urlencoded` body field, as
112
+ * its percent-encoded `name=value` pairs in order. Exploded lists repeat the
113
+ * name (`tags=dog&tags=cat`) and exploded objects spread their entries
114
+ * (`color=red&size=L`) whatever the style; unexploded ones join with the
115
+ * style's delimiter — `,` for form, `%20` for spaceDelimited, `|` for
116
+ * pipeDelimited. deepObject nests entries under the name (`filter[color]=red`).
117
+ */
118
+ export const queryPairs = (
119
+ name: string,
120
+ value: StyledValue,
121
+ style: string,
122
+ explode: boolean
123
+ ): string[] => {
124
+ const key = encodeURIComponent(name);
125
+ if (value.kind === "object" && style === "deepObject") {
126
+ return value.entries.map(
127
+ ([entry, member]) =>
128
+ `${key}[${encodeURIComponent(entry)}]=${encodeURIComponent(member)}`
129
+ );
130
+ }
131
+ if (value.kind === "list" && explode) {
132
+ return value.items.map((item) => `${key}=${encodeURIComponent(item)}`);
133
+ }
134
+ if (value.kind === "object" && explode) {
135
+ return value.entries.map(
136
+ ([entry, member]) =>
137
+ `${encodeURIComponent(entry)}=${encodeURIComponent(member)}`
138
+ );
139
+ }
140
+ let delimiter = ",";
141
+ if (style === "spaceDelimited") {
142
+ delimiter = "%20";
143
+ } else if (style === "pipeDelimited") {
144
+ delimiter = "|";
145
+ }
146
+ const text =
147
+ value.kind === "scalar"
148
+ ? encodeURIComponent(value.text)
149
+ : (value.kind === "list" ? value.items : value.entries.flat())
150
+ .map(encodeURIComponent)
151
+ .join(delimiter);
152
+ return [`${key}=${text}`];
153
+ };
154
+
155
+ /**
156
+ * A path or header parameter's value: `simple` (`dog,cat`), or one of the
157
+ * path-only `label` (`.dog.cat`) and `matrix` (`;tags=dog;tags=cat`) styles.
158
+ * `encode` percent-encodes a path segment; a header passes values through.
159
+ */
160
+ export const templateValue = (
161
+ name: string,
162
+ value: StyledValue,
163
+ style: string,
164
+ explode: boolean,
165
+ encode: (text: string) => string
166
+ ): string => {
167
+ if (style === "label") {
168
+ return `.${joined(value, explode, encode, ".")}`;
169
+ }
170
+ if (style !== "matrix") {
171
+ return joined(value, explode, encode, ",");
172
+ }
173
+ const key = encode(name);
174
+ if (value.kind === "list" && explode) {
175
+ return value.items.map((item) => `;${key}=${encode(item)}`).join("");
176
+ }
177
+ if (value.kind === "object" && explode) {
178
+ return `;${joined(value, true, encode, ";")}`;
179
+ }
180
+ return `;${key}=${joined(value, false, encode, ",")}`;
181
+ };
@@ -16,8 +16,14 @@ import type {
16
16
  PlaygroundModel,
17
17
  RequestValues,
18
18
  } from "./request.ts";
19
- import { buildRequest, redactAuth } from "./request.ts";
20
- import { sampleLanguages } from "./snippets.ts";
19
+ import {
20
+ bodyEncoding,
21
+ buildRequest,
22
+ PROXY_HEADERS_HEADER,
23
+ redactAuth,
24
+ } from "./request.ts";
25
+ import { fetchRefusesMethod, sampleLanguages } from "./snippets.ts";
26
+ import type { RequestSample } from "./snippets.ts";
21
27
  import { validateJson } from "./validate-json.ts";
22
28
 
23
29
  /**
@@ -42,6 +48,16 @@ const COOKIE_MESSAGE =
42
48
  "send the cookie credential you entered. Copy the sample above and run it " +
43
49
  "from a terminal instead.";
44
50
 
51
+ /**
52
+ * fetch refuses the TRACE method outright, proxied or not: the browser throws
53
+ * before anything is sent, which the send's catch would misdiagnose as the
54
+ * CORS wall or an unreachable host. The cURL and Python samples can send it.
55
+ */
56
+ const TRACE_MESSAGE =
57
+ "Browsers don't allow a page to send a `TRACE` request, so this panel " +
58
+ "can't send it. Run the cURL or Python sample above from a terminal " +
59
+ "instead.";
60
+
45
61
  /**
46
62
  * A fetch that fails before any response when the API couldn't be reached at
47
63
  * all — a mistyped host, a refused connection, no network — rather than
@@ -155,6 +171,80 @@ const prettyBody = (text: string): string => {
155
171
  }
156
172
  };
157
173
 
174
+ /*
175
+ * Remembered credentials are a convenience, never a requirement. With storage
176
+ * blocked (Safari's "Block All Cookies", a sandboxed iframe) even reading the
177
+ * `localStorage` global throws a SecurityError, which would take the whole
178
+ * panel down during init; each access is guarded so the form simply forgets.
179
+ */
180
+ const readStored = (key: string): string | null => {
181
+ try {
182
+ return localStorage.getItem(key);
183
+ } catch {
184
+ return null;
185
+ }
186
+ };
187
+
188
+ const writeStored = (key: string, value: string): void => {
189
+ try {
190
+ localStorage.setItem(key, value);
191
+ } catch {
192
+ // Blocked or full storage: the credentials stay in the form only.
193
+ }
194
+ };
195
+
196
+ const removeStored = (key: string): void => {
197
+ try {
198
+ localStorage.removeItem(key);
199
+ } catch {
200
+ // Blocked storage never held the entry in the first place.
201
+ }
202
+ };
203
+
204
+ /**
205
+ * The headers a live send carries. A `Cookie` header can still arrive via a
206
+ * spec-declared header parameter; the browser would silently drop the
207
+ * forbidden name anyway, so it is stripped rather than earn a console
208
+ * warning. Blume's own proxy — always a path on this site — forwards only the
209
+ * headers named in {@link PROXY_HEADERS_HEADER}, never ones the browser or
210
+ * the platform add on their own; a multipart body's Content-Type is fetch's,
211
+ * written with the boundary, so it is named without being set. An external
212
+ * proxy on another origin gets no such header: it would only be one more name
213
+ * for its preflight to allow.
214
+ */
215
+ const sendHeaders = (sample: RequestSample, proxy: string) => {
216
+ const headers = { ...sample.headers };
217
+ delete headers.Cookie;
218
+ if (proxy.startsWith("/") && !proxy.startsWith("//")) {
219
+ const names = Object.keys(headers);
220
+ if (sample.formData) {
221
+ names.push("Content-Type");
222
+ }
223
+ headers[PROXY_HEADERS_HEADER] = names.join(", ");
224
+ }
225
+ return headers;
226
+ };
227
+
228
+ /**
229
+ * The body a live send carries: multipart parts as `FormData`, anything else
230
+ * as its text. fetch throws a synchronous TypeError for a GET/HEAD with a
231
+ * body — which the send's catch would mislabel as CORS — so a spec that
232
+ * declares a GET requestBody keeps its samples, but the live send drops it.
233
+ */
234
+ const sendBody = (sample: RequestSample): string | FormData | undefined => {
235
+ if (sample.method === "GET" || sample.method === "HEAD") {
236
+ return undefined;
237
+ }
238
+ if (!sample.formData) {
239
+ return sample.body;
240
+ }
241
+ const form = new FormData();
242
+ for (const [name, value] of sample.formData) {
243
+ form.append(name, value);
244
+ }
245
+ return form;
246
+ };
247
+
158
248
  /** A one-line text element for the response/error regions. */
159
249
  const line = (className: string, text: string): HTMLElement => {
160
250
  const el = document.createElement("div");
@@ -302,9 +392,11 @@ export const initPlayground = (root: HTMLElement): void => {
302
392
  }
303
393
  // An emptied editor means "no body" (see `bodyFor`), not invalid JSON —
304
394
  // reporting a syntax error there would block a send the request builder is
305
- // perfectly happy to make.
395
+ // perfectly happy to make. A raw media type (`text/plain`, XML) isn't
396
+ // JSON at all, so it isn't checked as JSON either.
397
+ const raw = bodyEncoding(model.body?.contentType ?? "") === "raw";
306
398
  const errors =
307
- bodyArea.value.trim() === ""
399
+ raw || bodyArea.value.trim() === ""
308
400
  ? []
309
401
  : validateJson(bodyArea.value, model.body?.schema);
310
402
  bodyErrors.textContent = "";
@@ -316,7 +408,7 @@ export const initPlayground = (root: HTMLElement): void => {
316
408
 
317
409
  /** Restore remembered credentials into the inputs and re-check the box. */
318
410
  const restoreAuth = (): void => {
319
- const stored = localStorage.getItem(storageKey);
411
+ const stored = readStored(storageKey);
320
412
  if (!stored) {
321
413
  return;
322
414
  }
@@ -355,7 +447,7 @@ export const initPlayground = (root: HTMLElement): void => {
355
447
  }
356
448
  } catch {
357
449
  // A corrupt entry (older format, manual edit) must not break init.
358
- localStorage.removeItem(storageKey);
450
+ removeStored(storageKey);
359
451
  }
360
452
  };
361
453
 
@@ -374,12 +466,12 @@ export const initPlayground = (root: HTMLElement): void => {
374
466
  )
375
467
  );
376
468
  const table = document.createElement("table");
377
- table.className = "w-full text-left text-xs";
469
+ table.className = "w-full text-start text-xs";
378
470
  for (const [name, value] of res.headers) {
379
471
  const row = document.createElement("tr");
380
472
  const header = document.createElement("th");
381
473
  header.setAttribute("scope", "row");
382
- header.className = "pr-3 align-top font-medium text-muted-foreground";
474
+ header.className = "pe-3 align-top font-medium text-muted-foreground";
383
475
  header.textContent = name;
384
476
  const cell = document.createElement("td");
385
477
  cell.className = "break-all font-mono text-foreground";
@@ -423,6 +515,11 @@ export const initPlayground = (root: HTMLElement): void => {
423
515
  if (!response || sending) {
424
516
  return;
425
517
  }
518
+ if (fetchRefusesMethod(model.method)) {
519
+ response.textContent = "";
520
+ response.append(line(ERROR_TEXT, TRACE_MESSAGE));
521
+ return;
522
+ }
426
523
  if (validateBody().length > 0) {
427
524
  return;
428
525
  }
@@ -464,11 +561,7 @@ export const initPlayground = (root: HTMLElement): void => {
464
561
  sample.url
465
562
  )}`
466
563
  : sample.url;
467
- // A `Cookie` header can still arrive via a spec-declared header parameter;
468
- // the browser would silently drop the forbidden name anyway, so strip it
469
- // rather than earn a console warning.
470
- const headers = { ...sample.headers };
471
- delete headers.Cookie;
564
+ const headers = sendHeaders(sample, proxy);
472
565
  // fetch surfaces an invalid URL or header value as the same TypeError a
473
566
  // CORS rejection produces, and the catch below would misdiagnose it as the
474
567
  // CORS wall. Both are validated here, where the real error can be shown,
@@ -496,13 +589,7 @@ export const initPlayground = (root: HTMLElement): void => {
496
589
  const start = performance.now();
497
590
  try {
498
591
  const res = await fetch(url, {
499
- // fetch throws a synchronous TypeError for a GET/HEAD with a body —
500
- // which the catch below would mislabel as CORS. A spec that declares
501
- // a GET requestBody keeps its samples, but the live send drops it.
502
- body:
503
- sample.method === "GET" || sample.method === "HEAD"
504
- ? undefined
505
- : sample.body,
592
+ body: sendBody(sample),
506
593
  headers,
507
594
  method: sample.method,
508
595
  // A `TimeoutError` DOMException is not a TypeError, so it reads as a
@@ -548,9 +635,9 @@ export const initPlayground = (root: HTMLElement): void => {
548
635
  }
549
636
  if (remember && target === remember) {
550
637
  if (remember.checked) {
551
- localStorage.setItem(storageKey, JSON.stringify(collectAuth()));
638
+ writeStored(storageKey, JSON.stringify(collectAuth()));
552
639
  } else {
553
- localStorage.removeItem(storageKey);
640
+ removeStored(storageKey);
554
641
  }
555
642
  } else if (
556
643
  remember?.checked &&
@@ -558,7 +645,7 @@ export const initPlayground = (root: HTMLElement): void => {
558
645
  target.dataset.authUsername !== undefined ||
559
646
  target.dataset.authPassword !== undefined)
560
647
  ) {
561
- localStorage.setItem(storageKey, JSON.stringify(collectAuth()));
648
+ writeStored(storageKey, JSON.stringify(collectAuth()));
562
649
  }
563
650
  syncSamples();
564
651
  };