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
package/src/og/card.ts CHANGED
@@ -2,7 +2,13 @@ import { readFile } from "node:fs/promises";
2
2
 
3
3
  import { render } from "takumi-js";
4
4
  import type { RenderOptions } from "takumi-js";
5
- import { container, googleFonts, image, text } from "takumi-js/helpers";
5
+ import {
6
+ collectCodepoints,
7
+ container,
8
+ googleFonts,
9
+ image,
10
+ text,
11
+ } from "takumi-js/helpers";
6
12
  import type { FontSubset, GoogleFontFamily, Node } from "takumi-js/helpers";
7
13
 
8
14
  import { svgDimensions } from "../core/svg-dimensions.ts";
@@ -42,6 +48,9 @@ export type OgFont =
42
48
  }
43
49
  | OgLocalFont;
44
50
 
51
+ /** An OG font fetched from Google Fonts (a bare family name or the object form). */
52
+ export type OgGoogleFont = Exclude<OgFont, OgLocalFont>;
53
+
45
54
  /**
46
55
  * Which loaded family each card role renders in. Takumi still falls back
47
56
  * across every loaded font per glyph, so a family that misses a script
@@ -54,15 +63,16 @@ export interface OgFontFamilies {
54
63
  title?: string;
55
64
  }
56
65
 
57
- // Named presets resolve from the theme's own OKLCH table — Takumi parses the
58
- // full CSS color grammar, so the card renders exactly the accent the site
59
- // shows (a separate hand-synced hex palette used to drift: the card's "blue"
60
- // was Tailwind's, not Blume's). Anything else is handed to Takumi as-is, and
61
- // a genuinely malformed value fails the build with a parse error naming it.
66
+ // Named presets resolve from the theme's own OKLCH table, in their light
67
+ // shade since the card is light — Takumi parses the full CSS color grammar,
68
+ // so the card renders exactly the accent the site shows (a separate
69
+ // hand-synced hex palette used to drift: the card's "blue" was Tailwind's,
70
+ // not Blume's). Anything else is handed to Takumi as-is, and a genuinely
71
+ // malformed value fails the build with a parse error naming it.
62
72
  // `isAccentPreset` keeps a preset name like "constructor" from resolving up
63
73
  // the prototype chain.
64
74
  const resolveAccent = (accent: string): string =>
65
- isAccentPreset(accent) ? ACCENTS[accent] : accent;
75
+ isAccentPreset(accent) ? ACCENTS[accent].light : accent;
66
76
 
67
77
  export interface OgCardPalette {
68
78
  accent?: string;
@@ -106,6 +116,12 @@ export interface OgCardOptions {
106
116
  * from disk instead of Google Fonts.
107
117
  */
108
118
  fonts?: OgFont[];
119
+ /**
120
+ * Script fallbacks, tried after `fonts` for glyphs nothing else draws. A
121
+ * card loads them only when its text needs one, so a Latin-only card never
122
+ * fetches them — see {@link fallbackSubsets}.
123
+ */
124
+ fallbacks?: OgGoogleFont[];
109
125
  /** Per-role families from the loaded fonts (title vs body text). */
110
126
  families?: OgFontFamilies;
111
127
  }
@@ -145,9 +161,7 @@ const fontSubsetCache = new Map<string, Promise<FontSubset[]>>();
145
161
  * fetch failure rejects, failing the build with the cause rather than silently
146
162
  * shipping tofu — the same fail-fast the OG accent relies on.
147
163
  */
148
- const loadFonts = (
149
- fonts: Exclude<OgFont, OgLocalFont>[]
150
- ): Promise<FontSubset[]> => {
164
+ const loadFonts = (fonts: OgGoogleFont[]): Promise<FontSubset[]> => {
151
165
  const key = JSON.stringify(fonts);
152
166
  let pending = fontSubsetCache.get(key);
153
167
  if (!pending) {
@@ -160,6 +174,113 @@ const loadFonts = (
160
174
  return pending;
161
175
  };
162
176
 
177
+ /**
178
+ * The code points Takumi's built-in Geist draws: whitespace, printable ASCII,
179
+ * Latin-1 except `µ`, a few Latin extras French needs, and the typographic
180
+ * punctuation English copy leans on. Probed against the renderer, and
181
+ * `og-card.test.ts` renders every one, so a Takumi upgrade that drops a glyph
182
+ * fails there instead of shipping tofu.
183
+ */
184
+ export const BUILT_IN_GLYPHS: [number, number][] = [
185
+ [0x09, 0x0d],
186
+ [0x20, 0x7e],
187
+ [0xa0, 0xb4],
188
+ [0xb6, 0xff],
189
+ [0x1_31, 0x1_31],
190
+ [0x1_52, 0x1_53],
191
+ [0x1_78, 0x1_78],
192
+ [0x20_00, 0x20_0f],
193
+ [0x20_13, 0x20_14],
194
+ [0x20_18, 0x20_1a],
195
+ [0x20_1c, 0x20_1e],
196
+ [0x20_20, 0x20_22],
197
+ [0x20_26, 0x20_26],
198
+ [0x20_28, 0x20_30],
199
+ [0x20_32, 0x20_33],
200
+ [0x20_39, 0x20_3a],
201
+ [0x20_44, 0x20_44],
202
+ [0x20_5f, 0x20_6f],
203
+ [0x20_ac, 0x20_ac],
204
+ [0x21_22, 0x21_22],
205
+ [0x21_91, 0x21_91],
206
+ [0x21_93, 0x21_93],
207
+ [0x22_12, 0x22_12],
208
+ ];
209
+
210
+ /** Emoji render as Twemoji images, never from a font. */
211
+ const EMOJI = /\p{Emoji_Presentation}|\p{Emoji_Component}/u;
212
+
213
+ /** Whether a card draws `codePoint` without any loaded font. */
214
+ const drawnWithoutFonts = (codePoint: number): boolean =>
215
+ BUILT_IN_GLYPHS.some(([from, to]) => codePoint >= from && codePoint <= to) ||
216
+ EMOJI.test(String.fromCodePoint(codePoint));
217
+
218
+ /** Whether `subset` claims `codePoint` (a subset without ranges claims all). */
219
+ const subsetClaims = (subset: FontSubset, codePoint: number): boolean =>
220
+ subset.ranges.length === 0 ||
221
+ subset.ranges.some(([from, to]) => codePoint >= from && codePoint <= to);
222
+
223
+ /** `family`'s subsets that claim any of `codePoints`. */
224
+ const familySubsets = (
225
+ subsets: FontSubset[],
226
+ family: string,
227
+ codePoints: number[]
228
+ ): FontSubset[] =>
229
+ subsets.filter(
230
+ (subset) =>
231
+ subset.subsetOf === family &&
232
+ codePoints.some((codePoint) => subsetClaims(subset, codePoint))
233
+ );
234
+
235
+ /**
236
+ * The scripts only the CJK families draw. Google slices those fonts by the
237
+ * glyphs each one really has, so the first family claiming such a character
238
+ * draws it. Other claims follow Google's shared subset definitions whatever
239
+ * the font holds — every family's `latin` claims `※`, Arabic's `symbols`
240
+ * claims `→` — so a claim there proves nothing.
241
+ */
242
+ const CJK_SCRIPTS =
243
+ /[\p{Script=Bopomofo}\p{Script=Han}\p{Script=Hangul}\p{Script=Hiragana}\p{Script=Katakana}]/u;
244
+
245
+ /** Whether a claim on `codePoint` means the claiming family draws it. */
246
+ const claimIsExact = (codePoint: number): boolean =>
247
+ CJK_SCRIPTS.test(String.fromCodePoint(codePoint));
248
+
249
+ /**
250
+ * The fallback subsets a card's text needs. Nothing loads, and nothing is
251
+ * fetched, when the built-in font draws every glyph, so a Latin-only card
252
+ * builds offline. Otherwise only subsets claiming a glyph beyond the built-in
253
+ * font load (not every family's `latin`), and a CJK character stops at the
254
+ * first family that claims it: all four CJK families claim most Han, so left
255
+ * to `render` each would download its matching slices although only the
256
+ * first ever paints them.
257
+ */
258
+ const fallbackSubsets = async (
259
+ fallbacks: OgGoogleFont[] | undefined,
260
+ node: Node
261
+ ): Promise<FontSubset[]> => {
262
+ let missing = [...collectCodepoints(node)].filter(
263
+ (codePoint) => !drawnWithoutFonts(codePoint)
264
+ );
265
+ if (!fallbacks?.length || missing.length === 0) {
266
+ return [];
267
+ }
268
+ const subsets = await loadFonts(fallbacks);
269
+ const needed: FontSubset[] = [];
270
+ for (const family of new Set(subsets.map((subset) => subset.subsetOf))) {
271
+ const matching = familySubsets(subsets, family, missing);
272
+ needed.push(...matching);
273
+ missing = missing.filter(
274
+ (codePoint) =>
275
+ !(
276
+ claimIsExact(codePoint) &&
277
+ matching.some((subset) => subsetClaims(subset, codePoint))
278
+ )
279
+ );
280
+ }
281
+ return needed;
282
+ };
283
+
163
284
  /**
164
285
  * A lazy loader for a local font file, matching the shape `render` accepts
165
286
  * alongside Google subsets. Keyed by path so the shared renderer reads and
@@ -422,7 +543,11 @@ export const renderOgImage = async (
422
543
  const fontSubsets = googleFamilies.length
423
544
  ? await loadFonts(googleFamilies)
424
545
  : [];
425
- const cardFonts = [...fontSubsets, ...localFonts.map(localFontLoader)];
546
+ const cardFonts = [
547
+ ...fontSubsets,
548
+ ...localFonts.map(localFontLoader),
549
+ ...(await fallbackSubsets(options.fallbacks, node)),
550
+ ];
426
551
 
427
552
  return render(node, {
428
553
  fonts: cardFonts.length ? cardFonts : undefined,
package/src/og/derive.ts CHANGED
@@ -2,9 +2,9 @@
2
2
  * Bridges `theme.fonts` into the OG card renderer. A site that explicitly
3
3
  * picks its typefaces gets matching cards (and non-Latin coverage) without
4
4
  * configuring `seo.og.fonts`; untouched defaults derive nothing, so plain
5
- * sites keep Takumi's built-in font and gain no build-time font fetch. Locales
6
- * in scripts that font can't draw (Japanese, Hindi, Russian, …) add a Noto
7
- * fallback for their script either way.
5
+ * sites keep Takumi's built-in font. Either way every card gets a Noto
6
+ * fallback stack for the scripts that font can't draw (Japanese, Hindi,
7
+ * Russian, …), which it fetches only when its text needs one.
8
8
  */
9
9
 
10
10
  import { existsSync } from "node:fs";
@@ -19,9 +19,10 @@ import type {
19
19
  import { GOOGLE_FONTS, isFontSlug, localeFontSubsets } from "../theme/fonts.ts";
20
20
  import type { OgFont, OgFontFamilies, OgLocalFont } from "./card.ts";
21
21
 
22
- /** Fonts plus per-role families for the generated OG endpoint. */
22
+ /** Fonts, script fallbacks, and per-role families for the OG endpoint. */
23
23
  export interface DerivedOgFonts {
24
24
  families?: OgFontFamilies;
25
+ fallbacks?: OgFallbackFont[];
25
26
  fonts: OgFont[];
26
27
  }
27
28
 
@@ -165,17 +166,17 @@ export const deriveOgFonts = (
165
166
  /**
166
167
  * The family Takumi renders a card in when no font is loaded: its embedded
167
168
  * Geist, which covers Latin only. Naming it keeps a card's Latin text in that
168
- * face once locale fallbacks are loaded, since Takumi otherwise tries loaded
169
- * fonts first; a glyph Geist lacks still falls back to them. Were Takumi to
170
- * rename it, the name would resolve to nothing and cards would fall back to
171
- * the loaded fonts, never to tofu.
169
+ * face once fallbacks are loaded, since Takumi otherwise tries loaded fonts
170
+ * first; a glyph Geist lacks still falls back to them. Were Takumi to rename
171
+ * it, the name would resolve to nothing and cards would fall back to the
172
+ * loaded fonts, never to tofu.
172
173
  */
173
174
  const BUILT_IN_FAMILY = "Geist";
174
175
 
175
176
  /**
176
177
  * The Google Noto family that draws each language's script, keyed by BCP 47
177
178
  * language subtag, for scripts beyond the Latin, Cyrillic, Greek, and
178
- * Vietnamese that `Noto Sans` covers (see {@link localeOgFonts}). Keep keys
179
+ * Vietnamese that `Noto Sans` covers (see {@link localeFamily}). Keep keys
179
180
  * alphabetical.
180
181
  */
181
182
  const SCRIPT_FAMILIES = {
@@ -219,8 +220,8 @@ const isScriptLanguage = (
219
220
  ): language is keyof typeof SCRIPT_FAMILIES =>
220
221
  Object.hasOwn(SCRIPT_FAMILIES, language);
221
222
 
222
- /** A locale fallback: a Google family at the weights the card renders. */
223
- export interface LocaleOgFont {
223
+ /** A script fallback: a Google family at the weights the card renders. */
224
+ export interface OgFallbackFont {
224
225
  name: string;
225
226
  weight: number[];
226
227
  }
@@ -246,16 +247,28 @@ const localeFamily = (locale: string): string | null => {
246
247
  };
247
248
 
248
249
  /**
249
- * The fallback card fonts the configured locales' scripts need, at the card's
250
- * weights. Takumi's built-in font covers only basic Latin, so without these a
251
- * Japanese or Hindi page's card renders every glyph as tofu. The renderer
252
- * fetches only the glyph subsets a card's text uses, so an English card on
253
- * the same site pulls nothing extra.
250
+ * Every script's fallback, in the order a card tries them: `Noto Sans` for
251
+ * extended Latin, Cyrillic, Greek, and Vietnamese, then a family per script.
252
+ * The CJK families all draw Han ideographs, so the first one wins them —
253
+ * Japanese forms, unless a configured locale moves its own family ahead.
254
254
  */
255
- export const localeOgFonts = (locales: string[]): LocaleOgFont[] => {
255
+ const DEFAULT_FALLBACKS = [
256
+ "Noto Sans",
257
+ ...new Set(Object.values(SCRIPT_FAMILIES)),
258
+ "Noto Sans TC",
259
+ ];
260
+
261
+ /**
262
+ * The card's script fallbacks, at the card's weights: each configured
263
+ * locale's family first, so a Chinese site draws Han in Chinese forms, then
264
+ * every other script's, so a page in a language the site never configured
265
+ * still renders instead of tofu. Takumi's built-in font covers only basic
266
+ * Latin; the card fetches these only when its text needs a glyph beyond it,
267
+ * and then only the families and subsets that glyph needs.
268
+ */
269
+ export const ogFallbackFonts = (locales: string[]): OgFallbackFont[] => {
256
270
  const names = new Set<string>();
257
- for (const locale of locales) {
258
- const family = localeFamily(locale);
271
+ for (const family of [...locales.map(localeFamily), ...DEFAULT_FALLBACKS]) {
259
272
  if (family) {
260
273
  names.add(family);
261
274
  }
@@ -272,27 +285,24 @@ const ogFontName = (font: OgFont): string =>
272
285
  isGoogleFamilyName(font) ? font : font.name;
273
286
 
274
287
  /**
275
- * `derived` plus the locale fallbacks it doesn't already load. A card that
288
+ * `derived` plus the script fallbacks it doesn't already load. A card that
276
289
  * names no family is pinned to {@link BUILT_IN_FAMILY}, so its Latin text
277
290
  * keeps the face it had before any fallback was loaded.
278
291
  */
279
- const withLocaleFonts = (
292
+ const withFallbacks = (
280
293
  derived: DerivedOgFonts,
281
294
  locales: string[]
282
295
  ): DerivedOgFonts => {
283
296
  const loaded = new Set(derived.fonts.map(ogFontName));
284
- const fallbacks = localeOgFonts(locales).filter(
285
- (font) => !loaded.has(ogFontName(font))
286
- );
287
- if (fallbacks.length === 0) {
288
- return derived;
289
- }
290
297
  return {
298
+ fallbacks: ogFallbackFonts(locales).filter(
299
+ (font) => !loaded.has(font.name)
300
+ ),
291
301
  families: derived.families ?? {
292
302
  body: BUILT_IN_FAMILY,
293
303
  title: BUILT_IN_FAMILY,
294
304
  },
295
- fonts: [...derived.fonts, ...fallbacks],
305
+ fonts: derived.fonts,
296
306
  };
297
307
  };
298
308
 
@@ -307,8 +317,8 @@ export const resolveOgFontSources = (fonts: OgFont[], root: string): OgFont[] =>
307
317
  * always wins (including `[]` to opt out, keeping the card's role styling
308
318
  * untouched); otherwise a site that explicitly set `theme.fonts` gets its
309
319
  * display/body fonts derived so cards match the site without extra config,
310
- * and either way the configured locales add a fallback for each script the
311
- * built-in font can't draw.
320
+ * and either way every card gets the script fallbacks, the configured
321
+ * locales' first.
312
322
  */
313
323
  export const resolveOgFonts = (
314
324
  options: {
@@ -328,7 +338,7 @@ export const resolveOgFonts = (
328
338
  const derived = options.themeFontsConfigured
329
339
  ? deriveOgFonts(options.themeFonts, root)
330
340
  : { fonts: [] };
331
- return withLocaleFonts(derived, options.locales ?? []);
341
+ return withFallbacks(derived, options.locales ?? []);
332
342
  };
333
343
 
334
344
  /**
package/src/og/index.ts CHANGED
@@ -11,4 +11,5 @@ export type {
11
11
  OgCardPalette,
12
12
  OgFont,
13
13
  OgFontFamilies,
14
+ OgGoogleFont,
14
15
  } from "./card.ts";
@@ -46,7 +46,8 @@ export interface AsyncApiChannelObject {
46
46
  messages?: Record<string, AsyncApiRefLike>;
47
47
  parameters?: Record<string, AsyncApiRefLike>;
48
48
  servers?: AsyncApiRefLike[];
49
- bindings?: Record<string, Record<string, AsyncApiSpecValue>>;
49
+ /** Protocol-keyed binding objects, or a `$ref` to a components entry. */
50
+ bindings?: Record<string, AsyncApiSpecValue>;
50
51
  [key: string]: AsyncApiSpecValue;
51
52
  }
52
53
 
@@ -61,7 +62,8 @@ export interface AsyncApiOperationObject {
61
62
  tags?: { name?: string; description?: string }[];
62
63
  security?: AsyncApiRefLike[];
63
64
  messages?: AsyncApiRefLike[];
64
- bindings?: Record<string, Record<string, AsyncApiSpecValue>>;
65
+ /** Protocol-keyed binding objects, or a `$ref` to a components entry. */
66
+ bindings?: Record<string, AsyncApiSpecValue>;
65
67
  [key: string]: AsyncApiSpecValue;
66
68
  }
67
69
 
@@ -46,6 +46,9 @@ export type HttpMethod = (typeof HTTP_METHODS)[number];
46
46
  /** Group used for operations that declare no tag. */
47
47
  const UNTAGGED = "Operations";
48
48
 
49
+ /** Group used for webhooks that declare no tag. */
50
+ const UNTAGGED_WEBHOOKS = "Webhooks";
51
+
49
52
  /** A lower-case letter or digit followed by a capital: `addPet`, `v2List`. */
50
53
  const CASE_BOUNDARY = /(?<before>[\p{Ll}\p{N}])(?<capital>\p{Lu})/gu;
51
54
  /** The last capital of an acronym before a capitalized word: `HTTPResponse`. */
@@ -101,6 +104,12 @@ export interface ApiOperationRef {
101
104
  deprecated: boolean;
102
105
  /** The channel the operation acts on (AsyncAPI only). */
103
106
  channelId?: string;
107
+ /**
108
+ * An OpenAPI 3.1 webhook: a request the API sends to the reader's endpoint
109
+ * rather than one it serves. `path` then holds the webhook's name, the key
110
+ * it has under the document's `webhooks`.
111
+ */
112
+ webhook?: true;
104
113
  }
105
114
 
106
115
  /**
@@ -134,8 +143,8 @@ export interface ApiSpecData {
134
143
  /** Operations keyed by {@link ApiOperationRef.key}. */
135
144
  operations: Record<string, ApiOperationRef>;
136
145
  tags: ApiTagRef[];
137
- /** Code-sample languages to render per operation. */
138
- codeSamples: string[];
146
+ /** Code-sample languages to render per operation; `false` for none. */
147
+ codeSamples: string[] | false;
139
148
  /** Whether nested schema rows start expanded. */
140
149
  expandSchemas: boolean;
141
150
  /**
@@ -260,9 +269,18 @@ const isOperation = (
260
269
  value: OperationObject | undefined
261
270
  ): value is OperationObject => typeof value === "object" && value !== null;
262
271
 
263
- /** A declared tag whose `name` really is a string at runtime, type aside. */
264
- const hasTagName = (tag: SpecTag): tag is SpecTag =>
265
- typeof tag.name === "string";
272
+ /** Whether a spec value is text YAML may have read as a number. */
273
+ const isText = <Value>(value: Value): value is Value & (string | number) =>
274
+ typeof value === "string" || typeof value === "number";
275
+
276
+ /**
277
+ * A spec field that names something, as a string. YAML reads an unquoted
278
+ * `operationId: 404` or `tags: [2024]` as a number, which still names the
279
+ * operation or tag; anything else a hand-written spec puts there counts as
280
+ * absent.
281
+ */
282
+ const specText = <Value>(value: Value): string | undefined =>
283
+ isText(value) ? String(value) : undefined;
266
284
 
267
285
  /**
268
286
  * Assign each distinct tag name a unique slug. `slugify` can collapse
@@ -292,12 +310,14 @@ export const tagSlugger = (): ((name: string) => string) => {
292
310
  };
293
311
  };
294
312
 
313
+ /** One entry of a document's `paths` or `webhooks` map. */
314
+ type PathItemEntry =
315
+ | NonNullable<ApiDocument["paths"]>[string]
316
+ | NonNullable<ApiDocument["webhooks"]>[string];
317
+
295
318
  /** An operation before the collector assigns its unique key and route. */
296
319
  type CollectedOperation = Omit<ApiOperationRef, "route" | "tagSlug">;
297
320
 
298
- /** A document's declared tag entry (`tags[n]`). */
299
- type SpecTag = NonNullable<ApiDocument["tags"]>[number];
300
-
301
321
  /** The flattened output both extractors produce. */
302
322
  export interface CollectedOperations {
303
323
  operations: ApiOperationRef[];
@@ -380,10 +400,10 @@ export const operationCollector = (
380
400
 
381
401
  /**
382
402
  * Flatten a 3.1 document into a route-mapped operation list and its ordered
383
- * tags. Operations inherit the first tag they declare; keys are de-duplicated so
384
- * a repeated `operationId` still yields distinct routes. `warnings` reports
385
- * anything skipped (a `$ref` path item, webhooks), so missing operations
386
- * aren't silent.
403
+ * tags, webhooks included. Operations inherit the first tag they declare; keys
404
+ * are de-duplicated so a repeated `operationId` still yields distinct routes.
405
+ * `warnings` reports anything skipped (a `$ref` path item), so missing
406
+ * operations aren't silent.
387
407
  */
388
408
  export const extractOperations = (
389
409
  document: ApiDocument,
@@ -391,48 +411,67 @@ export const extractOperations = (
391
411
  ): ExtractedOperations => {
392
412
  const warnings: string[] = [];
393
413
  const tagMeta = new Map(
394
- (document.tags ?? [])
395
- .filter(hasTagName)
396
- .map((tag) => [tag.name, tag.description ?? ""])
414
+ (document.tags ?? []).flatMap((tag): [string, string][] => {
415
+ const name = specText(tag.name);
416
+ return name === undefined ? [] : [[name, tag.description ?? ""]];
417
+ })
397
418
  );
398
419
  const collector = operationCollector(baseRoute, tagMeta);
399
420
 
400
- for (const [path, item] of Object.entries(document.paths ?? {})) {
421
+ // A path item's operations, filed under `name`: a path for `paths`, the
422
+ // webhook's key for `webhooks`.
423
+ const addPathItem = (
424
+ name: string,
425
+ item: PathItemEntry,
426
+ webhook: boolean
427
+ ): void => {
401
428
  // A parsed spec can carry a null path item despite the type; skip it.
402
429
  if (!item) {
403
- continue;
430
+ return;
404
431
  }
405
432
  if ("$ref" in item) {
406
433
  warnings.push(
407
- `Path "${path}" is a $ref to a shared path item; referenced path items are not resolved, so its operations are missing from the reference. Inline the path item under "paths" to render it.`
434
+ webhook
435
+ ? `Webhook "${name}" is a $ref to a shared path item; referenced path items are not resolved, so it is missing from the reference. Inline the path item under "webhooks" to render it.`
436
+ : `Path "${name}" is a $ref to a shared path item; referenced path items are not resolved, so its operations are missing from the reference. Inline the path item under "paths" to render it.`
408
437
  );
409
- continue;
438
+ return;
410
439
  }
411
440
  for (const method of HTTP_METHODS) {
412
441
  const operation = item[method];
413
442
  if (!isOperation(operation)) {
414
443
  continue;
415
444
  }
416
- collector.add({
445
+ const operationId = specText(operation.operationId);
446
+ const entry: CollectedOperation = {
417
447
  deprecated: operation.deprecated ?? false,
418
- description: operation.description ?? "",
419
- key: operationKey(method, path, operation.operationId),
448
+ description: specText(operation.description) ?? "",
449
+ // A webhook without an operationId is keyed by its name, which is
450
+ // already an identifier (`newPet` -> `new-pet`).
451
+ key: operationKey(
452
+ method,
453
+ name,
454
+ operationId ?? (webhook ? name : undefined)
455
+ ),
420
456
  method,
421
- operationId: operation.operationId,
422
- path,
423
- summary: operation.summary ?? "",
424
- tag: operation.tags?.[0] ?? UNTAGGED,
425
- });
457
+ operationId,
458
+ path: name,
459
+ summary: specText(operation.summary) ?? "",
460
+ tag:
461
+ specText(operation.tags?.[0]) ??
462
+ (webhook ? UNTAGGED_WEBHOOKS : UNTAGGED),
463
+ };
464
+ collector.add(webhook ? { ...entry, webhook: true } : entry);
426
465
  }
427
- }
466
+ };
428
467
 
429
- // OpenAPI 3.1 webhooks (requests the API sends, not ones it serves) have no
430
- // page renderer; say so rather than dropping them silently.
431
- const webhooks = Object.keys(document.webhooks ?? {});
432
- if (webhooks.length > 0) {
433
- warnings.push(
434
- `The spec declares ${webhooks.length === 1 ? "a webhook" : `${webhooks.length} webhooks`} under "webhooks" (${webhooks.join(", ")}); webhooks aren't rendered, so they are missing from the reference.`
435
- );
468
+ for (const [path, item] of Object.entries(document.paths ?? {})) {
469
+ addPathItem(path, item, false);
470
+ }
471
+ // OpenAPI 3.1 webhooks: requests the API sends rather than serves. They
472
+ // follow the paths, so an untagged webhook group sits after the endpoints.
473
+ for (const [name, item] of Object.entries(document.webhooks ?? {})) {
474
+ addPathItem(name, item, true);
436
475
  }
437
476
 
438
477
  return { ...collector.finish(), warnings };
@@ -450,7 +489,9 @@ export const operationObject = (
450
489
  // (AsyncAPI documents go to `asyncApiOperationObject`), so the spec's
451
490
  // document is the OpenAPI shape.
452
491
  const document = spec.document as ApiDocument;
453
- const item = document.paths?.[ref.path];
492
+ const item = ref.webhook
493
+ ? document.webhooks?.[ref.path]
494
+ : document.paths?.[ref.path];
454
495
  const operation = method === undefined ? undefined : item?.[method];
455
496
  return isOperation(operation) ? operation : undefined;
456
497
  };