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,42 +9,66 @@ import { isSafeHref } from "../safe-href.ts";
9
9
 
10
10
  // Markdown/raw-HTML structure characters. Rich-text leaves are *plain text* —
11
11
  // formatting arrives as marks, never as syntax in the text — so a literal
12
- // `*`, `_`, `[`, backtick, `~`, or `<` typed in the CMS must render as
13
- // itself. Unescaped, it opened emphasis or a code span mid-paragraph, and `<`
14
- // let CMS prose inject raw HTML into the rendered page. `{` and `}` open an
15
- // expression once an entry is written as MDX (see {@link writesMdx}). CommonMark
12
+ // `*`, `_`, `[`, backtick, `~`, `^`, or `<` typed in the CMS must render as
13
+ // itself. Unescaped, it opened emphasis, a code span, or a superscript
14
+ // (`2^10 and 2^20`) mid-paragraph, and `<` let CMS prose inject raw HTML into
15
+ // the rendered page. `{` and `}` open an expression once an entry is written
16
+ // as MDX (see {@link writesMdx}), and `$$` opens math there. CommonMark
16
17
  // backslash-escapes every ASCII punctuation character, so `\*` is always the
17
18
  // literal asterisk, in `.md` and `.mdx` alike.
18
- const MARKDOWN_SPECIALS = /[\\`*_{}[\]~<]/gu;
19
+ const MARKDOWN_SPECIALS = /[\\`*_{}[\]~<$^]/gu;
20
+
21
+ // MDX reads a colon ahead of a letter or digit as a text directive (`10:30`,
22
+ // `og:image`, `pets:read`), so that colon is escaped too. One inside a bare
23
+ // URL (`http://localhost:3000`) stays as written: an escape there would end
24
+ // the autolink early.
25
+ const DIRECTIVE_COLON = /(?<!(?:[a-z][\w+.-]*:\/\/|www\.)\S*):(?=[a-z\d])/giu;
26
+
27
+ // GFM autolinks a bare `http://`, `https://`, or `www.` URL in prose, taking
28
+ // its characters verbatim up to the next space or `<`: an escape inside one
29
+ // reaches the href as a backslash (`a\_b`), so prose leaves a bare URL's
30
+ // characters as written. That includes a trailing `*`, `_`, or `~`, which
31
+ // GFM leaves out of the link: escaped, its backslash would still land inside.
32
+ // A link label is the exception — GFM links nothing inside one, so there
33
+ // every special is escaped (see `linkParts`).
34
+ const BARE_URL = /\b(?:https?:\/\/|www\.)[\w-][^\s<]*/giu;
19
35
 
20
36
  // An entity or numeric character reference (`&copy;`, `&#38;`) is decoded
21
37
  // even in plain text, so one typed in the CMS would render as the character
22
38
  // it names rather than as written. A bare `&` is left alone.
23
39
  const CHARACTER_REFERENCE = /&(?=#?[a-z0-9]+;)/giu;
24
40
 
25
- // Block syntax is only syntax at the start of a line: `# `, `- `, `+ ` and
26
- // `1. `/`1) ` need the space (or line end) to become a heading or list item,
27
- // while `>` opens a quote on its own, and a line of only `=` or `-` turns the
28
- // line above it into a heading (or, alone, into a rule). A CMS paragraph that
29
- // begins with one of these — a soft break inside it counts as a line start
30
- // too — must stay prose.
41
+ // Block syntax is only syntax at the start of a line: `# ` (through
42
+ // `###### `), `- `, `+ ` and `1. `/`1) ` need the space (or line end) to
43
+ // become a heading or list item, while `>` opens a quote on its own, and a
44
+ // line of only `=` or `-` turns the line above it into a heading (or, alone,
45
+ // into a rule). Indentation of up to three spaces still counts. A CMS
46
+ // paragraph that begins with one of these — a soft break inside it counts as
47
+ // a line start too — must stay prose.
31
48
  const BLOCK_START =
32
- /^(?<marker>[#+-]|\d+[.)])(?=[ \t]|$)|^(?<quote>>)|^(?<rule>=+|-{2,})(?=[ \t]*$)/gmu;
49
+ /^(?<indent>[ \t]*)(?:(?<marker>#+|[+-]|\d+[.)])(?=[ \t]|$)|(?<quote>>)|(?<rule>=+|-{2,})(?=[ \t]*$))/gmu;
50
+
51
+ // MDX reads a top-level line that opens with `import` or `export` followed by
52
+ // a space, a tab, `{`, or `*` as an ESM statement, and prose like "import the
53
+ // CSV first" then fails the whole page. The keyword's first letter as a
54
+ // character reference renders the same in `.md` and `.mdx`, and MDX no longer
55
+ // sees the keyword. The keyword is escaped wherever a word ends on it, which
56
+ // covers every character that opens a statement. Only a block's first line
57
+ // counts; a blank line inside the text starts a new block.
58
+ const ESM_START = /(?<=^|\n[ \t]*\n)(?<keyword>import|export)(?![\w$])/gu;
33
59
 
34
- // MDX reads a top-level paragraph that opens with `import ` or `export ` as an
35
- // ESM statement, and prose like "import the CSV first" then fails the whole
36
- // page. The keyword's first letter as a character reference renders the same
37
- // in `.md` and `.mdx`, and MDX no longer sees the keyword. Only a paragraph's
38
- // first line counts; a blank line inside a run starts a new paragraph.
39
- const ESM_START = /(?<=^|\n[ \t]*\n)(?<keyword>import|export)(?= )/gu;
60
+ // Whitespace opening a block: four columns of it (or a tab) make an indented
61
+ // code block, and a paragraph drops it when it renders anyway.
62
+ const BLOCK_INDENT = /(?<=^|\n[ \t]*\n)[ \t]+/gu;
40
63
 
41
64
  const escapeBlockStart = (text: string): string =>
42
- text.replaceAll(BLOCK_START, (marker: string) => {
65
+ text.replaceAll(BLOCK_START, (match: string, indent: string) => {
43
66
  // A numbered marker escapes its punctuation (`1\.`), the rest themselves.
67
+ const marker = match.slice(indent.length);
44
68
  const index = marker.search(/[.)]/u);
45
69
  return index === -1
46
- ? `\\${marker}`
47
- : `${marker.slice(0, index)}\\${marker.slice(index)}`;
70
+ ? `${indent}\\${marker}`
71
+ : `${indent}${marker.slice(0, index)}\\${marker.slice(index)}`;
48
72
  });
49
73
 
50
74
  const escapeEsmStart = (text: string): string =>
@@ -53,15 +77,37 @@ const escapeEsmStart = (text: string): string =>
53
77
  (keyword: string) => `&#${keyword.codePointAt(0)};${keyword.slice(1)}`
54
78
  );
55
79
 
56
- export const escapeMarkdownText = (text: string): string =>
80
+ const escapeSpecials = (text: string): string =>
81
+ text.replaceAll(MARKDOWN_SPECIALS, String.raw`\$&`);
82
+
83
+ /** The escapes that apply everywhere, once the specials are escaped. */
84
+ const escapeRest = (text: string): string =>
57
85
  escapeEsmStart(
58
86
  escapeBlockStart(
59
87
  text
60
- .replaceAll(MARKDOWN_SPECIALS, String.raw`\$&`)
88
+ .replaceAll(DIRECTIVE_COLON, String.raw`\:`)
61
89
  .replaceAll(CHARACTER_REFERENCE, String.raw`\&`)
62
90
  )
63
91
  );
64
92
 
93
+ /**
94
+ * CMS text as Markdown that renders as written, wherever it lands — a link
95
+ * label included, where GFM autolinks nothing, so every special is escaped.
96
+ */
97
+ export const escapeMarkdownText = (text: string): string =>
98
+ escapeRest(escapeSpecials(text));
99
+
100
+ /** CMS prose as {@link escapeMarkdownText}, except inside a bare URL. */
101
+ const escapeProse = (text: string): string => {
102
+ let out = "";
103
+ let last = 0;
104
+ for (const match of text.matchAll(BARE_URL)) {
105
+ out += escapeSpecials(text.slice(last, match.index)) + match[0];
106
+ last = match.index + match[0].length;
107
+ }
108
+ return escapeRest(out + escapeSpecials(text.slice(last)));
109
+ };
110
+
65
111
  /** The marks a lowerer can put on an inline run. */
66
112
  export interface InlineMarks {
67
113
  bold?: boolean;
@@ -70,6 +116,20 @@ export interface InlineMarks {
70
116
  strike?: boolean;
71
117
  }
72
118
 
119
+ /** A run of CMS text and the marks on it. */
120
+ export interface InlineRun {
121
+ marks: InlineMarks;
122
+ text: string;
123
+ }
124
+
125
+ /** Inline Markdown a lowerer rendered itself: a link, an embed, a break. */
126
+ export interface InlineMarkdown {
127
+ markdown: string;
128
+ }
129
+
130
+ /** One piece of a block's inline content, in order. */
131
+ export type InlinePart = InlineMarkdown | InlineRun;
132
+
73
133
  const BACKTICK_RUN = /`+/gu;
74
134
 
75
135
  /** The longest run of backticks in `text`, 0 when there is none. */
@@ -92,52 +152,195 @@ export const codeSpan = (code: string): string => {
92
152
  return `${fence}${pad}${code}${pad}${fence}`;
93
153
  };
94
154
 
155
+ // Two struck runs side by side meet as `~~~~`: a closer and an opener that
156
+ // GFM reads as one four-tilde run, so both print as literal tildes. A literal
157
+ // `~` is escaped outside a bare URL, so an unescaped `~~~~` outside a code
158
+ // span, a link destination, or a bare URL is exactly that seam, and dropping
159
+ // it merges the runs into one strikethrough. The other alternatives only step
160
+ // over escapes, code spans, destinations, and bare URLs so the seam is never
161
+ // matched inside them.
162
+ const SEAM_STEPS =
163
+ /\\[\s\S]|(?<ticks>`+)[\s\S]*?(?<!`)\k<ticks>(?!`)|\]\((?:<(?:\\.|[^\\<>\n])*>|(?:\\.|[^\\\s)])+)\)/u;
164
+ const STRIKE_SEAM = new RegExp(
165
+ `${SEAM_STEPS.source}|${BARE_URL.source}|~~~~`,
166
+ "giu"
167
+ );
168
+
169
+ // A heading that ends in a space and a run of `#` reads that run as the
170
+ // optional closing sequence and drops it (`## Issue #` renders "Issue").
171
+ const CLOSING_HASHES = /(?<=[ \t])#+(?=[ \t]*$)/u;
172
+
173
+ /**
174
+ * A block's inline text, guarded where only the whole block can tell. Runs
175
+ * are escaped one at a time, but "import" ending one run and a space or code
176
+ * span opening the next form an MDX `import` statement only once they are
177
+ * joined, whitespace the first runs put in front of a block turns it into
178
+ * an indented code block, two struck runs fuse into literal tildes, and a
179
+ * trailing ` #` is a heading's closing sequence. Apply it to the joined text
180
+ * of a paragraph, heading, quote, or list item — never to code.
181
+ */
182
+ export const guardBlockStart = (text: string): string =>
183
+ escapeEsmStart(
184
+ text
185
+ .replaceAll(BLOCK_INDENT, "")
186
+ .replaceAll(STRIKE_SEAM, (match: string) =>
187
+ match === "~~~~" ? "" : match
188
+ )
189
+ .replace(CLOSING_HASHES, String.raw`\$&`)
190
+ );
191
+
95
192
  const EDGE_SPACE = /^(?<lead>\s*)(?<body>[\s\S]*?)(?<trail>\s*)$/u;
96
193
 
97
194
  /**
98
- * Wrap a text run in the Markdown for its marks. Code stays verbatim inside
99
- * the backticks (an escape would render as a backslash); everything else is
100
- * escaped first. Whitespace at either edge moves outside the delimiters,
101
- * since `** bold **` is not strong emphasis but `**bold**` is — editors
102
- * routinely bold a word together with the space after it.
195
+ * `text` with `render` applied between its edge whitespace, or as it is when
196
+ * it is only whitespace.
103
197
  */
104
- export const renderInline = (text: string, marks: InlineMarks): string => {
198
+ const betweenEdges = (
199
+ text: string,
200
+ render: (body: string) => string
201
+ ): string => {
105
202
  const match = EDGE_SPACE.exec(text);
106
- const lead = match?.groups?.lead ?? "";
107
203
  const body = match?.groups?.body ?? "";
108
- const trail = match?.groups?.trail ?? "";
109
204
  if (body === "") {
110
205
  return text;
111
206
  }
112
- let out = marks.code ? codeSpan(body) : escapeMarkdownText(body);
113
- if (marks.bold) {
114
- out = `**${out}**`;
115
- }
116
- if (marks.italic) {
117
- out = `*${out}*`;
207
+ return `${match?.groups?.lead ?? ""}${render(body)}${match?.groups?.trail ?? ""}`;
208
+ };
209
+
210
+ const MARK_NAMES = ["bold", "code", "italic", "strike"] as const;
211
+
212
+ const sameMarks = (
213
+ a: InlineMarks,
214
+ b: InlineMarks,
215
+ names: readonly (keyof InlineMarks)[] = MARK_NAMES
216
+ ): boolean => names.every((mark) => Boolean(a[mark]) === Boolean(b[mark]));
217
+
218
+ /** A stretch of neighboring runs that share marks, and those marks. */
219
+ interface RunGroup {
220
+ marks: InlineMarks;
221
+ runs: InlineRun[];
222
+ }
223
+
224
+ /** Runs split into stretches of neighbors that share the `names` marks. */
225
+ const groupRuns = (
226
+ runs: InlineRun[],
227
+ names: readonly (keyof InlineMarks)[] = MARK_NAMES
228
+ ): RunGroup[] => {
229
+ const groups: RunGroup[] = [];
230
+ for (const run of runs) {
231
+ const group = groups.at(-1);
232
+ if (group && sameMarks(group.marks, run.marks, names)) {
233
+ group.runs.push(run);
234
+ } else {
235
+ groups.push({ marks: run.marks, runs: [run] });
236
+ }
118
237
  }
119
- if (marks.strike) {
120
- out = `~~${out}~~`;
238
+ return groups;
239
+ };
240
+
241
+ /**
242
+ * Consecutive text runs as Markdown. Neighbors with the same bold, italic,
243
+ * and strikethrough share one set of delimiters — rendered one run at a
244
+ * time, two bold runs met as `****` and two italic ones as `**`, which print
245
+ * as literal asterisks. Code stays verbatim inside the backticks (an escape
246
+ * would render as a backslash), and neighboring code joins into one span;
247
+ * everything else is escaped. Whitespace at either edge moves outside the
248
+ * delimiters, since `** bold **` is not strong emphasis but `**bold**` is —
249
+ * editors routinely bold a word together with the space after it.
250
+ */
251
+ const renderRuns = (
252
+ runs: InlineRun[],
253
+ escape: (text: string) => string
254
+ ): string =>
255
+ groupRuns(runs, ["bold", "italic", "strike"])
256
+ .map(({ marks, runs: shared }) => {
257
+ const inner = groupRuns(shared)
258
+ .map((same) =>
259
+ betweenEdges(same.runs.map((run) => run.text).join(""), (body) =>
260
+ same.marks.code ? codeSpan(body) : escape(body)
261
+ )
262
+ )
263
+ .join("");
264
+ return betweenEdges(inner, (body) => {
265
+ let out = body;
266
+ if (marks.bold) {
267
+ out = `**${out}**`;
268
+ }
269
+ if (marks.italic) {
270
+ out = `*${out}*`;
271
+ }
272
+ if (marks.strike) {
273
+ out = `~~${out}~~`;
274
+ }
275
+ return out;
276
+ });
277
+ })
278
+ .join("");
279
+
280
+ /** Inline parts as Markdown, each stretch of runs through {@link renderRuns}. */
281
+ const renderParts = (
282
+ parts: InlinePart[],
283
+ escape: (text: string) => string
284
+ ): string => {
285
+ let out = "";
286
+ let runs: InlineRun[] = [];
287
+ for (const part of parts) {
288
+ if ("markdown" in part) {
289
+ out += renderRuns(runs, escape) + part.markdown;
290
+ runs = [];
291
+ } else {
292
+ runs.push(part);
293
+ }
121
294
  }
122
- return `${lead}${out}${trail}`;
295
+ return out + renderRuns(runs, escape);
123
296
  };
124
297
 
125
- // A destination with whitespace or parentheses ends early in `[label](…)`;
126
- // CommonMark's pointy-bracket form carries it intact.
127
- const UNSAFE_DESTINATION = /[\s()]/u;
298
+ /** A block's inline content as Markdown. */
299
+ export const renderInline = (parts: InlinePart[]): string =>
300
+ renderParts(parts, escapeProse);
301
+
302
+ // A destination with whitespace, a control character, or parentheses ends
303
+ // early in `[label](…)`; CommonMark's pointy-bracket form carries it intact.
304
+ // oxlint-disable-next-line no-control-regex -- control characters are exactly what the bare form refuses.
305
+ const UNSAFE_DESTINATION = /[\s()\u0000-\u001F\u007F]/u;
128
306
 
129
- /** A link or image destination as Markdown can carry it verbatim. */
130
- export const destination = (url: string): string =>
131
- UNSAFE_DESTINATION.test(url) ? `<${url}>` : url;
307
+ // Markdown decodes a destination before the browser sees it: a backslash
308
+ // escape or a character reference becomes its character, so
309
+ // `java&#115;cript:` would reach the `href` as `javascript:` after the safety
310
+ // check passed it. Escaping both — and `<`/`>`, which would close the
311
+ // pointy form early and let the rest of the URL open a second link — keeps
312
+ // the `href` exactly the URL the CMS holds.
313
+ const DESTINATION_SPECIALS = /[\\<>]|&(?=#?[a-z0-9]+;)/giu;
314
+
315
+ // A destination can't hold a line break, and the URL parser drops tabs and
316
+ // line breaks anyway, so removing them leaves the same URL.
317
+ const DESTINATION_IGNORED = /[\t\n\r]/gu;
318
+
319
+ /** A link or image destination as Markdown carries it verbatim. */
320
+ export const destination = (url: string): string => {
321
+ const escaped = url
322
+ .replaceAll(DESTINATION_IGNORED, "")
323
+ .replaceAll(DESTINATION_SPECIALS, String.raw`\$&`);
324
+ return UNSAFE_DESTINATION.test(escaped) ? `<${escaped}>` : escaped;
325
+ };
132
326
 
133
327
  /**
134
- * A Markdown link, or the label alone when the target is missing — or unsafe:
135
- * CMS content is the editor's, not the site author's, so a destination that
136
- * isn't a web, mail, or relative address (`javascript:`, `data:`) never
137
- * becomes a clickable link on the docs site (see `safe-links.ts`).
328
+ * A Markdown link over inline parts, or the parts alone when the target is
329
+ * missing — or unsafe: CMS content is the editor's, not the site author's, so
330
+ * a destination that isn't a web, mail, or relative address (`javascript:`,
331
+ * `data:`) never becomes a clickable link on the docs site (see
332
+ * `safe-links.ts`). The parts stay parts then, so their runs merge with their
333
+ * neighbors'. A label escapes a bare URL too: GFM autolinks nothing inside a
334
+ * link, so its specials would read as syntax.
138
335
  */
139
- export const renderLink = (label: string, href?: string): string =>
140
- href && isSafeHref(href) ? `[${label}](${destination(href)})` : label;
336
+ export const linkParts = (parts: InlinePart[], href?: string): InlinePart[] =>
337
+ href && isSafeHref(href)
338
+ ? [
339
+ {
340
+ markdown: `[${renderParts(parts, escapeMarkdownText)}](${destination(href)})`,
341
+ },
342
+ ]
343
+ : parts;
141
344
 
142
345
  /** The ATX prefix for a heading level, clamped to Markdown's six. */
143
346
  export const headingPrefix = (level: number): string =>
@@ -172,10 +375,20 @@ export const indent = (text: string, width: number): string => {
172
375
  .join("\n");
173
376
  };
174
377
 
175
- /** A fenced code block whose fence outruns any backtick run in the code. */
378
+ // A fence's language is one word of its info string. A backtick in it keeps
379
+ // the fence from opening at all, so the code renders as Markdown and HTML,
380
+ // and whitespace would start the meta Blume reads code titles from; a
381
+ // language that isn't a single such word is dropped.
382
+ const FENCE_LANGUAGE = /^[^\s`]+$/u;
383
+
384
+ /**
385
+ * A fenced code block whose fence outruns any backtick run in the code,
386
+ * labeled with its language when that is one word a fence can carry.
387
+ */
176
388
  export const codeFence = (code: string, language = ""): string => {
177
389
  const fence = "`".repeat(Math.max(3, longestBacktickRun(code) + 1));
178
- return `${fence}${language}\n${code}\n${fence}`;
390
+ const label = FENCE_LANGUAGE.test(language) ? language : "";
391
+ return `${fence}${label}\n${code}\n${fence}`;
179
392
  };
180
393
 
181
394
  /** A Markdown image; the alt is escaped so a `]` in a caption can't close it. */
@@ -193,12 +406,19 @@ export const writesMdx = <Node>(
193
406
  serializers?: Record<string, (node: Node) => string>
194
407
  ): boolean => serializers !== undefined && Object.keys(serializers).length > 0;
195
408
 
409
+ // A node's type is the CMS's data too: a `*/` (or `--`) in it would close the
410
+ // comment early and let the rest run as MDX (or render as HTML). A space
411
+ // between the two characters keeps it inside.
412
+ const COMMENT_END = /\*\/|--/gu;
413
+
196
414
  /**
197
415
  * A comment marking a node the lowerer has no Markdown for — an MDX comment
198
416
  * when the output is MDX, which rejects `<!-- -->`.
199
417
  */
200
- export const unsupported = (what: string, mdx = false): string =>
201
- mdx ? `{/* unsupported ${what} */}` : `<!-- unsupported ${what} -->`;
418
+ export const unsupported = (what: string, mdx = false): string => {
419
+ const label = what.replaceAll(COMMENT_END, (end) => [...end].join(" "));
420
+ return mdx ? `{/* unsupported ${label} */}` : `<!-- unsupported ${label} -->`;
421
+ };
202
422
 
203
423
  /** Blocks separated by blank lines; empty blocks are dropped. */
204
424
  export const joinBlocks = (blocks: string[]): string =>
@@ -9,6 +9,7 @@ import {
9
9
  pollingWatch,
10
10
  snapshotCache,
11
11
  } from "./cache.ts";
12
+ import { REMOTE_TIMEOUT_MS } from "./remote.ts";
12
13
  import type {
13
14
  ContentSource,
14
15
  SourceContext,
@@ -64,6 +65,38 @@ const githubHeaders = (url: string): Record<string, string> => {
64
65
  return GITHUB_HOSTS.has(host) ? { authorization: `Bearer ${token}` } : {};
65
66
  };
66
67
 
68
+ /**
69
+ * GET `url`'s body, giving up after {@link REMOTE_TIMEOUT_MS}: a server that
70
+ * accepts the connection and never finishes answering would otherwise hold the
71
+ * scan, and so the build or every dev rescan, indefinitely. A timeout rejects
72
+ * with an error naming the URL and the limit, so the skip or offline
73
+ * diagnostic says what stalled instead of only that an operation was aborted.
74
+ */
75
+ const fetchText = async (
76
+ url: string,
77
+ doFetch: typeof fetch
78
+ ): Promise<string> => {
79
+ try {
80
+ // The signal bounds the body read too, not only the response headers.
81
+ const res = await doFetch(url, {
82
+ headers: githubHeaders(url),
83
+ signal: AbortSignal.timeout(REMOTE_TIMEOUT_MS),
84
+ });
85
+ if (!res.ok) {
86
+ throw new Error(`${url} -> ${res.status}`);
87
+ }
88
+ return await res.text();
89
+ } catch (error) {
90
+ if (error instanceof DOMException && error.name === "TimeoutError") {
91
+ throw new Error(
92
+ `${url} did not respond within ${REMOTE_TIMEOUT_MS / 1000}s`,
93
+ { cause: error }
94
+ );
95
+ }
96
+ throw error;
97
+ }
98
+ };
99
+
67
100
  interface GithubTreeEntry {
68
101
  path: string;
69
102
  type: string;
@@ -78,13 +111,10 @@ const enumerateGithub = async (
78
111
  const { owner, repo, ref } = github;
79
112
  const base = github.path.replaceAll(/^\/|\/$/gu, "");
80
113
  const treeUrl = `https://api.github.com/repos/${owner}/${repo}/git/trees/${ref}?recursive=1`;
81
- const res = await doFetch(treeUrl, { headers: githubHeaders(treeUrl) });
82
- if (!res.ok) {
83
- throw new Error(`${treeUrl} -> ${res.status}`);
84
- }
114
+ const text = await fetchText(treeUrl, doFetch);
85
115
  // SAFETY: GitHub's git/trees endpoint returns this envelope; a missing or
86
116
  // differently-typed field falls through the `?? []` and blob filters below.
87
- const body = (await res.json()) as {
117
+ const body = JSON.parse(text) as {
88
118
  tree?: GithubTreeEntry[];
89
119
  truncated?: boolean;
90
120
  };
@@ -157,13 +187,7 @@ export const mdxRemoteSource = (
157
187
  };
158
188
 
159
189
  const fetchEntry = async (item: RemoteRef): Promise<SourceEntry> => {
160
- const res = await doFetch(item.fetchUrl, {
161
- headers: githubHeaders(item.fetchUrl),
162
- });
163
- if (!res.ok) {
164
- throw new Error(`${item.fetchUrl} -> ${res.status}`);
165
- }
166
- const text = await res.text();
190
+ const text = await fetchText(item.fetchUrl, doFetch);
167
191
  const parsed = matter(text);
168
192
  const format = item.ref.toLowerCase().endsWith(".mdx") ? "mdx" : "md";
169
193
  return {
@@ -193,16 +217,19 @@ export const mdxRemoteSource = (
193
217
  severity: "warning",
194
218
  });
195
219
  }
220
+ const reasons: string[] = [];
196
221
  const settled = await Promise.all(
197
222
  refs.map(async (ref) => {
198
223
  try {
199
224
  return await fetchEntry(ref);
200
225
  } catch (error) {
226
+ // SAFETY: fetch and decode failures throw Error instances;
227
+ // only the message is read for the skip diagnostic.
228
+ const reason = (error as Error).message;
229
+ reasons.push(reason);
201
230
  skipped.push({
202
231
  code: "BLUME_SOURCE_FETCH_FAILED",
203
- // SAFETY: fetch and decode failures throw Error instances;
204
- // only the message is read for the skip diagnostic.
205
- message: `Source "${options.name}" skipped "${ref.ref}" (${(error as Error).message}); the rest were imported.`,
232
+ message: `Source "${options.name}" skipped "${ref.ref}" (${reason}); the rest were imported.`,
206
233
  severity: "warning",
207
234
  });
208
235
  return null;
@@ -215,9 +242,13 @@ export const mdxRemoteSource = (
215
242
  // Only a total wipeout is a hard failure — let loadWithCache fall back
216
243
  // to cache or fail loudly rather than silently importing nothing. A
217
244
  // partial failure keeps the healthy pages and warns about the rest.
245
+ // The first failure's reason rides along, so a server that timed out
246
+ // on every file says so.
218
247
  if (refs.length > 0 && entries.length === 0) {
219
248
  skipped.length = 0;
220
- throw new Error(`all ${refs.length} remote file(s) failed to fetch`);
249
+ throw new Error(
250
+ `all ${refs.length} remote file(s) failed to fetch (${reasons[0]})`
251
+ );
221
252
  }
222
253
  return entries;
223
254
  },
@@ -243,6 +274,10 @@ export const mdxRemoteSource = (
243
274
  return {
244
275
  load,
245
276
  name: options.name,
277
+ // Refs are the remote tree's file and folder names, so an ordering prefix
278
+ // (`01-getting-started/02-install.mdx`) sorts the sidebar and drops from
279
+ // the route, the way it does in a local content folder.
280
+ orderedNames: true,
246
281
  prefix: options.prefix,
247
282
  read,
248
283
  staged: true,
@@ -253,5 +288,6 @@ export const mdxRemoteSource = (
253
288
  () => load()
254
289
  )
255
290
  : undefined,
291
+ withContext: (next) => mdxRemoteSource(options, next),
256
292
  };
257
293
  };