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
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
@@ -106,6 +115,12 @@ export interface OgCardOptions {
106
115
  * from disk instead of Google Fonts.
107
116
  */
108
117
  fonts?: OgFont[];
118
+ /**
119
+ * Script fallbacks, tried after `fonts` for glyphs nothing else draws. A
120
+ * card loads them only when its text needs one, so a Latin-only card never
121
+ * fetches them — see {@link fallbackSubsets}.
122
+ */
123
+ fallbacks?: OgGoogleFont[];
109
124
  /** Per-role families from the loaded fonts (title vs body text). */
110
125
  families?: OgFontFamilies;
111
126
  }
@@ -145,9 +160,7 @@ const fontSubsetCache = new Map<string, Promise<FontSubset[]>>();
145
160
  * fetch failure rejects, failing the build with the cause rather than silently
146
161
  * shipping tofu — the same fail-fast the OG accent relies on.
147
162
  */
148
- const loadFonts = (
149
- fonts: Exclude<OgFont, OgLocalFont>[]
150
- ): Promise<FontSubset[]> => {
163
+ const loadFonts = (fonts: OgGoogleFont[]): Promise<FontSubset[]> => {
151
164
  const key = JSON.stringify(fonts);
152
165
  let pending = fontSubsetCache.get(key);
153
166
  if (!pending) {
@@ -160,6 +173,113 @@ const loadFonts = (
160
173
  return pending;
161
174
  };
162
175
 
176
+ /**
177
+ * The code points Takumi's built-in Geist draws: whitespace, printable ASCII,
178
+ * Latin-1 except `µ`, a few Latin extras French needs, and the typographic
179
+ * punctuation English copy leans on. Probed against the renderer, and
180
+ * `og-card.test.ts` renders every one, so a Takumi upgrade that drops a glyph
181
+ * fails there instead of shipping tofu.
182
+ */
183
+ export const BUILT_IN_GLYPHS: [number, number][] = [
184
+ [0x09, 0x0d],
185
+ [0x20, 0x7e],
186
+ [0xa0, 0xb4],
187
+ [0xb6, 0xff],
188
+ [0x1_31, 0x1_31],
189
+ [0x1_52, 0x1_53],
190
+ [0x1_78, 0x1_78],
191
+ [0x20_00, 0x20_0f],
192
+ [0x20_13, 0x20_14],
193
+ [0x20_18, 0x20_1a],
194
+ [0x20_1c, 0x20_1e],
195
+ [0x20_20, 0x20_22],
196
+ [0x20_26, 0x20_26],
197
+ [0x20_28, 0x20_30],
198
+ [0x20_32, 0x20_33],
199
+ [0x20_39, 0x20_3a],
200
+ [0x20_44, 0x20_44],
201
+ [0x20_5f, 0x20_6f],
202
+ [0x20_ac, 0x20_ac],
203
+ [0x21_22, 0x21_22],
204
+ [0x21_91, 0x21_91],
205
+ [0x21_93, 0x21_93],
206
+ [0x22_12, 0x22_12],
207
+ ];
208
+
209
+ /** Emoji render as Twemoji images, never from a font. */
210
+ const EMOJI = /\p{Emoji_Presentation}|\p{Emoji_Component}/u;
211
+
212
+ /** Whether a card draws `codePoint` without any loaded font. */
213
+ const drawnWithoutFonts = (codePoint: number): boolean =>
214
+ BUILT_IN_GLYPHS.some(([from, to]) => codePoint >= from && codePoint <= to) ||
215
+ EMOJI.test(String.fromCodePoint(codePoint));
216
+
217
+ /** Whether `subset` claims `codePoint` (a subset without ranges claims all). */
218
+ const subsetClaims = (subset: FontSubset, codePoint: number): boolean =>
219
+ subset.ranges.length === 0 ||
220
+ subset.ranges.some(([from, to]) => codePoint >= from && codePoint <= to);
221
+
222
+ /** `family`'s subsets that claim any of `codePoints`. */
223
+ const familySubsets = (
224
+ subsets: FontSubset[],
225
+ family: string,
226
+ codePoints: number[]
227
+ ): FontSubset[] =>
228
+ subsets.filter(
229
+ (subset) =>
230
+ subset.subsetOf === family &&
231
+ codePoints.some((codePoint) => subsetClaims(subset, codePoint))
232
+ );
233
+
234
+ /**
235
+ * The scripts only the CJK families draw. Google slices those fonts by the
236
+ * glyphs each one really has, so the first family claiming such a character
237
+ * draws it. Other claims follow Google's shared subset definitions whatever
238
+ * the font holds — every family's `latin` claims `※`, Arabic's `symbols`
239
+ * claims `→` — so a claim there proves nothing.
240
+ */
241
+ const CJK_SCRIPTS =
242
+ /[\p{Script=Bopomofo}\p{Script=Han}\p{Script=Hangul}\p{Script=Hiragana}\p{Script=Katakana}]/u;
243
+
244
+ /** Whether a claim on `codePoint` means the claiming family draws it. */
245
+ const claimIsExact = (codePoint: number): boolean =>
246
+ CJK_SCRIPTS.test(String.fromCodePoint(codePoint));
247
+
248
+ /**
249
+ * The fallback subsets a card's text needs. Nothing loads, and nothing is
250
+ * fetched, when the built-in font draws every glyph, so a Latin-only card
251
+ * builds offline. Otherwise only subsets claiming a glyph beyond the built-in
252
+ * font load (not every family's `latin`), and a CJK character stops at the
253
+ * first family that claims it: all four CJK families claim most Han, so left
254
+ * to `render` each would download its matching slices although only the
255
+ * first ever paints them.
256
+ */
257
+ const fallbackSubsets = async (
258
+ fallbacks: OgGoogleFont[] | undefined,
259
+ node: Node
260
+ ): Promise<FontSubset[]> => {
261
+ let missing = [...collectCodepoints(node)].filter(
262
+ (codePoint) => !drawnWithoutFonts(codePoint)
263
+ );
264
+ if (!fallbacks?.length || missing.length === 0) {
265
+ return [];
266
+ }
267
+ const subsets = await loadFonts(fallbacks);
268
+ const needed: FontSubset[] = [];
269
+ for (const family of new Set(subsets.map((subset) => subset.subsetOf))) {
270
+ const matching = familySubsets(subsets, family, missing);
271
+ needed.push(...matching);
272
+ missing = missing.filter(
273
+ (codePoint) =>
274
+ !(
275
+ claimIsExact(codePoint) &&
276
+ matching.some((subset) => subsetClaims(subset, codePoint))
277
+ )
278
+ );
279
+ }
280
+ return needed;
281
+ };
282
+
163
283
  /**
164
284
  * A lazy loader for a local font file, matching the shape `render` accepts
165
285
  * alongside Google subsets. Keyed by path so the shared renderer reads and
@@ -261,11 +381,23 @@ const logoMark = (svg: string, foreground: string): Node => {
261
381
  };
262
382
 
263
383
  // Fallback mark when no SVG logo is configured: an accent tile with the brand's
264
- // initial, matching the docs favicon aesthetic.
265
- const initialMark = (accent: string, initial: string): Node =>
384
+ // initial, matching the docs favicon aesthetic. The initial takes the headline's
385
+ // family, so it matches the title rather than whichever loaded font comes first.
386
+ const initialMark = (
387
+ accent: string,
388
+ initial: string,
389
+ family: { fontFamily?: string }
390
+ ): Node =>
266
391
  container({
267
392
  children: initial
268
- ? [text(initial, { color: "#ffffff", fontSize: 32, fontWeight: 600 })]
393
+ ? [
394
+ text(initial, {
395
+ color: "#ffffff",
396
+ fontSize: 32,
397
+ fontWeight: 600,
398
+ ...family,
399
+ }),
400
+ ]
269
401
  : [],
270
402
  style: {
271
403
  alignItems: "center",
@@ -319,7 +451,11 @@ export const renderOgImage = async (
319
451
  if (logo === false) {
320
452
  return [];
321
453
  }
322
- return [logo ? logoMark(logo, foreground) : initialMark(accent, initial)];
454
+ return [
455
+ logo
456
+ ? logoMark(logo, foreground)
457
+ : initialMark(accent, initial, titleFamily),
458
+ ];
323
459
  };
324
460
  const header = container({
325
461
  children: mark(),
@@ -406,7 +542,11 @@ export const renderOgImage = async (
406
542
  const fontSubsets = googleFamilies.length
407
543
  ? await loadFonts(googleFamilies)
408
544
  : [];
409
- const cardFonts = [...fontSubsets, ...localFonts.map(localFontLoader)];
545
+ const cardFonts = [
546
+ ...fontSubsets,
547
+ ...localFonts.map(localFontLoader),
548
+ ...(await fallbackSubsets(options.fallbacks, node)),
549
+ ];
410
550
 
411
551
  return render(node, {
412
552
  fonts: cardFonts.length ? cardFonts : undefined,
package/src/og/derive.ts CHANGED
@@ -2,7 +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.
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.
6
8
  */
7
9
 
8
10
  import { existsSync } from "node:fs";
@@ -14,12 +16,13 @@ import type {
14
16
  FontValue,
15
17
  LocalFontConfig,
16
18
  } from "../theme/fonts.ts";
17
- import { GOOGLE_FONTS, isFontSlug } from "../theme/fonts.ts";
19
+ import { GOOGLE_FONTS, isFontSlug, localeFontSubsets } from "../theme/fonts.ts";
18
20
  import type { OgFont, OgFontFamilies, OgLocalFont } from "./card.ts";
19
21
 
20
- /** Fonts plus per-role families for the generated OG endpoint. */
22
+ /** Fonts, script fallbacks, and per-role families for the OG endpoint. */
21
23
  export interface DerivedOgFonts {
22
24
  families?: OgFontFamilies;
25
+ fallbacks?: OgFallbackFont[];
23
26
  fonts: OgFont[];
24
27
  }
25
28
 
@@ -160,6 +163,149 @@ export const deriveOgFonts = (
160
163
  return result;
161
164
  };
162
165
 
166
+ /**
167
+ * The family Takumi renders a card in when no font is loaded: its embedded
168
+ * Geist, which covers Latin only. Naming it keeps a card's Latin text in that
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.
173
+ */
174
+ const BUILT_IN_FAMILY = "Geist";
175
+
176
+ /**
177
+ * The Google Noto family that draws each language's script, keyed by BCP 47
178
+ * language subtag, for scripts beyond the Latin, Cyrillic, Greek, and
179
+ * Vietnamese that `Noto Sans` covers (see {@link localeFamily}). Keep keys
180
+ * alphabetical.
181
+ */
182
+ const SCRIPT_FAMILIES = {
183
+ am: "Noto Sans Ethiopic",
184
+ ar: "Noto Sans Arabic",
185
+ as: "Noto Sans Bengali",
186
+ bn: "Noto Sans Bengali",
187
+ ckb: "Noto Sans Arabic",
188
+ fa: "Noto Sans Arabic",
189
+ gu: "Noto Sans Gujarati",
190
+ he: "Noto Sans Hebrew",
191
+ hi: "Noto Sans Devanagari",
192
+ hy: "Noto Sans Armenian",
193
+ ja: "Noto Sans JP",
194
+ ka: "Noto Sans Georgian",
195
+ km: "Noto Sans Khmer",
196
+ kn: "Noto Sans Kannada",
197
+ ko: "Noto Sans KR",
198
+ lo: "Noto Sans Lao",
199
+ ml: "Noto Sans Malayalam",
200
+ mr: "Noto Sans Devanagari",
201
+ my: "Noto Sans Myanmar",
202
+ ne: "Noto Sans Devanagari",
203
+ or: "Noto Sans Oriya",
204
+ pa: "Noto Sans Gurmukhi",
205
+ ps: "Noto Sans Arabic",
206
+ sa: "Noto Sans Devanagari",
207
+ si: "Noto Sans Sinhala",
208
+ ta: "Noto Sans Tamil",
209
+ te: "Noto Sans Telugu",
210
+ th: "Noto Sans Thai",
211
+ ti: "Noto Sans Ethiopic",
212
+ ur: "Noto Sans Arabic",
213
+ yi: "Noto Sans Hebrew",
214
+ zh: "Noto Sans SC",
215
+ } satisfies Record<string, string>;
216
+
217
+ /** Type guard: does `language` have an entry in the script table? */
218
+ const isScriptLanguage = (
219
+ language: string
220
+ ): language is keyof typeof SCRIPT_FAMILIES =>
221
+ Object.hasOwn(SCRIPT_FAMILIES, language);
222
+
223
+ /** A script fallback: a Google family at the weights the card renders. */
224
+ export interface OgFallbackFont {
225
+ name: string;
226
+ weight: number[];
227
+ }
228
+
229
+ /** Subtags that write Chinese in traditional characters (`zh-Hant`, `zh-TW`). */
230
+ const TRADITIONAL_CHINESE = new Set(["hant", "hk", "mo", "tw"]);
231
+
232
+ /** The Noto family for one locale's script, or null when Geist covers it. */
233
+ const localeFamily = (locale: string): string | null => {
234
+ const [language = "", ...subtags] = locale.toLowerCase().split("-");
235
+ if (
236
+ language === "zh" &&
237
+ subtags.some((tag) => TRADITIONAL_CHINESE.has(tag))
238
+ ) {
239
+ return "Noto Sans TC";
240
+ }
241
+ if (isScriptLanguage(language)) {
242
+ return SCRIPT_FAMILIES[language];
243
+ }
244
+ // Anything past plain `latin` in the theme's subset table (Polish, Russian,
245
+ // Greek, Vietnamese, …) needs glyphs Geist lacks and `Noto Sans` has.
246
+ return localeFontSubsets([locale]).length > 1 ? "Noto Sans" : null;
247
+ };
248
+
249
+ /**
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
+ */
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[] => {
270
+ const names = new Set<string>();
271
+ for (const family of [...locales.map(localeFamily), ...DEFAULT_FALLBACKS]) {
272
+ if (family) {
273
+ names.add(family);
274
+ }
275
+ }
276
+ return [...names].map((name) => ({ name, weight: CARD_WEIGHTS }));
277
+ };
278
+
279
+ /** A bare Google family name entry (as opposed to the object forms). */
280
+ const isGoogleFamilyName = (font: OgFont): font is string =>
281
+ typeof font === "string";
282
+
283
+ /** The family an OG font entry registers under. */
284
+ const ogFontName = (font: OgFont): string =>
285
+ isGoogleFamilyName(font) ? font : font.name;
286
+
287
+ /**
288
+ * `derived` plus the script fallbacks it doesn't already load. A card that
289
+ * names no family is pinned to {@link BUILT_IN_FAMILY}, so its Latin text
290
+ * keeps the face it had before any fallback was loaded.
291
+ */
292
+ const withFallbacks = (
293
+ derived: DerivedOgFonts,
294
+ locales: string[]
295
+ ): DerivedOgFonts => {
296
+ const loaded = new Set(derived.fonts.map(ogFontName));
297
+ return {
298
+ fallbacks: ogFallbackFonts(locales).filter(
299
+ (font) => !loaded.has(font.name)
300
+ ),
301
+ families: derived.families ?? {
302
+ body: BUILT_IN_FAMILY,
303
+ title: BUILT_IN_FAMILY,
304
+ },
305
+ fonts: derived.fonts,
306
+ };
307
+ };
308
+
163
309
  /** Explicit `seo.og.fonts` with local `src` paths resolved to absolute. */
164
310
  export const resolveOgFontSources = (fonts: OgFont[], root: string): OgFont[] =>
165
311
  fonts.map((font) =>
@@ -170,10 +316,14 @@ export const resolveOgFontSources = (fonts: OgFont[], root: string): OgFont[] =>
170
316
  * The fonts baked into the generated OG endpoint. An explicit `seo.og.fonts`
171
317
  * always wins (including `[]` to opt out, keeping the card's role styling
172
318
  * untouched); otherwise a site that explicitly set `theme.fonts` gets its
173
- * display/body fonts derived so cards match the site without extra config.
319
+ * display/body fonts derived so cards match the site without extra config,
320
+ * and either way every card gets the script fallbacks, the configured
321
+ * locales' first.
174
322
  */
175
323
  export const resolveOgFonts = (
176
324
  options: {
325
+ /** The site's configured locale codes (none without `i18n`). */
326
+ locales?: string[];
177
327
  /** Explicit `seo.og.fonts`, or undefined when unset. */
178
328
  ogFonts: OgFont[] | undefined;
179
329
  themeFonts: FontsConfig;
@@ -185,9 +335,10 @@ export const resolveOgFonts = (
185
335
  if (options.ogFonts) {
186
336
  return { fonts: resolveOgFontSources(options.ogFonts, root) };
187
337
  }
188
- return options.themeFontsConfigured
338
+ const derived = options.themeFontsConfigured
189
339
  ? deriveOgFonts(options.themeFonts, root)
190
340
  : { fonts: [] };
341
+ return withFallbacks(derived, options.locales ?? []);
191
342
  };
192
343
 
193
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
 
@@ -1,4 +1,5 @@
1
1
  import type {
2
+ DocumentNode,
2
3
  GraphQLArgument,
3
4
  GraphQLEnumType,
4
5
  GraphQLField,
@@ -12,8 +13,8 @@ import type {
12
13
  GraphQLUnionType,
13
14
  } from "graphql";
14
15
  import {
16
+ buildASTSchema,
15
17
  buildClientSchema,
16
- buildSchema,
17
18
  getNamedType,
18
19
  isEnumType,
19
20
  isInputObjectType,
@@ -21,7 +22,11 @@ import {
21
22
  isObjectType,
22
23
  isSpecifiedScalarType,
23
24
  isUnionType,
25
+ Kind,
26
+ parse,
24
27
  print,
28
+ specifiedDirectives,
29
+ visit,
25
30
  } from "graphql";
26
31
 
27
32
  import type {
@@ -259,6 +264,27 @@ const introspectionOf = <Value>(parsed: Value): object | undefined => {
259
264
  : undefined;
260
265
  };
261
266
 
267
+ /**
268
+ * SDL with every use of an undeclared directive removed. Subgraph and platform
269
+ * schemas lean on directives their server defines out of band — Apollo
270
+ * Federation's `@key` and `@link`, AppSync's `@aws_*` — and strict SDL
271
+ * validation rejects each one as `Unknown directive`. The reference renders
272
+ * none of them, so they are dropped rather than declared; every other check
273
+ * (syntax, unknown types, duplicates) still runs.
274
+ */
275
+ const withoutUnknownDirectives = (document: DocumentNode): DocumentNode => {
276
+ const known = new Set(specifiedDirectives.map((directive) => directive.name));
277
+ for (const definition of document.definitions) {
278
+ if (definition.kind === Kind.DIRECTIVE_DEFINITION) {
279
+ known.add(definition.name.value);
280
+ }
281
+ }
282
+ return visit(document, {
283
+ // `null` deletes the node; `undefined` leaves it in place.
284
+ Directive: (node) => (known.has(node.name.value) ? undefined : null),
285
+ });
286
+ };
287
+
262
288
  /**
263
289
  * Build the document from schema text: an introspection JSON result (the raw
264
290
  * `{ __schema }` shape or a `{ data: { __schema } }` response envelope) or
@@ -289,5 +315,5 @@ export const buildGraphqlDocument = (text: string): GraphqlDocument => {
289
315
  )
290
316
  );
291
317
  }
292
- return documentOf(buildSchema(text));
318
+ return documentOf(buildASTSchema(withoutUnknownDirectives(parse(text))));
293
319
  };