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
@@ -9,17 +9,18 @@
9
9
  */
10
10
  import type { JsonObject } from "./json.ts";
11
11
  import { asString, getPath, objectsIn } from "./json.ts";
12
- import type { InlineMarks } from "./lower.ts";
12
+ import type { InlineMarks, InlinePart } from "./lower.ts";
13
13
  import {
14
14
  absoluteUrl,
15
15
  blockquote,
16
+ guardBlockStart,
16
17
  headingPrefix,
17
18
  image,
18
19
  joinBlocks,
20
+ linkParts,
19
21
  listItem,
20
22
  markdownDocument,
21
23
  renderInline,
22
- renderLink,
23
24
  unsupported,
24
25
  writesMdx,
25
26
  } from "./lower.ts";
@@ -138,45 +139,50 @@ const embeddedEntry = (
138
139
  );
139
140
  };
140
141
 
141
- const renderInlines = (
142
+ const inlineParts = (
142
143
  nodes: JsonObject[],
143
144
  options: ContentfulRichTextOptions
144
- ): string =>
145
- nodes
146
- // oxlint-disable-next-line no-use-before-define -- mutual recursion: a link's label is itself inline content
147
- .map((node) => renderInlineNode(node, options))
148
- .join("");
145
+ ): InlinePart[] =>
146
+ // oxlint-disable-next-line no-use-before-define -- mutual recursion: a link's label is itself inline content
147
+ nodes.flatMap((node) => inlineNodeParts(node, options));
149
148
 
150
- const renderInlineNode = (
149
+ const inlineNodeParts = (
151
150
  node: JsonObject,
152
151
  options: ContentfulRichTextOptions
153
- ): string => {
152
+ ): InlinePart[] => {
154
153
  switch (asString(node.nodeType)) {
155
154
  case "text": {
156
- return renderInline(asString(node.value) ?? "", marksOf(node));
155
+ return [{ marks: marksOf(node), text: asString(node.value) ?? "" }];
157
156
  }
158
157
  case "hyperlink": {
159
- return renderLink(
160
- renderInlines(children(node), options),
158
+ return linkParts(
159
+ inlineParts(children(node), options),
161
160
  asString(getPath(node, "data.uri"))
162
161
  );
163
162
  }
164
163
  case "asset-hyperlink": {
165
- return renderLink(
166
- renderInlines(children(node), options),
164
+ return linkParts(
165
+ inlineParts(children(node), options),
167
166
  linkedAsset(node, options)?.url
168
167
  );
169
168
  }
170
169
  case "embedded-entry-inline": {
171
- return embeddedEntry(node, options);
170
+ return [{ markdown: embeddedEntry(node, options) }];
172
171
  }
173
172
  default: {
174
173
  // entry-hyperlink (no route to point at) and anything unknown: the label.
175
- return renderInlines(children(node), options);
174
+ return inlineParts(children(node), options);
176
175
  }
177
176
  }
178
177
  };
179
178
 
179
+ /** A block's inline children as Markdown, guarded as one block. */
180
+ const blockText = (
181
+ node: JsonObject,
182
+ options: ContentfulRichTextOptions
183
+ ): string =>
184
+ guardBlockStart(renderInline(inlineParts(children(node), options)));
185
+
180
186
  const renderList = (
181
187
  items: JsonObject[],
182
188
  marker: (index: number) => string,
@@ -197,12 +203,14 @@ const renderList = (
197
203
  )
198
204
  .join("\n");
199
205
 
206
+ // Each of a cell's paragraphs is a block as much as a page's is, so it goes
207
+ // through the same guard: two struck runs in one still meet as `~~~~`.
200
208
  const cellText = (
201
209
  cell: JsonObject,
202
210
  options: ContentfulRichTextOptions
203
211
  ): string =>
204
212
  children(cell)
205
- .map((paragraph) => renderInlines(children(paragraph), options))
213
+ .map((paragraph) => blockText(paragraph, options))
206
214
  .join(" ")
207
215
  .replaceAll(CELL_BREAK, " ")
208
216
  .replaceAll(CELL_PIPE, String.raw`\|`);
@@ -234,11 +242,11 @@ const renderBlock = (
234
242
  const type = asString(node.nodeType) ?? "";
235
243
  const heading = HEADING.exec(type)?.groups?.level;
236
244
  if (heading) {
237
- return `${headingPrefix(Number(heading))}${renderInlines(children(node), options)}`;
245
+ return `${headingPrefix(Number(heading))}${blockText(node, options)}`;
238
246
  }
239
247
  switch (type) {
240
248
  case "paragraph": {
241
- return renderInlines(children(node), options);
249
+ return blockText(node, options);
242
250
  }
243
251
  case "blockquote": {
244
252
  return blockquote(
@@ -1,3 +1,4 @@
1
+ import { BlumeError } from "../diagnostics.ts";
1
2
  import {
2
3
  assetFromEntry,
3
4
  contentfulRichTextToMarkdown,
@@ -111,19 +112,29 @@ export const contentfulSource = (
111
112
  );
112
113
  };
113
114
 
114
- const fetchEntries = async (): Promise<SourceEntry[]> => {
115
- const preview = ctx?.preview ?? false;
116
- const deliveryToken = options.token ?? process.env.CONTENTFUL_ACCESS_TOKEN;
117
- const previewToken =
118
- options.previewToken ?? process.env.CONTENTFUL_PREVIEW_TOKEN;
119
- // The Preview API rejects delivery tokens, so falling back to one would
120
- // only trade a clear config error for a 401.
121
- if (preview && !previewToken) {
122
- throw new Error(
123
- "Contentful --preview needs a Preview API token: set previewToken or CONTENTFUL_PREVIEW_TOKEN."
124
- );
115
+ const preview = ctx?.preview ?? false;
116
+ const previewToken = (): string | undefined =>
117
+ options.previewToken ?? process.env.CONTENTFUL_PREVIEW_TOKEN;
118
+
119
+ // The Preview API rejects delivery tokens, so falling back to one would
120
+ // only trade a clear config error for a 401. Checked before the cache, so
121
+ // a warm snapshot can't stand in for the drafts `--preview` asked for.
122
+ const assertConfigured = (): void => {
123
+ if (preview && !previewToken()) {
124
+ throw new BlumeError({
125
+ code: "BLUME_SOURCE_MISCONFIGURED",
126
+ message: `Source "${options.name}": Contentful --preview needs a Preview API token.`,
127
+ severity: "error",
128
+ suggestion:
129
+ "Set previewToken on contentfulSource, or the CONTENTFUL_PREVIEW_TOKEN environment variable.",
130
+ });
125
131
  }
126
- const token = preview ? previewToken : deliveryToken;
132
+ };
133
+
134
+ const fetchEntries = async (): Promise<SourceEntry[]> => {
135
+ const token = preview
136
+ ? previewToken()
137
+ : (options.token ?? process.env.CONTENTFUL_ACCESS_TOKEN);
127
138
  const client: RestClient = {
128
139
  fetchImpl: options.fetchImpl,
129
140
  headers: token ? { authorization: `Bearer ${token}` } : {},
@@ -163,10 +174,12 @@ export const contentfulSource = (
163
174
 
164
175
  return remoteSource(
165
176
  {
177
+ assertConfigured,
166
178
  fetchEntries,
167
179
  name: options.name,
168
180
  pollInterval: options.pollInterval,
169
181
  prefix: options.prefix,
182
+ withContext: (next) => contentfulSource(options, next),
170
183
  },
171
184
  ctx
172
185
  );
@@ -6,6 +6,8 @@ import { glob } from "tinyglobby";
6
6
 
7
7
  import { BlumeError } from "../diagnostics.ts";
8
8
  import matter from "../frontmatter.ts";
9
+ import type { Diagnostic } from "../types.ts";
10
+ import { unloadablePathDiagnostic } from "./normalize.ts";
9
11
  import type { ContentSource, SourceEntry, SourceLoadResult } from "./types.ts";
10
12
  import {
11
13
  baselineScanIgnore,
@@ -54,8 +56,24 @@ export const filesystemSource = (
54
56
  });
55
57
  files.sort();
56
58
 
59
+ // A file Astro's content loader can't read would publish a route that
60
+ // only ever renders "Page not found"; it is reported and left out.
61
+ const diagnostics: Diagnostic[] = [];
62
+ const loadable: string[] = [];
63
+ for (const file of files) {
64
+ const unloadable = unloadablePathDiagnostic(
65
+ relative(contentRoot, file),
66
+ file
67
+ );
68
+ if (unloadable) {
69
+ diagnostics.push(unloadable);
70
+ } else {
71
+ loadable.push(file);
72
+ }
73
+ }
74
+
57
75
  const entries = await Promise.all(
58
- files.map(async (file): Promise<SourceEntry> => {
76
+ loadable.map(async (file): Promise<SourceEntry> => {
59
77
  const source = await readFile(file, "utf-8");
60
78
  const ext = extname(file).toLowerCase();
61
79
  const format = ext === ".mdx" ? "mdx" : "md";
@@ -73,7 +91,7 @@ export const filesystemSource = (
73
91
  })
74
92
  );
75
93
 
76
- return { diagnostics: [], entries };
94
+ return { diagnostics, entries };
77
95
  };
78
96
 
79
97
  const validate = (): void => {
@@ -83,7 +101,9 @@ export const filesystemSource = (
83
101
  file: contentRoot,
84
102
  message: `Content root not found: ${options.root}`,
85
103
  severity: "error",
86
- suggestion: `Create a "${options.root}" folder with at least one .md or .mdx file, or set content.root in blume.config.ts.`,
104
+ // `content.root` is rejected beside `content.sources`, so the hint
105
+ // names both places a root is set.
106
+ suggestion: `Create a "${options.root}" folder with at least one .md or .mdx file, or point Blume at your docs folder: content.root in blume.config.ts, or the filesystem() source's root when you list content.sources.`,
87
107
  });
88
108
  }
89
109
  };
@@ -113,6 +133,8 @@ export const filesystemSource = (
113
133
 
114
134
  return {
115
135
  contentRoot,
136
+ exclude: options.exclude,
137
+ include: options.include,
116
138
  load,
117
139
  name: options.name,
118
140
  prefix: options.prefix,
@@ -1,4 +1,4 @@
1
- import type { Heading, Nodes } from "mdast";
1
+ import type { Definition, Heading, Link, Nodes } from "mdast";
2
2
  import { fromMarkdown } from "mdast-util-from-markdown";
3
3
  import { gfmFromMarkdown } from "mdast-util-gfm";
4
4
  import { toString as mdastToString } from "mdast-util-to-string";
@@ -15,6 +15,8 @@ import {
15
15
  pollingWatch,
16
16
  snapshotCache,
17
17
  } from "./cache.ts";
18
+ import { destination, escapeMarkdownText } from "./lower.ts";
19
+ import { REMOTE_TIMEOUT_MS } from "./remote.ts";
18
20
  import type {
19
21
  ContentSource,
20
22
  SourceContext,
@@ -43,6 +45,11 @@ export interface GithubReleasesSourceOptions {
43
45
  prereleases?: boolean;
44
46
  /** Repository name. */
45
47
  repo: string;
48
+ /**
49
+ * The site's own URL (`deployment.site`). A link in the notes back to it
50
+ * becomes root-relative, the way a page links to another page.
51
+ */
52
+ site?: string;
46
53
  }
47
54
 
48
55
  /** The subset of the GitHub release payload the adapter reads. */
@@ -164,6 +171,92 @@ const liftHeadings = (body: string): string => {
164
171
  return lifted;
165
172
  };
166
173
 
174
+ const WEB_PROTOCOL = /^https?:$/u;
175
+ const TITLE_SPECIALS = /["\\]/gu;
176
+
177
+ /** The origin of an http(s) site URL, or null when there is none. */
178
+ const webOrigin = (site?: string): string | null => {
179
+ const parsed = site ? URL.parse(site) : null;
180
+ return parsed && WEB_PROTOCOL.test(parsed.protocol) ? parsed.origin : null;
181
+ };
182
+
183
+ /** A link title as Markdown carries it, or nothing when there is none. */
184
+ const titleSuffix = (title?: string | null): string =>
185
+ title ? ` "${title.replaceAll(TITLE_SPECIALS, String.raw`\$&`)}"` : "";
186
+
187
+ /**
188
+ * The Markdown a link or definition to one of the site's own pages becomes,
189
+ * pointing at `path` instead. An inline link keeps its label as written, and
190
+ * an autolink keeps showing the URL it was written as.
191
+ */
192
+ const rewrittenLink = (
193
+ markdown: string,
194
+ node: Definition | Link,
195
+ path: string
196
+ ): string => {
197
+ const target = `${destination(path)}${titleSuffix(node.title)}`;
198
+ if (node.type === "definition") {
199
+ return `[${node.label ?? node.identifier}]: ${target}`;
200
+ }
201
+ const start = node.position?.start.offset ?? 0;
202
+ if (markdown[start] !== "[") {
203
+ return `[${escapeMarkdownText(mdastToString(node))}](${target})`;
204
+ }
205
+ const first = node.children.at(0)?.position?.start.offset ?? start + 1;
206
+ const last = node.children.at(-1)?.position?.end.offset ?? first;
207
+ return `[${markdown.slice(first, last)}](${target})`;
208
+ };
209
+
210
+ /**
211
+ * Point the notes' links to the site itself (`https://acme.dev/docs/x` when
212
+ * `deployment.site` is `https://acme.dev`) at the root-relative path, as a
213
+ * page on the site links to another: an absolute link hardcodes production,
214
+ * so it leaves a preview deploy and trips `blume audit`'s own-origin check.
215
+ */
216
+ const relativizeOwnLinks = (markdown: string, site?: string): string => {
217
+ const origin = webOrigin(site);
218
+ if (!origin) {
219
+ return markdown;
220
+ }
221
+ const spans: { end: number; start: number; text: string }[] = [];
222
+ const visit = (node: Nodes): void => {
223
+ if (node.type === "link" || node.type === "definition") {
224
+ const parsed = URL.parse(node.url);
225
+ if (parsed?.origin === origin) {
226
+ spans.push({
227
+ // fromMarkdown stamps every node's position.
228
+ end: node.position?.end.offset ?? 0,
229
+ start: node.position?.start.offset ?? 0,
230
+ text: rewrittenLink(
231
+ markdown,
232
+ node,
233
+ `${parsed.pathname}${parsed.search}${parsed.hash}`
234
+ ),
235
+ });
236
+ return;
237
+ }
238
+ }
239
+ if ("children" in node) {
240
+ for (const child of node.children) {
241
+ visit(child);
242
+ }
243
+ }
244
+ };
245
+ visit(
246
+ fromMarkdown(markdown, {
247
+ extensions: [gfm()],
248
+ mdastExtensions: [gfmFromMarkdown()],
249
+ })
250
+ );
251
+ let out = "";
252
+ let cursor = 0;
253
+ for (const span of spans) {
254
+ out += markdown.slice(cursor, span.start) + span.text;
255
+ cursor = span.end;
256
+ }
257
+ return out + markdown.slice(cursor);
258
+ };
259
+
167
260
  /** Slugify a tag into a stable, URL-safe source ref (`v1.2.0` -> `v1-2-0`). */
168
261
  const slugifyTag = (tag: string): string =>
169
262
  tag.toLowerCase().replaceAll(NON_SLUG, "-").replaceAll(EDGE_DASHES, "");
@@ -197,16 +290,22 @@ interface ChangelogFrontmatter {
197
290
  * generated `/changelog` timeline and RSS feed, and a summary derived from the
198
291
  * notes becomes the release page's meta description.
199
292
  */
200
- const releaseToEntry = (release: GithubRelease): SourceEntry => {
293
+ const releaseToEntry = (release: GithubRelease, site?: string): SourceEntry => {
201
294
  const version = release.tag_name.replace(LEADING_V, "");
202
295
  const title = release.name?.trim() || release.tag_name;
203
296
  const date = release.published_at ?? release.created_at;
204
297
  const category = release.prerelease ? "Prerelease" : "Release";
205
298
  // Release notes are the repository's content, not the site author's, so a
206
299
  // link whose destination isn't a web, mail, or relative address
207
- // (`javascript:`, `data:`) keeps only its label (see `safe-links.ts`).
300
+ // (`javascript:`, `data:`) keeps only its label (see `safe-links.ts`), and
301
+ // a link back to the site becomes root-relative.
208
302
  const body = liftHeadings(
209
- neutralizeUnsafeLinks((release.body ?? "").replaceAll("\r\n", "\n")).trim()
303
+ relativizeOwnLinks(
304
+ neutralizeUnsafeLinks(
305
+ (release.body ?? "").replaceAll("\r\n", "\n")
306
+ ).trim(),
307
+ site
308
+ )
210
309
  );
211
310
  // A summary in `seo.description` gives each release page a unique meta
212
311
  // description (instead of the site-wide fallback) without also rendering the
@@ -260,15 +359,33 @@ export const githubReleasesSource = (
260
359
  (options.drafts || !release.draft) &&
261
360
  (options.prereleases || !release.prerelease);
262
361
 
362
+ // Each page gets {@link REMOTE_TIMEOUT_MS}, body included: an API that
363
+ // accepts the connection and never answers would otherwise hold the scan,
364
+ // and so the build or every dev rescan, indefinitely. The timeout's error
365
+ // names the URL and the limit, so the empty-changelog warning says what
366
+ // stalled instead of only that an operation was aborted.
263
367
  const fetchPage = async (page: number): Promise<GithubRelease[]> => {
264
368
  const url = `${base}/repos/${options.owner}/${options.repo}/releases?per_page=${PER_PAGE}&page=${page}`;
265
- const res = await doFetch(url, { headers: githubHeaders() });
266
- if (!res.ok) {
267
- throw new Error(`${url} -> ${res.status}`);
369
+ try {
370
+ const res = await doFetch(url, {
371
+ headers: githubHeaders(),
372
+ signal: AbortSignal.timeout(REMOTE_TIMEOUT_MS),
373
+ });
374
+ if (!res.ok) {
375
+ throw new Error(`${url} -> ${res.status}`);
376
+ }
377
+ // SAFETY: GitHub's releases endpoint returns a JSON array of release
378
+ // objects; `GithubRelease` models only the fields the adapter reads.
379
+ return (await res.json()) as GithubRelease[];
380
+ } catch (error) {
381
+ if (error instanceof DOMException && error.name === "TimeoutError") {
382
+ throw new Error(
383
+ `${url} did not respond within ${REMOTE_TIMEOUT_MS / 1000}s`,
384
+ { cause: error }
385
+ );
386
+ }
387
+ throw error;
268
388
  }
269
- // SAFETY: GitHub's releases endpoint returns a JSON array of release
270
- // objects; `GithubRelease` models only the fields the adapter reads.
271
- return (await res.json()) as GithubRelease[];
272
389
  };
273
390
 
274
391
  const fetchReleases = async (): Promise<GithubRelease[]> => {
@@ -295,7 +412,9 @@ export const githubReleasesSource = (
295
412
  cache,
296
413
  async () => {
297
414
  const releases = await fetchReleases();
298
- return releases.map(releaseToEntry);
415
+ return releases.map((release) =>
416
+ releaseToEntry(release, options.site)
417
+ );
299
418
  },
300
419
  refresh
301
420
  );
@@ -347,5 +466,6 @@ export const githubReleasesSource = (
347
466
  () => load()
348
467
  )
349
468
  : undefined,
469
+ withContext: (next) => githubReleasesSource(options, next),
350
470
  };
351
471
  };
@@ -7,17 +7,18 @@
7
7
  */
8
8
  import type { JsonObject } from "./json.ts";
9
9
  import { asNumber, asObject, asString, getPath, objectsIn } from "./json.ts";
10
- import type { InlineMarks } from "./lower.ts";
10
+ import type { InlineMarks, InlinePart } from "./lower.ts";
11
11
  import {
12
12
  absoluteUrl,
13
13
  blockquote,
14
+ guardBlockStart,
14
15
  headingPrefix,
15
16
  image,
16
17
  indent,
18
+ linkParts,
17
19
  listItem,
18
20
  markdownDocument,
19
21
  renderInline,
20
- renderLink,
21
22
  unsupported,
22
23
  writesMdx,
23
24
  } from "./lower.ts";
@@ -66,45 +67,54 @@ const customBlock = (node: JsonObject, options: LexicalOptions): string => {
66
67
  );
67
68
  };
68
69
 
69
- const renderInlines = (nodes: JsonObject[], options: LexicalOptions): string =>
70
- nodes
71
- // oxlint-disable-next-line no-use-before-define -- mutual recursion: a link's label is itself inline content
72
- .map((node) => renderInlineNode(node, options))
73
- .join("");
70
+ const inlineParts = (
71
+ nodes: JsonObject[],
72
+ options: LexicalOptions
73
+ ): InlinePart[] =>
74
+ // oxlint-disable-next-line no-use-before-define -- mutual recursion: a link's label is itself inline content
75
+ nodes.flatMap((node) => inlineNodeParts(node, options));
74
76
 
75
- const renderInlineNode = (
77
+ const inlineNodeParts = (
76
78
  node: JsonObject,
77
79
  options: LexicalOptions
78
- ): string => {
80
+ ): InlinePart[] => {
79
81
  switch (typeOf(node)) {
80
82
  case "text": {
81
- return renderInline(asString(node.text) ?? "", marksOf(node));
83
+ return [{ marks: marksOf(node), text: asString(node.text) ?? "" }];
82
84
  }
83
85
  case "linebreak": {
84
- return "\n";
86
+ return [{ markdown: "\n" }];
85
87
  }
86
88
  case "tab": {
87
- return "\t";
89
+ return [{ markdown: "\t" }];
88
90
  }
89
91
  case "link":
90
92
  case "autolink": {
91
93
  // `fields.url` on Payload 3; `url` on older Lexical payloads. An
92
94
  // internal link (`linkType: "internal"`) carries a document, not a
93
95
  // URL, so its label stays.
94
- return renderLink(
95
- renderInlines(children(node), options),
96
+ return linkParts(
97
+ inlineParts(children(node), options),
96
98
  asString(getPath(node, "fields.url")) ?? asString(node.url)
97
99
  );
98
100
  }
99
101
  case "inlineBlock": {
100
- return customBlock(node, options);
102
+ return [{ markdown: customBlock(node, options) }];
101
103
  }
102
104
  default: {
103
- return renderInlines(children(node), options);
105
+ return inlineParts(children(node), options);
104
106
  }
105
107
  }
106
108
  };
107
109
 
110
+ /** Inline nodes as Markdown, guarded as one block. */
111
+ const inlineText = (nodes: JsonObject[], options: LexicalOptions): string =>
112
+ guardBlockStart(renderInline(inlineParts(nodes, options)));
113
+
114
+ /** A text block's inline children, joined and guarded as one block. */
115
+ const blockText = (node: JsonObject, options: LexicalOptions): string =>
116
+ inlineText(children(node), options);
117
+
108
118
  const renderUpload = (node: JsonObject, options: LexicalOptions): string => {
109
119
  const value = asObject(node.value);
110
120
  const url = value ? asString(value.url) : undefined;
@@ -140,7 +150,7 @@ const renderList = (node: JsonObject, options: LexicalOptions): string => {
140
150
  if (listType === "check") {
141
151
  check = item.checked === true ? "[x] " : "[ ] ";
142
152
  }
143
- const line = `${check}${renderInlines(inline, options)}`;
153
+ const line = `${check}${inlineText(inline, options)}`;
144
154
  items.push(listItem(lastMarker, sub ? `${line}\n${sub}` : line));
145
155
  index += 1;
146
156
  }
@@ -150,14 +160,14 @@ const renderList = (node: JsonObject, options: LexicalOptions): string => {
150
160
  const renderBlock = (node: JsonObject, options: LexicalOptions): string => {
151
161
  switch (typeOf(node)) {
152
162
  case "paragraph": {
153
- return renderInlines(children(node), options);
163
+ return blockText(node, options);
154
164
  }
155
165
  case "heading": {
156
166
  const level = Number((asString(node.tag) ?? "h1").slice(1));
157
- return `${headingPrefix(level)}${renderInlines(children(node), options)}`;
167
+ return `${headingPrefix(level)}${blockText(node, options)}`;
158
168
  }
159
169
  case "quote": {
160
- return blockquote(renderInlines(children(node), options));
170
+ return blockquote(blockText(node, options));
161
171
  }
162
172
  case "list": {
163
173
  return renderList(node, options);