blume 2.1.0 → 2.1.2

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 (450) hide show
  1. package/CHANGELOG.md +110 -0
  2. package/dist/cli/{chunk-ga0pf4aj.js → chunk-0n9xkp3d.js} +12 -3
  3. package/dist/cli/{chunk-ga0pf4aj.js.map → chunk-0n9xkp3d.js.map} +3 -3
  4. package/dist/cli/{chunk-43zcf4gz.js → chunk-0pmb4tr4.js} +541 -368
  5. package/dist/cli/chunk-0pmb4tr4.js.map +35 -0
  6. package/dist/cli/{chunk-9geymqns.js → chunk-0w6hs479.js} +15 -15
  7. package/dist/cli/{chunk-xkz5xbtv.js → chunk-124fvmzq.js} +2802 -1422
  8. package/dist/cli/chunk-124fvmzq.js.map +220 -0
  9. package/dist/cli/chunk-150jk7km.js +52 -0
  10. package/dist/cli/chunk-150jk7km.js.map +10 -0
  11. package/dist/cli/{chunk-y4ykbark.js → chunk-1f882900.js} +2 -2
  12. package/dist/cli/{chunk-5ffywsmb.js → chunk-297ajdcv.js} +8 -6
  13. package/dist/cli/{chunk-5ffywsmb.js.map → chunk-297ajdcv.js.map} +3 -3
  14. package/dist/cli/{chunk-h2a48yhd.js → chunk-32khwcb6.js} +120 -16
  15. package/dist/cli/{chunk-h2a48yhd.js.map → chunk-32khwcb6.js.map} +10 -10
  16. package/dist/cli/{chunk-5cg2rzgf.js → chunk-36bxs9c7.js} +20 -49
  17. package/dist/cli/{chunk-5cg2rzgf.js.map → chunk-36bxs9c7.js.map} +4 -5
  18. package/dist/cli/{chunk-py62fewh.js → chunk-53jqw8z0.js} +3 -3
  19. package/dist/cli/{chunk-5wj2qwn2.js → chunk-58bt3hag.js} +4 -4
  20. package/dist/cli/{chunk-e7vvk5ya.js → chunk-5cmts6dv.js} +3 -3
  21. package/dist/cli/{chunk-y5h4r2y0.js → chunk-60hz265r.js} +15 -5
  22. package/dist/cli/chunk-60hz265r.js.map +10 -0
  23. package/dist/cli/chunk-74wfhv79.js +65 -0
  24. package/dist/cli/chunk-74wfhv79.js.map +10 -0
  25. package/dist/cli/{chunk-g2z9x01v.js → chunk-9qzgg6mj.js} +3 -3
  26. package/dist/cli/{chunk-q3wxxq39.js → chunk-a57yh0h9.js} +5 -5
  27. package/dist/cli/{chunk-713mngdq.js → chunk-ave1wcbr.js} +76 -10
  28. package/dist/cli/chunk-ave1wcbr.js.map +14 -0
  29. package/dist/cli/{chunk-2n4ps1b3.js → chunk-bqxaqn5p.js} +40 -22
  30. package/dist/cli/chunk-bqxaqn5p.js.map +15 -0
  31. package/dist/cli/{chunk-jnwj5kjj.js → chunk-bxwhtvw8.js} +20 -7
  32. package/dist/cli/{chunk-jnwj5kjj.js.map → chunk-bxwhtvw8.js.map} +3 -3
  33. package/dist/cli/chunk-ckrcm08h.js +324 -0
  34. package/dist/cli/chunk-ckrcm08h.js.map +14 -0
  35. package/dist/cli/{chunk-2f3ndkpw.js → chunk-desvm72z.js} +3 -3
  36. package/dist/cli/{chunk-gp84za0h.js → chunk-dhwtqnnd.js} +2 -2
  37. package/dist/cli/{chunk-4h8z9vv5.js → chunk-dpy8qdjf.js} +93 -47
  38. package/dist/cli/chunk-dpy8qdjf.js.map +17 -0
  39. package/dist/cli/{chunk-2f7n4qzt.js → chunk-dqhg54mr.js} +1 -1
  40. package/dist/cli/{chunk-0gz36akr.js → chunk-dv2z0rzf.js} +21 -5
  41. package/dist/cli/chunk-dv2z0rzf.js.map +12 -0
  42. package/dist/cli/{chunk-cm89scdv.js → chunk-es8h0v3j.js} +9 -11
  43. package/dist/cli/{chunk-cm89scdv.js.map → chunk-es8h0v3j.js.map} +3 -3
  44. package/dist/cli/{chunk-zhvd6adm.js → chunk-gfc5fqvf.js} +14 -14
  45. package/dist/cli/{chunk-nd1821sk.js → chunk-gxtg6ed2.js} +33 -19
  46. package/dist/cli/chunk-gxtg6ed2.js.map +10 -0
  47. package/dist/cli/{chunk-p804f3pe.js → chunk-hvcxng5f.js} +6 -6
  48. package/dist/cli/{chunk-ta27feev.js → chunk-j1x92mx9.js} +21 -2
  49. package/dist/cli/chunk-j1x92mx9.js.map +10 -0
  50. package/dist/cli/{chunk-rekqz4qg.js → chunk-k3gr1nm9.js} +19 -16
  51. package/dist/cli/chunk-k3gr1nm9.js.map +10 -0
  52. package/dist/cli/{chunk-1n228xqb.js → chunk-kdpzw38a.js} +2 -2
  53. package/dist/cli/{chunk-cy5bm0j0.js → chunk-ms9z66nz.js} +2 -2
  54. package/dist/cli/{chunk-cy5bm0j0.js.map → chunk-ms9z66nz.js.map} +2 -2
  55. package/dist/cli/{chunk-zf748avv.js → chunk-nbyvgnnm.js} +193 -286
  56. package/dist/cli/chunk-nbyvgnnm.js.map +36 -0
  57. package/dist/cli/{chunk-6bm0xpgd.js → chunk-s0bdnc7v.js} +2 -2
  58. package/dist/cli/{chunk-yxmk4pec.js → chunk-sc93apfs.js} +17 -16
  59. package/dist/cli/{chunk-yxmk4pec.js.map → chunk-sc93apfs.js.map} +3 -3
  60. package/dist/cli/{chunk-cmtcnh9f.js → chunk-tfycddbt.js} +2 -2
  61. package/dist/cli/{chunk-9xr4dgee.js → chunk-twy7psdz.js} +29 -16
  62. package/dist/cli/chunk-twy7psdz.js.map +10 -0
  63. package/dist/cli/{chunk-ntv4qrt7.js → chunk-y4msrwm9.js} +15 -15
  64. package/dist/cli/{chunk-yj17vs39.js → chunk-ydp3065p.js} +5 -5
  65. package/dist/cli/index.js +24 -19
  66. package/dist/cli/index.js.map +3 -3
  67. package/dist/types/ai/ask-context.d.ts +6 -2
  68. package/dist/types/ai/ask.d.ts +24 -5
  69. package/dist/types/ai/component-markdown.d.ts +2 -0
  70. package/dist/types/ai/openapi-components.d.ts +4 -1
  71. package/dist/types/analytics/clics.d.ts +37 -0
  72. package/dist/types/analytics/index.d.ts +2 -0
  73. package/dist/types/analytics/schema.d.ts +18 -0
  74. package/dist/types/astro/integration.d.ts +11 -4
  75. package/dist/types/astro/markdown-negotiation.d.ts +3 -0
  76. package/dist/types/astro/pages.d.ts +10 -0
  77. package/dist/types/cli/init/scaffold.d.ts +1 -1
  78. package/dist/types/components/content/api-field.d.ts +4 -1
  79. package/dist/types/components/content/api-page.d.ts +6 -1
  80. package/dist/types/components/openapi/code-samples.d.ts +29 -0
  81. package/dist/types/components/openapi/helpers.d.ts +22 -1
  82. package/dist/types/components/openapi/security.d.ts +10 -0
  83. package/dist/types/components/openapi/snippets.d.ts +60 -0
  84. package/dist/types/core/changelog-index.d.ts +8 -0
  85. package/dist/types/core/config-input.d.ts +37 -6
  86. package/dist/types/core/data.d.ts +4 -0
  87. package/dist/types/core/i18n-ui.d.ts +2 -0
  88. package/dist/types/core/links.d.ts +8 -0
  89. package/dist/types/core/powered-by.d.ts +12 -0
  90. package/dist/types/core/probe.d.ts +2 -0
  91. package/dist/types/core/project-graph.d.ts +6 -0
  92. package/dist/types/core/redirect-patterns.d.ts +11 -3
  93. package/dist/types/core/schema.d.ts +228 -3
  94. package/dist/types/core/sources/cache.d.ts +3 -0
  95. package/dist/types/core/sources/contentful-rich-text.d.ts +11 -2
  96. package/dist/types/core/sources/contentful.d.ts +17 -1
  97. package/dist/types/core/sources/lexical.d.ts +5 -0
  98. package/dist/types/core/sources/lower.d.ts +10 -2
  99. package/dist/types/core/sources/normalize.d.ts +20 -1
  100. package/dist/types/core/sources/notion.d.ts +8 -0
  101. package/dist/types/core/sources/remote.d.ts +37 -10
  102. package/dist/types/core/sources/sanity.d.ts +1 -1
  103. package/dist/types/core/types.d.ts +17 -0
  104. package/dist/types/deploy/adapter-output.d.ts +2 -1
  105. package/dist/types/deploy/adapters/cloudflare.d.ts +7 -5
  106. package/dist/types/deploy/adapters/node.d.ts +58 -23
  107. package/dist/types/deploy/adapters/registry.d.ts +10 -0
  108. package/dist/types/deploy/adapters/types.d.ts +3 -2
  109. package/dist/types/deploy/cloudflare-negotiation.d.ts +5 -2
  110. package/dist/types/deploy/node-headers.d.ts +12 -4
  111. package/dist/types/deploy/platforms/cloudflare.d.ts +10 -7
  112. package/dist/types/deploy/platforms/node.d.ts +3 -1
  113. package/dist/types/deploy/platforms/types.d.ts +18 -7
  114. package/dist/types/deploy/platforms/vercel.d.ts +21 -2
  115. package/dist/types/deploy/redirects.d.ts +17 -1
  116. package/dist/types/deploy/sitemap.d.ts +2 -1
  117. package/dist/types/deploy/vercel-negotiation.d.ts +44 -21
  118. package/dist/types/narration/build.d.ts +34 -12
  119. package/dist/types/narration/provider.d.ts +96 -8
  120. package/dist/types/openapi/checks.d.ts +27 -0
  121. package/dist/types/openapi/graphql.d.ts +6 -0
  122. package/dist/types/openapi/model.d.ts +15 -4
  123. package/dist/types/openapi/parse.d.ts +3 -0
  124. package/dist/types/openapi/references.d.ts +16 -0
  125. package/dist/types/openapi/sentence.d.ts +11 -1
  126. package/dist/types/ratelimit/index.d.ts +1 -1
  127. package/dist/types/ratelimit/schema.d.ts +8 -0
  128. package/dist/types/ratelimit/upstash.d.ts +39 -14
  129. package/dist/types/reference/graphql.d.ts +76 -1
  130. package/dist/types/reference/index.d.ts +1 -1
  131. package/dist/types/reference/openapi.d.ts +2 -2
  132. package/dist/types/reference/options.d.ts +37 -1
  133. package/dist/types/reference/schema.d.ts +84 -0
  134. package/dist/types/search/adapters/registry.d.ts +3 -0
  135. package/dist/types/search/adapters/typesense.d.ts +14 -4
  136. package/dist/types/search/build.d.ts +3 -1
  137. package/dist/types/search/orama-index.d.ts +9 -9
  138. package/dist/types/search/sync/algolia.d.ts +5 -2
  139. package/dist/types/search/sync/index.d.ts +3 -1
  140. package/dist/types/search/sync/typesense.d.ts +11 -7
  141. package/dist/types/seo/jsonld.d.ts +11 -6
  142. package/dist/types/sources/contentful.d.ts +16 -0
  143. package/dist/types/sources/github-releases.d.ts +8 -1
  144. package/dist/types/sources/notion.d.ts +8 -0
  145. package/dist/types/sources/payload.d.ts +2 -0
  146. package/dist/types/sources/registry.d.ts +63 -0
  147. package/dist/types/sources/shared.d.ts +3 -0
  148. package/dist/types/sources/strapi.d.ts +2 -0
  149. package/docs/01-quickstart.mdx +4 -2
  150. package/docs/02-deployment.mdx +59 -13
  151. package/docs/03-upgrading.mdx +2 -1
  152. package/docs/04-migrating.mdx +9 -9
  153. package/docs/08-faq.mdx +12 -12
  154. package/docs/advanced/changelog.mdx +30 -2
  155. package/docs/advanced/custom-pages.mdx +1 -1
  156. package/docs/advanced/skills.mdx +1 -1
  157. package/docs/cli/audit.mdx +30 -7
  158. package/docs/cli/doctor.mdx +6 -2
  159. package/docs/cli/evals.mdx +4 -4
  160. package/docs/cli/translate.mdx +1 -0
  161. package/docs/cli/validate.mdx +4 -3
  162. package/docs/configuration/analytics.mdx +19 -1
  163. package/docs/configuration/assistant.mdx +6 -2
  164. package/docs/configuration/customization.mdx +4 -2
  165. package/docs/configuration/export.mdx +5 -3
  166. package/docs/configuration/index.mdx +4 -2
  167. package/docs/configuration/narration.mdx +43 -6
  168. package/docs/configuration/rate-limiting.mdx +23 -2
  169. package/docs/configuration/search.mdx +37 -14
  170. package/docs/content/components.mdx +2 -2
  171. package/docs/content/frontmatter.mdx +5 -3
  172. package/docs/content/i18n.mdx +2 -2
  173. package/docs/content/includes.mdx +1 -1
  174. package/docs/content/navigation.mdx +8 -6
  175. package/docs/content/sources/contentful.mdx +9 -4
  176. package/docs/content/sources/custom.mdx +7 -3
  177. package/docs/content/sources/github-releases.mdx +12 -2
  178. package/docs/content/sources/index.mdx +3 -3
  179. package/docs/content/sources/notion.mdx +17 -2
  180. package/docs/content/sources/obsidian.mdx +3 -1
  181. package/docs/content/sources/payload.mdx +5 -2
  182. package/docs/content/sources/remote-mdx.mdx +6 -0
  183. package/docs/content/sources/sanity.mdx +4 -2
  184. package/docs/content/sources/strapi.mdx +5 -2
  185. package/docs/content/syntax.mdx +2 -2
  186. package/docs/discoverability/agent-discovery.mdx +1 -1
  187. package/docs/discoverability/index.mdx +1 -1
  188. package/docs/discoverability/json-api.mdx +2 -0
  189. package/docs/discoverability/llms-txt.mdx +1 -1
  190. package/docs/discoverability/markdown.mdx +3 -1
  191. package/docs/discoverability/mcp.mdx +6 -0
  192. package/docs/discoverability/sitemap-and-robots.mdx +1 -1
  193. package/docs/discoverability/structured-data.mdx +8 -6
  194. package/docs/references/api-pages.mdx +1 -1
  195. package/docs/references/asyncapi.mdx +5 -1
  196. package/docs/references/graphql.mdx +15 -1
  197. package/docs/references/openapi.mdx +64 -8
  198. package/package.json +9 -9
  199. package/skills/blume/SKILL.md +1 -1
  200. package/skills/blume-migrate/SKILL.md +2 -2
  201. package/skills/blume-migrate/assets/{oxfmt@0.67.0.patch → oxfmt@0.71.0.patch} +7 -7
  202. package/skills/blume-migrate/references/docusaurus.md +4 -4
  203. package/skills/blume-migrate/references/fumadocs.md +17 -13
  204. package/skills/blume-migrate/references/monorepo.md +5 -5
  205. package/skills/blume-migrate/references/nextra.md +8 -5
  206. package/skills/blume-migrate/references/starlight.md +9 -7
  207. package/src/ai/ask-context.ts +33 -3
  208. package/src/ai/ask-limits.ts +13 -0
  209. package/src/ai/ask.ts +24 -6
  210. package/src/ai/changelog-markdown.ts +11 -6
  211. package/src/ai/component-markdown.ts +1 -1
  212. package/src/ai/cors.ts +12 -3
  213. package/src/ai/mcp/query.ts +14 -6
  214. package/src/ai/openapi-components.ts +28 -4
  215. package/src/ai/relative-links.ts +4 -38
  216. package/src/analytics/clics.ts +58 -0
  217. package/src/analytics/head.ts +5 -0
  218. package/src/analytics/index.ts +2 -0
  219. package/src/analytics/schema.ts +2 -0
  220. package/src/astro/generate.ts +91 -56
  221. package/src/astro/integration.ts +72 -22
  222. package/src/astro/markdown-negotiation.ts +3 -0
  223. package/src/astro/module-types.ts +3 -1
  224. package/src/astro/pages.ts +23 -0
  225. package/src/astro/render-deps.ts +10 -5
  226. package/src/astro/runtime-deps.ts +23 -2
  227. package/src/astro/templates.ts +129 -81
  228. package/src/audit/catalog.ts +19 -3
  229. package/src/audit/checks/links.ts +14 -1
  230. package/src/audit/checks/network.ts +110 -6
  231. package/src/audit/checks/redirects.ts +6 -4
  232. package/src/audit/checks/robots.ts +58 -2
  233. package/src/audit/run.ts +3 -0
  234. package/src/audit/types.ts +2 -0
  235. package/src/audit/url.ts +14 -5
  236. package/src/blume-modules.d.ts +5 -1
  237. package/src/cli/args.ts +51 -0
  238. package/src/cli/build-failure.ts +23 -0
  239. package/src/cli/commands/audit.ts +10 -1
  240. package/src/cli/commands/dev.ts +18 -2
  241. package/src/cli/commands/doctor.ts +45 -12
  242. package/src/cli/commands/eval.ts +36 -4
  243. package/src/cli/commands/init.ts +6 -4
  244. package/src/cli/commands/validate.ts +10 -1
  245. package/src/cli/init/scaffold.ts +9 -6
  246. package/src/cli/prepare.ts +6 -3
  247. package/src/cli/required-secrets.ts +3 -2
  248. package/src/components/content/ApiPlayground.astro +6 -2
  249. package/src/components/content/api-field.ts +4 -1
  250. package/src/components/content/api-page.ts +31 -7
  251. package/src/components/content/mermaid-element.ts +15 -1
  252. package/src/components/content/view-switcher.ts +7 -4
  253. package/src/components/islands/hooks.ts +27 -1
  254. package/src/components/layout/NavTree.astro +49 -5
  255. package/src/components/layout/NavTreeScript.astro +18 -0
  256. package/src/components/layout/PageActions.astro +139 -24
  257. package/src/components/layout/PageLayout.astro +13 -3
  258. package/src/components/layout/RootLayout.astro +27 -14
  259. package/src/components/layout/Search.astro +17 -3
  260. package/src/components/layout/TableOfContents.astro +24 -17
  261. package/src/components/layout/analytics-client.ts +8 -8
  262. package/src/components/layout/head-scripts.ts +6 -1
  263. package/src/components/layout/search/algolia.ts +10 -1
  264. package/src/components/layout/search/analytics.ts +42 -14
  265. package/src/components/layout/search/endpoint.ts +48 -5
  266. package/src/components/layout/search/pagefind.ts +114 -5
  267. package/src/components/layout/search/types.ts +12 -0
  268. package/src/components/layout/search/typesense.ts +18 -6
  269. package/src/components/openapi/AsyncApiOperation.astro +14 -7
  270. package/src/components/openapi/Authorization.astro +10 -0
  271. package/src/components/openapi/GraphqlOperation.astro +5 -0
  272. package/src/components/openapi/Operation.astro +24 -1
  273. package/src/components/openapi/ParametersTable.astro +8 -4
  274. package/src/components/openapi/Playground.astro +7 -1
  275. package/src/components/openapi/RequestBody.astro +19 -2
  276. package/src/components/openapi/Responses.astro +1 -0
  277. package/src/components/openapi/SchemaTable.astro +15 -3
  278. package/src/components/openapi/async-snippets.ts +61 -5
  279. package/src/components/openapi/async.ts +123 -29
  280. package/src/components/openapi/graphql-helpers.ts +35 -12
  281. package/src/components/openapi/helpers.ts +36 -2
  282. package/src/components/openapi/message-model.ts +54 -11
  283. package/src/components/openapi/message.ts +14 -4
  284. package/src/components/openapi/operation-model.ts +16 -7
  285. package/src/components/openapi/playground-client.ts +77 -58
  286. package/src/components/openapi/playground-schema.ts +1 -1
  287. package/src/components/openapi/schema-tree.ts +44 -14
  288. package/src/components/openapi/security.ts +10 -0
  289. package/src/components/openapi/snippets.ts +5 -3
  290. package/src/components/openapi/validate-json.ts +4 -3
  291. package/src/components/powered-by-middleware.ts +33 -0
  292. package/src/core/changelog-index.ts +31 -1
  293. package/src/core/component-diagnostics.ts +53 -0
  294. package/src/core/component-overrides.ts +32 -0
  295. package/src/core/config-input.ts +42 -6
  296. package/src/core/data.ts +2 -0
  297. package/src/core/diagnostics.ts +5 -0
  298. package/src/core/i18n-ui.ts +2 -0
  299. package/src/core/includes.ts +1 -1
  300. package/src/core/links.ts +47 -17
  301. package/src/core/powered-by.ts +12 -0
  302. package/src/core/probe.ts +26 -6
  303. package/src/core/project-graph.ts +59 -18
  304. package/src/core/redirect-patterns.ts +36 -7
  305. package/src/core/schema.ts +26 -6
  306. package/src/core/sources/cache.ts +6 -0
  307. package/src/core/sources/contentful-rich-text.ts +29 -6
  308. package/src/core/sources/contentful.ts +135 -25
  309. package/src/core/sources/filesystem.ts +26 -4
  310. package/src/core/sources/github-releases.ts +12 -1
  311. package/src/core/sources/lexical.ts +15 -10
  312. package/src/core/sources/lower.ts +19 -3
  313. package/src/core/sources/mdx-remote.ts +62 -10
  314. package/src/core/sources/normalize.ts +227 -41
  315. package/src/core/sources/notion.ts +34 -10
  316. package/src/core/sources/obsidian.ts +16 -3
  317. package/src/core/sources/payload.ts +38 -15
  318. package/src/core/sources/remote.ts +72 -15
  319. package/src/core/sources/resolve.ts +11 -1
  320. package/src/core/sources/sanity.ts +43 -25
  321. package/src/core/sources/strapi.ts +2 -2
  322. package/src/core/types.ts +18 -0
  323. package/src/core/ui-packs/ar.ts +1 -0
  324. package/src/core/ui-packs/bg.ts +1 -0
  325. package/src/core/ui-packs/bn.ts +1 -0
  326. package/src/core/ui-packs/ca.ts +1 -0
  327. package/src/core/ui-packs/cs.ts +1 -0
  328. package/src/core/ui-packs/da.ts +1 -0
  329. package/src/core/ui-packs/de.ts +1 -0
  330. package/src/core/ui-packs/el.ts +1 -0
  331. package/src/core/ui-packs/es.ts +1 -0
  332. package/src/core/ui-packs/fa.ts +1 -0
  333. package/src/core/ui-packs/fi.ts +1 -0
  334. package/src/core/ui-packs/fr.ts +1 -0
  335. package/src/core/ui-packs/he.ts +1 -0
  336. package/src/core/ui-packs/hi.ts +1 -0
  337. package/src/core/ui-packs/hr.ts +1 -0
  338. package/src/core/ui-packs/hu.ts +1 -0
  339. package/src/core/ui-packs/id.ts +1 -0
  340. package/src/core/ui-packs/it.ts +1 -0
  341. package/src/core/ui-packs/ja.ts +1 -0
  342. package/src/core/ui-packs/ko.ts +1 -0
  343. package/src/core/ui-packs/nl.ts +1 -0
  344. package/src/core/ui-packs/no.ts +1 -0
  345. package/src/core/ui-packs/pl.ts +1 -0
  346. package/src/core/ui-packs/pt-br.ts +1 -0
  347. package/src/core/ui-packs/pt.ts +1 -0
  348. package/src/core/ui-packs/ro.ts +1 -0
  349. package/src/core/ui-packs/ru.ts +1 -0
  350. package/src/core/ui-packs/sk.ts +1 -0
  351. package/src/core/ui-packs/sr.ts +1 -0
  352. package/src/core/ui-packs/sv.ts +1 -0
  353. package/src/core/ui-packs/th.ts +1 -0
  354. package/src/core/ui-packs/tr.ts +1 -0
  355. package/src/core/ui-packs/uk.ts +1 -0
  356. package/src/core/ui-packs/vi.ts +1 -0
  357. package/src/core/ui-packs/zh-tw.ts +1 -0
  358. package/src/core/ui-packs/zh.ts +1 -0
  359. package/src/deploy/adapter-output.ts +7 -2
  360. package/src/deploy/adapters/cloudflare.ts +7 -5
  361. package/src/deploy/adapters/node.ts +47 -9
  362. package/src/deploy/adapters/types.ts +7 -3
  363. package/src/deploy/artifacts.ts +2 -1
  364. package/src/deploy/cloudflare-negotiation.ts +36 -7
  365. package/src/deploy/headers.ts +15 -1
  366. package/src/deploy/node-headers.ts +32 -20
  367. package/src/deploy/platforms/cloudflare.ts +14 -9
  368. package/src/deploy/platforms/netlify.ts +2 -1
  369. package/src/deploy/platforms/node.ts +12 -2
  370. package/src/deploy/platforms/types.ts +21 -7
  371. package/src/deploy/platforms/vercel.ts +115 -17
  372. package/src/deploy/redirects.ts +66 -6
  373. package/src/deploy/rss.ts +3 -1
  374. package/src/deploy/sitemap.ts +39 -5
  375. package/src/deploy/vercel-negotiation.ts +325 -103
  376. package/src/eval/agents.ts +18 -1
  377. package/src/eval/prompts.ts +6 -3
  378. package/src/eval/report.ts +34 -13
  379. package/src/eval/run.ts +5 -0
  380. package/src/markdown/base-links.ts +15 -14
  381. package/src/markdown/package-commands.ts +9 -8
  382. package/src/markdown/relative-links.ts +3 -2
  383. package/src/narration/build.ts +218 -40
  384. package/src/narration/provider.ts +89 -9
  385. package/src/openapi/checks.ts +273 -0
  386. package/src/openapi/graphql-build.ts +21 -0
  387. package/src/openapi/graphql.ts +11 -0
  388. package/src/openapi/model.ts +15 -3
  389. package/src/openapi/parse.ts +127 -2
  390. package/src/openapi/references.ts +43 -3
  391. package/src/openapi/render-mdx.ts +68 -8
  392. package/src/openapi/sentence.ts +29 -2
  393. package/src/openapi/source.ts +39 -14
  394. package/src/ratelimit/index.ts +1 -1
  395. package/src/ratelimit/runtime.ts +37 -19
  396. package/src/ratelimit/upstash.ts +40 -10
  397. package/src/reference/graphql.ts +15 -1
  398. package/src/reference/index.ts +1 -0
  399. package/src/reference/openapi.ts +2 -2
  400. package/src/reference/options.ts +29 -1
  401. package/src/registry/eject.ts +8 -2
  402. package/src/search/adapters/locale-scope.ts +30 -0
  403. package/src/search/adapters/typesense.ts +14 -4
  404. package/src/search/build.ts +21 -3
  405. package/src/search/orama-index.ts +163 -31
  406. package/src/search/source-pages.ts +29 -0
  407. package/src/search/sync/algolia.ts +99 -9
  408. package/src/search/sync/index.ts +52 -13
  409. package/src/search/sync/typesense.ts +72 -27
  410. package/src/seo/jsonld.ts +38 -20
  411. package/src/sources/contentful.ts +12 -0
  412. package/src/sources/github-releases.ts +7 -1
  413. package/src/sources/notion.ts +7 -0
  414. package/src/sources/shared.ts +3 -0
  415. package/src/theme/color.ts +9 -10
  416. package/src/theme/entry.ts +43 -6
  417. package/src/translate/validate.ts +23 -1
  418. package/dist/cli/chunk-0gz36akr.js.map +0 -12
  419. package/dist/cli/chunk-2n4ps1b3.js.map +0 -15
  420. package/dist/cli/chunk-43zcf4gz.js.map +0 -36
  421. package/dist/cli/chunk-4ejxbzee.js +0 -39
  422. package/dist/cli/chunk-4ejxbzee.js.map +0 -10
  423. package/dist/cli/chunk-4h8z9vv5.js.map +0 -16
  424. package/dist/cli/chunk-713mngdq.js.map +0 -14
  425. package/dist/cli/chunk-9xr4dgee.js.map +0 -10
  426. package/dist/cli/chunk-amrb6hgh.js +0 -145
  427. package/dist/cli/chunk-amrb6hgh.js.map +0 -11
  428. package/dist/cli/chunk-nd1821sk.js.map +0 -10
  429. package/dist/cli/chunk-rekqz4qg.js.map +0 -10
  430. package/dist/cli/chunk-ta27feev.js.map +0 -10
  431. package/dist/cli/chunk-xkz5xbtv.js.map +0 -213
  432. package/dist/cli/chunk-y5h4r2y0.js.map +0 -10
  433. package/dist/cli/chunk-zf748avv.js.map +0 -37
  434. /package/dist/cli/{chunk-9geymqns.js.map → chunk-0w6hs479.js.map} +0 -0
  435. /package/dist/cli/{chunk-y4ykbark.js.map → chunk-1f882900.js.map} +0 -0
  436. /package/dist/cli/{chunk-py62fewh.js.map → chunk-53jqw8z0.js.map} +0 -0
  437. /package/dist/cli/{chunk-5wj2qwn2.js.map → chunk-58bt3hag.js.map} +0 -0
  438. /package/dist/cli/{chunk-e7vvk5ya.js.map → chunk-5cmts6dv.js.map} +0 -0
  439. /package/dist/cli/{chunk-g2z9x01v.js.map → chunk-9qzgg6mj.js.map} +0 -0
  440. /package/dist/cli/{chunk-q3wxxq39.js.map → chunk-a57yh0h9.js.map} +0 -0
  441. /package/dist/cli/{chunk-2f3ndkpw.js.map → chunk-desvm72z.js.map} +0 -0
  442. /package/dist/cli/{chunk-gp84za0h.js.map → chunk-dhwtqnnd.js.map} +0 -0
  443. /package/dist/cli/{chunk-2f7n4qzt.js.map → chunk-dqhg54mr.js.map} +0 -0
  444. /package/dist/cli/{chunk-zhvd6adm.js.map → chunk-gfc5fqvf.js.map} +0 -0
  445. /package/dist/cli/{chunk-p804f3pe.js.map → chunk-hvcxng5f.js.map} +0 -0
  446. /package/dist/cli/{chunk-1n228xqb.js.map → chunk-kdpzw38a.js.map} +0 -0
  447. /package/dist/cli/{chunk-6bm0xpgd.js.map → chunk-s0bdnc7v.js.map} +0 -0
  448. /package/dist/cli/{chunk-cmtcnh9f.js.map → chunk-tfycddbt.js.map} +0 -0
  449. /package/dist/cli/{chunk-ntv4qrt7.js.map → chunk-y4msrwm9.js.map} +0 -0
  450. /package/dist/cli/{chunk-yj17vs39.js.map → chunk-ydp3065p.js.map} +0 -0
@@ -1,3 +1,4 @@
1
+ import { BlumeError } from "../../core/diagnostics.ts";
1
2
  import type { BlumeProject } from "../../core/project-graph.ts";
2
3
  import type { ResolvedSearchAdapter } from "../adapters/registry.ts";
3
4
  import { buildSearchDocuments, toSearchRecords } from "../documents.ts";
@@ -13,23 +14,49 @@ export interface SyncReporter {
13
14
  warn: (message: string) => void;
14
15
  }
15
16
 
17
+ /** A hosted adapter's build-time sync. */
18
+ interface HostedSync {
19
+ /** The env var holding the admin key the sync reads. */
20
+ key: string;
21
+ /**
22
+ * Whether the sync is set up to run: its admin key is set, and for Orama
23
+ * Cloud, the `indexId` that turns the sync on.
24
+ */
25
+ ready: boolean;
26
+ /** The upload, closed over the adapter's options. */
27
+ run: (records: SearchRecord[]) => Promise<void>;
28
+ }
29
+
16
30
  /**
17
- * The sync a hosted adapter runs after a build, closed over its options, or
18
- * `null` for adapters with no in-process sync (static and Pagefind indexes are
19
- * built locally; Mixedbread syncs out-of-band via the `mxbai` CLI).
31
+ * The sync a hosted adapter runs after a build, or `null` for adapters with no
32
+ * in-process sync (static and Pagefind indexes are built locally; Mixedbread
33
+ * syncs out-of-band via the `mxbai` CLI).
20
34
  */
21
- const searchSync = (
22
- provider: ResolvedSearchAdapter
23
- ): ((records: SearchRecord[]) => Promise<void>) | null => {
35
+ const searchSync = (provider: ResolvedSearchAdapter): HostedSync | null => {
24
36
  switch (provider.kind) {
25
37
  case "algolia": {
26
- return (records) => syncAlgolia(records, provider.options);
38
+ const key = "ALGOLIA_ADMIN_API_KEY";
39
+ return {
40
+ key,
41
+ ready: Boolean(process.env[key]),
42
+ run: (records) => syncAlgolia(records, provider.options),
43
+ };
27
44
  }
28
45
  case "orama-cloud": {
29
- return (records) => syncOramaCloud(records, provider.options);
46
+ const key = "ORAMA_PRIVATE_API_KEY";
47
+ return {
48
+ key,
49
+ ready: Boolean(process.env[key] && provider.options.indexId),
50
+ run: (records) => syncOramaCloud(records, provider.options),
51
+ };
30
52
  }
31
53
  case "typesense": {
32
- return (records) => syncTypesense(records, provider.options);
54
+ const key = "TYPESENSE_ADMIN_API_KEY";
55
+ return {
56
+ key,
57
+ ready: Boolean(process.env[key]),
58
+ run: (records) => syncTypesense(records, provider.options),
59
+ };
33
60
  }
34
61
  default: {
35
62
  return null;
@@ -41,7 +68,9 @@ const searchSync = (
41
68
  * After a build, upload the per-page records to the configured hosted adapter.
42
69
  * Reads secret admin keys from the environment; if a key (or option) is
43
70
  * missing the sync is skipped with a warning rather than failing the build —
44
- * CI may sync in a separate step.
71
+ * a local build has no secrets, and CI may sync in a separate step. Once the
72
+ * sync is set up, a failure fails the build (`BLUME_SEARCH_SYNC_FAILED`):
73
+ * the site would otherwise deploy against an index this build didn't update.
45
74
  */
46
75
  export const syncSearchProvider = async (
47
76
  project: BlumeProject,
@@ -57,11 +86,21 @@ export const syncSearchProvider = async (
57
86
  reporter.start(`Syncing ${records.length} record(s) to ${provider.kind}`);
58
87
 
59
88
  try {
60
- await sync(records);
89
+ await sync.run(records);
61
90
  reporter.success(`Synced ${records.length} record(s) to ${provider.kind}`);
62
91
  } catch (error) {
63
92
  // SAFETY: the three sync clients surface network/auth failures as Error
64
- // instances; the message is read only to annotate the skip warning.
65
- reporter.warn(`Search sync skipped: ${(error as Error).message}`);
93
+ // instances; the message is read only to annotate the warning or error.
94
+ const { message } = error as Error;
95
+ if (!sync.ready) {
96
+ reporter.warn(`Search sync skipped: ${message}`);
97
+ return;
98
+ }
99
+ throw new BlumeError({
100
+ code: "BLUME_SEARCH_SYNC_FAILED",
101
+ message: `Search sync to ${provider.kind} failed: ${message}`,
102
+ severity: "error",
103
+ suggestion: `Check ${sync.key} and the adapter's options, or unset ${sync.key} to build without syncing.`,
104
+ });
66
105
  }
67
106
  };
@@ -7,17 +7,44 @@ import type { SearchRecord } from "../documents.ts";
7
7
  /** The adapter options the sync reads (the public `apiKey` is never used). */
8
8
  export type TypesenseSyncConfig = Pick<
9
9
  TypesenseOptions,
10
- "collection" | "host" | "port" | "protocol"
10
+ "collection" | "host" | "locale" | "port" | "protocol"
11
11
  >;
12
12
 
13
+ type TypesenseClient = InstanceType<typeof TypesenseSdk.Client>;
14
+
15
+ /**
16
+ * The collection an alias points at, or `undefined` when there is no such
17
+ * alias. Only a 404 means that: any other failure (a key that can't read
18
+ * aliases, an outage) throws before the sync has touched anything.
19
+ */
20
+ const aliasTarget = async (
21
+ client: TypesenseClient,
22
+ alias: string,
23
+ errors: typeof TypesenseSdk.Errors
24
+ ): Promise<string | undefined> => {
25
+ try {
26
+ const { collection_name: target } = await client.aliases(alias).retrieve();
27
+ return target;
28
+ } catch (error) {
29
+ if (error instanceof errors.ObjectNotFound) {
30
+ return undefined;
31
+ }
32
+ throw error;
33
+ }
34
+ };
35
+
13
36
  /**
14
- * Import the search records into a Typesense collection. Uses the admin key
15
- * from `TYPESENSE_ADMIN_API_KEY`. Throws on a missing key so the caller can
16
- * warn.
37
+ * Import the search records into Typesense. Uses the admin key from
38
+ * `TYPESENSE_ADMIN_API_KEY`. Throws on a missing key so the caller can warn.
17
39
  *
18
- * The collection is dropped and recreated on each sync so that pages deleted or
19
- * renamed since the last sync don't linger as stale search hits that 404 when
20
- * clicked (an upsert alone never removes them).
40
+ * `collection` names an alias. Each sync imports every record into a new
41
+ * collection (`<collection>_<timestamp>`), points the alias at it, then drops
42
+ * the collection it replaced: searches keep reading the previous collection
43
+ * until the new one is complete, pages deleted or renamed since the last sync
44
+ * don't linger as stale hits that 404 when clicked, and a sync that fails
45
+ * leaves the previous collection serving. A collection named `collection`
46
+ * itself, from a sync before the alias existed, is dropped just before the
47
+ * alias takes its name, since Typesense won't let the two share one.
21
48
  */
22
49
  export const syncTypesense = async (
23
50
  records: SearchRecord[],
@@ -29,7 +56,7 @@ export const syncTypesense = async (
29
56
  }
30
57
  // `require`, not `import()`: this runs in `astro:build:done` (see
31
58
  // `core/node-require.ts`).
32
- const { Client }: typeof TypesenseSdk = nodeRequire("typesense");
59
+ const { Client, Errors }: typeof TypesenseSdk = nodeRequire("typesense");
33
60
  const client = new Client({
34
61
  apiKey: adminKey,
35
62
  nodes: [
@@ -41,23 +68,18 @@ export const syncTypesense = async (
41
68
  ],
42
69
  });
43
70
 
44
- const collection = client.collections(config.collection);
45
- let exists = true;
46
- try {
47
- await collection.retrieve();
48
- } catch {
49
- exists = false;
50
- }
51
- if (exists) {
52
- await collection.delete();
53
- }
71
+ const alias = config.collection;
72
+ const previous = await aliasTarget(client, alias, Errors);
73
+ const name = `${alias}_${Date.now()}`;
74
+ // The searched text fields, tokenized for `locale` when the site sets one.
75
+ const text = config.locale ? { locale: config.locale } : {};
54
76
  await client.collections().create({
55
77
  fields: [
56
- { name: "title", type: "string" },
57
- { name: "description", optional: true, type: "string" },
58
- { name: "content", type: "string" },
78
+ { name: "title", type: "string", ...text },
79
+ { name: "description", optional: true, type: "string", ...text },
80
+ { name: "content", type: "string", ...text },
59
81
  { name: "url", type: "string" },
60
- { name: "keywords", optional: true, type: "string[]" },
82
+ { name: "keywords", optional: true, type: "string[]", ...text },
61
83
  // The dialog sorts close matches by it (search.boost, 1 by default).
62
84
  { name: "boost", type: "float" },
63
85
  { facet: true, name: "tag", optional: true, type: "string" },
@@ -66,7 +88,7 @@ export const syncTypesense = async (
66
88
  { facet: true, name: "locale", optional: true, type: "string" },
67
89
  { facet: true, name: "version", optional: true, type: "string" },
68
90
  ],
69
- name: config.collection,
91
+ name,
70
92
  });
71
93
 
72
94
  const documents = records.map((record) => ({
@@ -81,8 +103,31 @@ export const syncTypesense = async (
81
103
  url: record.url,
82
104
  version: record.version,
83
105
  }));
84
- await client
85
- .collections(config.collection)
86
- .documents()
87
- .import(documents, { action: "upsert" });
106
+ try {
107
+ await client
108
+ .collections(name)
109
+ .documents()
110
+ .import(documents, { action: "upsert" });
111
+ } catch (error) {
112
+ // Nothing reads the new collection yet: drop it, and the alias keeps
113
+ // serving the last complete sync.
114
+ await client.collections(name).delete();
115
+ throw error;
116
+ }
117
+
118
+ if (previous === undefined) {
119
+ let legacy = true;
120
+ try {
121
+ await client.collections(alias).retrieve();
122
+ } catch {
123
+ legacy = false;
124
+ }
125
+ if (legacy) {
126
+ await client.collections(alias).delete();
127
+ }
128
+ }
129
+ await client.aliases().upsert(alias, { collection_name: name });
130
+ if (previous !== undefined) {
131
+ await client.collections(previous).delete();
132
+ }
88
133
  };
package/src/seo/jsonld.ts CHANGED
@@ -19,7 +19,7 @@ export interface PostalAddressIdentity {
19
19
 
20
20
  /**
21
21
  * `seo.organization`: the organization behind the site, emitted on every page
22
- * as an `Organization` node the WebSite and article nodes cite as publisher.
22
+ * as an `Organization` node the WebSite and page nodes cite as publisher.
23
23
  */
24
24
  export interface OrganizationIdentity {
25
25
  address?: PostalAddressIdentity;
@@ -73,6 +73,12 @@ export interface StructuredDataInput {
73
73
  description?: string;
74
74
  /** Page route, e.g. `/blog/post`. */
75
75
  route: string;
76
+ /**
77
+ * The route of this page's homepage: the navigation tree root
78
+ * (`navigation.root` — `/fr` for a locale, `/docs` under a `basePath`,
79
+ * `/v1.0` in an archived version). Defaults to `/`.
80
+ */
81
+ homeRoute?: string;
76
82
  /** Deployment base (`import.meta.env.BASE_URL`); prefixed onto absolute URLs. */
77
83
  base?: string;
78
84
  /** Content type — `blog` and `changelog` map to richer article types. */
@@ -246,24 +252,41 @@ const softwareNode = (
246
252
  return node;
247
253
  };
248
254
 
249
- /** The page as an article node (`BlogPosting`/`TechArticle`). */
250
- const articleNode = (
255
+ /** The page's schema.org `@type`: a `WebPage` for a homepage, else an article. */
256
+ const pageSchemaType = (input: StructuredDataInput, home: boolean): string => {
257
+ if (home) {
258
+ return "WebPage";
259
+ }
260
+ const pageType = input.pageType ?? "";
261
+ return isArticleType(pageType) ? ARTICLE_TYPES[pageType] : "TechArticle";
262
+ };
263
+
264
+ /**
265
+ * The page's own node: a homepage is a `WebPage` (it isn't an article, but it
266
+ * still needs a machine-readable date, or search engines take whatever other
267
+ * date the page shows), and every other page an article (`BlogPosting`/
268
+ * `TechArticle`) with a `headline`. Both carry the language, description,
269
+ * dates, the WebSite they belong to, and the publisher.
270
+ */
271
+ const pageNode = (
251
272
  input: StructuredDataInput,
252
273
  context: {
253
274
  base: string | null;
275
+ home: boolean;
254
276
  organizationId: string | null;
255
277
  pageUrl: string;
256
278
  }
257
279
  ): JsonLdNode => {
258
- const pageType = input.pageType ?? "";
259
280
  const node: JsonLdNode = {
260
281
  "@id": `${context.pageUrl}#page`,
261
- "@type": isArticleType(pageType) ? ARTICLE_TYPES[pageType] : "TechArticle",
262
- headline: input.title,
282
+ "@type": pageSchemaType(input, context.home),
263
283
  inLanguage: input.locale || "en",
264
284
  name: input.title,
265
285
  url: context.pageUrl,
266
286
  };
287
+ if (!context.home) {
288
+ node.headline = input.title;
289
+ }
267
290
  if (input.description) {
268
291
  node.description = input.description;
269
292
  }
@@ -315,14 +338,11 @@ const breadcrumbNode = (
315
338
  /**
316
339
  * Build a schema.org JSON-LD `@graph` for a page: site identity (the WebSite,
317
340
  * plus the configured Organization everywhere and the SoftwareApplication on
318
- * the homepage), the page as an article, and its breadcrumb trail. Returns
319
- * null when there is nothing useful to emit (e.g. the homepage without a
320
- * configured site). URLs are absolute when `siteUrl` is set, otherwise
321
- * route-relative.
341
+ * the homepage), the page itself (a WebPage on the homepage, an article
342
+ * elsewhere), and an article's breadcrumb trail. URLs are absolute when
343
+ * `siteUrl` is set, otherwise route-relative.
322
344
  */
323
- export const buildStructuredData = (
324
- input: StructuredDataInput
325
- ): JsonLdNode | null => {
345
+ export const buildStructuredData = (input: StructuredDataInput): JsonLdNode => {
326
346
  const base = input.siteUrl ? trimSlash(input.siteUrl) : null;
327
347
  // Routes carry `basePath`; a `deployment.base` subdirectory is layered on top
328
348
  // so JSON-LD URLs match the served location.
@@ -365,9 +385,11 @@ export const buildStructuredData = (
365
385
  );
366
386
  }
367
387
 
368
- // The homepage is described by the WebSite node (and the product, when one
369
- // is configured); deeper pages get an article node plus a breadcrumb trail.
370
- if (input.route === "/") {
388
+ // The homepage is a WebPage, next to the product when one is configured;
389
+ // deeper pages are articles with a breadcrumb trail.
390
+ const home = input.route === (input.homeRoute ?? "/");
391
+ graph.push(pageNode(input, { base, home, organizationId, pageUrl }));
392
+ if (home) {
371
393
  if (software && base) {
372
394
  graph.push(
373
395
  softwareNode(software, {
@@ -380,15 +402,11 @@ export const buildStructuredData = (
380
402
  );
381
403
  }
382
404
  } else {
383
- graph.push(articleNode(input, { base, organizationId, pageUrl }));
384
405
  const breadcrumbs = breadcrumbNode(input.breadcrumbs, base, deployBase);
385
406
  if (breadcrumbs) {
386
407
  graph.push(breadcrumbs);
387
408
  }
388
409
  }
389
410
 
390
- if (graph.length === 0) {
391
- return null;
392
- }
393
411
  return { "@context": "https://schema.org", "@graph": graph };
394
412
  };
@@ -21,10 +21,20 @@ export interface ContentfulOptions extends SharedSourceOptions {
21
21
  * / `sys.updatedAt`.
22
22
  */
23
23
  fields?: SourceFieldMap;
24
+ /**
25
+ * Delivery API host name. Defaults to `cdn.contentful.com`; a space with
26
+ * EU data residency reads from `cdn.eu.contentful.com`.
27
+ */
28
+ host?: string;
24
29
  /** Locale code to fetch; omit for the space's default locale. */
25
30
  locale?: string;
26
31
  /** Extra query parameters for the entries request (`"fields.section": "sdk"`). */
27
32
  params?: Record<string, string>;
33
+ /**
34
+ * Preview API host name for `--preview`. Defaults to
35
+ * `preview.contentful.com` (`preview.eu.contentful.com` in the EU).
36
+ */
37
+ previewHost?: string;
28
38
  /** Space id. */
29
39
  space: string;
30
40
  }
@@ -33,8 +43,10 @@ export const contentfulOptionsSchema = sharedSourceOptionsSchema.extend({
33
43
  contentType: z.string(),
34
44
  environment: z.string().optional(),
35
45
  fields: sourceFieldMapSchema.optional(),
46
+ host: z.string().optional(),
36
47
  locale: z.string().optional(),
37
48
  params: queryParamsSchema.optional(),
49
+ previewHost: z.string().optional(),
38
50
  space: z.string(),
39
51
  });
40
52
 
@@ -7,7 +7,12 @@ import { sharedSourceOptionsSchema } from "./shared.ts";
7
7
 
8
8
  /** Options for {@link githubReleases}. */
9
9
  export interface GithubReleasesOptions extends SharedSourceOptions {
10
- /** Include draft releases (needs a token with repo write access). */
10
+ /**
11
+ * The GitHub REST API base URL. Defaults to `https://api.github.com`; a
12
+ * GitHub Enterprise Server's is `https://<host>/api/v3`.
13
+ */
14
+ baseUrl?: string;
15
+ /** Include draft releases, tagged `Draft` (needs a token with repo write access). */
11
16
  drafts?: boolean;
12
17
  /** Cap the number of releases materialized, newest-first. Defaults to 100. */
13
18
  limit?: number;
@@ -20,6 +25,7 @@ export interface GithubReleasesOptions extends SharedSourceOptions {
20
25
  }
21
26
 
22
27
  export const githubReleasesOptionsSchema = sharedSourceOptionsSchema.extend({
28
+ baseUrl: z.string().optional(),
23
29
  drafts: z.boolean().optional(),
24
30
  limit: z.number().positive().optional(),
25
31
  owner: z.string(),
@@ -11,6 +11,12 @@ export interface NotionOptions extends SharedSourceOptions {
11
11
  concurrency?: number;
12
12
  /** Notion database id. */
13
13
  database: string;
14
+ /**
15
+ * Read text typed in Notion as MDX, so components and Markdown in it
16
+ * render. Anyone who can edit the database can then run code at build
17
+ * time. Default false: text renders as written.
18
+ */
19
+ mdx?: boolean;
14
20
  /** Notion property names mapped onto Blume meta. */
15
21
  properties?: {
16
22
  /** Property holding the page description. */
@@ -31,6 +37,7 @@ export interface NotionOptions extends SharedSourceOptions {
31
37
  export const notionOptionsSchema = sharedSourceOptionsSchema.extend({
32
38
  concurrency: z.number().positive().optional(),
33
39
  database: z.string(),
40
+ mdx: z.boolean().optional(),
34
41
  properties: z
35
42
  .strictObject({
36
43
  description: z.string().optional(),
@@ -33,6 +33,8 @@ export interface SourceFieldMap {
33
33
  description?: string;
34
34
  /** Field holding the last-modified date. */
35
35
  lastModified?: string;
36
+ /** Field holding the sidebar order (a number). No default: unset, none is read. */
37
+ order?: string;
36
38
  /** Field holding the page slug. */
37
39
  slug?: string;
38
40
  /** Field holding the page title. */
@@ -44,6 +46,7 @@ export const sourceFieldMapSchema = z.strictObject({
44
46
  body: z.string().optional(),
45
47
  description: z.string().optional(),
46
48
  lastModified: z.string().optional(),
49
+ order: z.string().optional(),
47
50
  slug: z.string().optional(),
48
51
  title: z.string().optional(),
49
52
  });
@@ -374,16 +374,15 @@ const rectangular = (lightness: number, chroma: number, hue: number) => {
374
374
  /** A color function's channels, converted to sRGB with `alpha`. */
375
375
  type Converter = (channels: Channels, alpha: number) => Rgba;
376
376
 
377
- const rgb: Converter = ([red, green, blue], alpha) => {
378
- const channel = (component: Component): number =>
379
- clamp(scaled(component, 255) / 255);
380
- return {
381
- alpha,
382
- blue: channel(blue),
383
- green: channel(green),
384
- red: channel(red),
385
- };
386
- };
377
+ const rgbChannel = (component: Component): number =>
378
+ clamp(scaled(component, 255) / 255);
379
+
380
+ const rgb: Converter = ([red, green, blue], alpha) => ({
381
+ alpha,
382
+ blue: rgbChannel(blue),
383
+ green: rgbChannel(green),
384
+ red: rgbChannel(red),
385
+ });
387
386
 
388
387
  /** A converter for a hue-and-two-percentages function (`hsl()`, `hwb()`). */
389
388
  const cylindrical =
@@ -263,13 +263,24 @@ ${THEME_MAPPING}
263
263
  so a large site's HTML is mostly this markup — one utility per row kind
264
264
  instead of the dozen classes each expands to keeps every page (and the
265
265
  build's HTML output) small. Kept in sync with NavTree.astro: a page row,
266
- a page-mode group's drill-in button, a collapsible group's summary and
267
- the link inside it, and a flat group's heading and the link inside it. */
266
+ a page-mode group's drill-in row (or, when the group has a page, the
267
+ link and chevron button it splits into), a collapsible group's summary
268
+ and the link inside it, and a flat group's heading and the link inside
269
+ it. */
268
270
  @utility blume-nav-link {
269
271
  @apply block rounded-[0.65rem] px-2.5 py-1.5 text-muted-foreground text-sm transition-colors hover:bg-muted hover:text-foreground aria-[current=page]:bg-muted aria-[current=page]:font-medium aria-[current=page]:text-foreground;
270
272
  }
271
273
  @utility blume-nav-drill {
272
- @apply flex w-full items-center gap-2 rounded-[0.65rem] px-2.5 py-1.5 text-start font-medium text-foreground text-sm transition-colors hover:bg-muted;
274
+ @apply flex w-full items-center gap-2 rounded-[0.65rem] px-2.5 py-1.5 text-start font-medium text-foreground text-sm transition-colors hover:bg-muted aria-[current]:bg-muted;
275
+ }
276
+ /* A routed drill row's label and chevron are separate pills, like a panel's
277
+ back button and title, so each shows which one the pointer is on. The
278
+ chevron's padding matches the drill row's, so chevrons line up. */
279
+ @utility blume-nav-drill-link {
280
+ @apply flex min-w-0 flex-1 items-center gap-2 rounded-[0.65rem] px-2.5 py-1.5 font-medium text-foreground text-sm transition-colors hover:bg-muted aria-[current]:bg-muted;
281
+ }
282
+ @utility blume-nav-drill-toggle {
283
+ @apply flex shrink-0 items-center justify-center self-stretch rounded-[0.65rem] px-2.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground;
273
284
  }
274
285
  @utility blume-nav-summary {
275
286
  @apply flex cursor-pointer list-none items-center gap-1.5 rounded-[0.65rem] px-2.5 py-1.5 text-muted-foreground text-sm transition-colors hover:bg-muted hover:text-foreground [&::-webkit-details-marker]:hidden;
@@ -940,8 +951,11 @@ pre:has(.line.focused):hover .line:not(.focused) {
940
951
  Strip the surrounding chrome so the printout is just the article. Scoped to
941
952
  the chrome itself, never bare \`header\`/\`aside\`: content renders those too
942
953
  (every Callout and Panel is an <aside>, every changelog Update has a
943
- <header>), and they belong in the printout. \`body > header\` keeps a
944
- \`layout.Header\` override out as well. */
954
+ <header>), and they belong in the printout. \`body > header\` and
955
+ \`body > footer\` keep \`layout.Header\` and \`layout.Footer\` overrides out
956
+ as well. Everything after the article (directory and related-page cards,
957
+ last updated, feedback, prev/next) is page chrome too, and so are the
958
+ copy, ask, and expand buttons on code blocks. */
945
959
  @media print {
946
960
  [data-blume-banner],
947
961
  [data-blume-header],
@@ -951,7 +965,13 @@ pre:has(.line.focused):hover .line:not(.focused) {
951
965
  [data-blume-assistant-panel],
952
966
  [data-blume-page-actions],
953
967
  #blume-content > nav,
954
- #blume-content > details {
968
+ #blume-content > details,
969
+ [data-blume-page-end],
970
+ [data-blume-footer],
971
+ body > footer,
972
+ [data-blume-copy],
973
+ [data-blume-ask],
974
+ [data-blume-code-expand] {
955
975
  display: none !important;
956
976
  }
957
977
 
@@ -962,6 +982,23 @@ pre:has(.line.focused):hover .line:not(.focused) {
962
982
  overflow: visible;
963
983
  }
964
984
 
985
+ /* A collapsed \`expandable\` block prints open: its toggle is hidden above,
986
+ so the collapse would cut the block off at its first lines. Same
987
+ selector as the collapse, so this later rule wins. */
988
+ .prose pre[data-expandable]:not([data-expanded]) > code {
989
+ mask-image: none;
990
+ max-height: none;
991
+ overflow-y: visible;
992
+ }
993
+
994
+ /* The theme script switches a dark page to light for printing
995
+ (\`beforeprint\`); a color transition would print mid-fade. */
996
+ *,
997
+ *::before,
998
+ *::after {
999
+ transition: none !important;
1000
+ }
1001
+
965
1002
  #blume-content {
966
1003
  padding: 0 !important;
967
1004
  }
@@ -7,7 +7,8 @@ import { TRANSLATABLE_KEY_PATHS } from "./prompts.ts";
7
7
  * structure: the final frontmatter is *reconstructed* from the source (clone
8
8
  * the source data, overlay only the translatable string values), so invented
9
9
  * keys are dropped, deleted keys are restored, and slugs/icons/orders/dates
10
- * stay source-verbatim by construction. Only then is the file written.
10
+ * stay source-verbatim by construction. The one source key left out is
11
+ * `seo.canonical` (see `dropCanonical`). Only then is the file written.
11
12
  */
12
13
 
13
14
  export type ValidationResult =
@@ -93,6 +94,26 @@ const setPath = (
93
94
  parent[path.at(-1) as string] = value;
94
95
  };
95
96
 
97
+ /**
98
+ * Drop `seo.canonical`, and `seo` with it when nothing else is left. The
99
+ * source's canonical names a page in the source language (most often the
100
+ * source page itself); copied into a translation, it would canonicalize the
101
+ * translation to another language and take it out of search. Without it the
102
+ * translation gets Blume's default canonical: its own URL.
103
+ */
104
+ const dropCanonical = (data: FrontmatterData): void => {
105
+ const { seo } = data;
106
+ if (!isKeyedObject(seo) || !("canonical" in seo)) {
107
+ return;
108
+ }
109
+ const { canonical: _canonical, ...rest } = seo;
110
+ if (Object.keys(rest).length > 0) {
111
+ data.seo = rest;
112
+ } else {
113
+ delete data.seo;
114
+ }
115
+ };
116
+
96
117
  const ensureTrailingNewline = (text: string): string =>
97
118
  text.endsWith("\n") ? text : `${text}\n`;
98
119
 
@@ -181,6 +202,7 @@ export const validateTranslation = (
181
202
  setPath(data, path, translated);
182
203
  }
183
204
  }
205
+ dropCanonical(data);
184
206
 
185
207
  return {
186
208
  ok: true,