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
@@ -1,5 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ resolveFallbackLocale,
4
+ localizeRoute,
5
+ localizeInternalPath,
6
+ i18nDiagnostics,
3
7
  blumeReferences,
4
8
  frontmatter_default,
5
9
  resolveDocsCollection,
@@ -12,10 +16,11 @@ import {
12
16
  pollingWatch,
13
17
  snapshotCache,
14
18
  loadWithCache,
15
- resolveFallbackLocale,
16
- localizeRoute,
17
- localizeInternalPath,
18
- i18nDiagnostics,
19
+ MDX_BODY_FEATURES,
20
+ normalizeRef,
21
+ isPageRef,
22
+ pagesByRef,
23
+ buildNavigation,
19
24
  PUBLIC_API_URL,
20
25
  pageMetaSchema,
21
26
  folderMetaSchema,
@@ -23,6 +28,7 @@ import {
23
28
  versionsDiagnostics,
24
29
  slugify,
25
30
  slugifyPath,
31
+ unloadablePathDiagnostic,
26
32
  extractHeadings,
27
33
  strippedLineOffset,
28
34
  isStringValue,
@@ -35,8 +41,10 @@ import {
35
41
  parseYouTubeId,
36
42
  nodeRequire,
37
43
  escapeMarkdownText,
44
+ guardBlockStart,
38
45
  renderInline,
39
- renderLink,
46
+ destination,
47
+ linkParts,
40
48
  headingPrefix,
41
49
  blockquote,
42
50
  listItem,
@@ -51,10 +59,15 @@ import {
51
59
  neutralizeUnsafeLinks,
52
60
  columnsPrefix,
53
61
  openApiSource,
62
+ BLUME_IGNORE_DIRS,
63
+ baselineScanIgnore,
64
+ BLUME_WATCH_IGNORE_DIRS,
65
+ excludeDirSegments,
66
+ ignoringWatchListener,
54
67
  createModuleLoader,
55
68
  resolveProjectContext,
56
69
  loadConfig
57
- } from "./chunk-qkqwkpte.js";
70
+ } from "./chunk-pv29h0wf.js";
58
71
  import {
59
72
  getBlumeVersion,
60
73
  BlumeError,
@@ -64,15 +77,8 @@ import {
64
77
  trimChar,
65
78
  isInternalPath,
66
79
  withBasePath,
67
- stripBasePath
68
- } from "./chunk-jts8mvcz.js";
69
- import {
70
- BLUME_IGNORE_DIRS,
71
- baselineScanIgnore,
72
- BLUME_WATCH_IGNORE_DIRS,
73
- excludeDirSegments,
74
- ignoringWatchListener
75
- } from "./chunk-abh8yjkn.js";
80
+ mountBasePath
81
+ } from "./chunk-3em5wd2y.js";
76
82
 
77
83
  // src/core/manifest.ts
78
84
  var versionAlternateKey = (versionKey, locale) => `${versionKey}\x00${locale}`;
@@ -105,7 +111,7 @@ var buildFallbackRoutes = (graph, i18n, alternatesByKey, basePath, versionAltern
105
111
  if (present.has(key)) {
106
112
  continue;
107
113
  }
108
- const path = withBasePath(basePath, localizeRoute(key, code, i18n));
114
+ const path = mountBasePath(basePath, localizeRoute(key, code, i18n));
109
115
  const versionAlternates = versionAlternatesByKey ? versionAlternatesFor(versionAlternatesByKey, source.versionKey, code) : [];
110
116
  if (versionAlternatesByKey) {
111
117
  versionAlternates.push({ path, version: source.version });
@@ -155,6 +161,7 @@ var buildManifest = (options) => {
155
161
  }
156
162
  }
157
163
  const routes = graph.pages.map((page) => ({
164
+ aiExclude: page.meta.ai.exclude,
158
165
  alternates: alternatesByKey.get(page.translationKey) ?? [],
159
166
  collection: page.collection ?? "docs",
160
167
  contentType: page.contentType,
@@ -198,6 +205,103 @@ var buildManifest = (options) => {
198
205
  // src/core/project-graph.ts
199
206
  import { isAbsolute as isAbsolute3, relative as relative5 } from "pathe";
200
207
 
208
+ // src/core/directive-diagnostics.ts
209
+ import { mdxToMdast } from "satteri";
210
+
211
+ // src/markdown/directives.ts
212
+ import { toString as mdastToString } from "mdast-util-to-string";
213
+ var CALLOUT_TYPES = new Set([
214
+ "danger",
215
+ "info",
216
+ "note",
217
+ "success",
218
+ "tip",
219
+ "warning"
220
+ ]);
221
+ var CALLOUT_ALIASES = {
222
+ caution: "warning",
223
+ error: "danger",
224
+ important: "note",
225
+ warn: "warning"
226
+ };
227
+ var calloutTypeFor = (name) => {
228
+ const lower = name.toLowerCase();
229
+ if (CALLOUT_TYPES.has(lower)) {
230
+ return lower;
231
+ }
232
+ return CALLOUT_ALIASES[lower] ?? null;
233
+ };
234
+ var LITERAL_MARKERS = new Map([
235
+ ["leafDirective", "::"],
236
+ ["textDirective", ":"]
237
+ ]);
238
+ var labelSource = (label) => label.length > 0 ? `[${mdastToString(label, { includeImageAlt: false })}]` : "";
239
+ var attributeSource = (node) => {
240
+ const attributes = Object.entries(node.attributes ?? {}).map(([key, value]) => value ? `${key}="${value}"` : key);
241
+ return attributes.length > 0 ? `{${attributes.join(" ")}}` : "";
242
+ };
243
+ var spannedSource = (node, source) => {
244
+ const start = node.position?.start?.offset;
245
+ const end = node.position?.end?.offset;
246
+ return start !== undefined && end !== undefined ? source.slice(start, end) : null;
247
+ };
248
+ var directiveSource = (node, marker, source) => {
249
+ const opening = `${marker}${node.name}`;
250
+ const slice = spannedSource(node, source);
251
+ if (slice?.startsWith(opening)) {
252
+ return slice;
253
+ }
254
+ return `${opening}${labelSource(node.children ?? [])}${attributeSource(node)}`;
255
+ };
256
+
257
+ // src/core/directive-diagnostics.ts
258
+ var CONTAINER_OPENING = /^[\t >]*:{3,}(?<name>[a-z][\w-]*)/gimu;
259
+ var codeList = (names) => [...names].map((name) => `\`${name}\``).join(", ");
260
+ var CALLOUT_NAMES = `${codeList(CALLOUT_TYPES)} (or the aliases ${codeList(Object.keys(CALLOUT_ALIASES))})`;
261
+ var unknownContainers = (text) => {
262
+ const named = [...text.matchAll(CONTAINER_OPENING)].some((match) => calloutTypeFor(match.groups?.name ?? "") === null);
263
+ if (!named) {
264
+ return [];
265
+ }
266
+ let tree;
267
+ try {
268
+ tree = mdxToMdast(text, { features: MDX_BODY_FEATURES });
269
+ } catch {
270
+ return [];
271
+ }
272
+ const found = [];
273
+ const walk = (node) => {
274
+ if (node.type === "containerDirective" && calloutTypeFor(node.name) === null) {
275
+ found.push({ line: node.position?.start.line ?? 1, name: node.name });
276
+ }
277
+ if ("children" in node) {
278
+ for (const child of node.children) {
279
+ walk(child);
280
+ }
281
+ }
282
+ };
283
+ walk(tree);
284
+ return found;
285
+ };
286
+ var unknownDirectiveDiagnostics = (entry, sourceName) => {
287
+ if (entry.body.format !== "mdx") {
288
+ return [];
289
+ }
290
+ const page = entry.sourcePath ?? `${sourceName}:${entry.ref}`;
291
+ const offset = entry.bodyLineOffset ?? strippedLineOffset(entry.raw, entry.body.text);
292
+ return unknownContainers(entry.expanded?.text ?? entry.body.text).map(({ line, name }) => {
293
+ const origin = entry.expanded?.origins[line - 1];
294
+ return {
295
+ code: "BLUME_UNKNOWN_DIRECTIVE",
296
+ file: origin?.file ?? page,
297
+ line: origin?.line ?? line + offset,
298
+ message: `\`:::${name}\` isn't a callout type, so the page shows its \`:::\` lines as written around its content.`,
299
+ severity: "warning",
300
+ suggestion: `Use a callout type — ${CALLOUT_NAMES} — or remove the \`:::\` lines to keep the content as plain prose.`
301
+ };
302
+ });
303
+ };
304
+
201
305
  // src/theme/icon-kind.ts
202
306
  var IMAGE_ICON = /^(?:https?:\/\/|data:image\/|\/|\.{1,2}\/)|\.(?:avif|gif|jpe?g|png|svg|webp)$/iu;
203
307
  var INLINE_SVG = /^\s*<svg[\s\S]*<\/svg>\s*$/u;
@@ -315,7 +419,7 @@ var validateNavTargets = (navigation, routes) => {
315
419
  if (!isInternalPath(path) || path.startsWith("/#") || seen.has(path)) {
316
420
  continue;
317
421
  }
318
- if (!resolvesToPages(routes, path.split("#")[0] ?? path)) {
422
+ if (!resolvesToPages(routes, path.split(/[?#]/u)[0] ?? path)) {
319
423
  seen.add(path);
320
424
  diagnostics.push({
321
425
  code: "BLUME_NAV_MISSING_PAGE",
@@ -327,6 +431,44 @@ var validateNavTargets = (navigation, routes) => {
327
431
  }
328
432
  return diagnostics;
329
433
  };
434
+ var validateSidebarItems = (items, resolves) => {
435
+ const diagnostics = [];
436
+ const walk = (list) => {
437
+ for (const item of list) {
438
+ if (isPageRef(item)) {
439
+ if (!resolves(normalizeRef(item))) {
440
+ diagnostics.push({
441
+ code: "BLUME_NAV_MISSING_PAGE",
442
+ message: `Sidebar entry "${item}" matches no page, so it's left out of the sidebar.`,
443
+ severity: "warning",
444
+ suggestion: "Fix the route, or add a page at it."
445
+ });
446
+ }
447
+ continue;
448
+ }
449
+ if (item.root && !resolves(normalizeRef(item.root))) {
450
+ diagnostics.push({
451
+ code: "BLUME_NAV_MISSING_PAGE",
452
+ message: `Sidebar entry "${item.label}" has root "${item.root}", but no page matches it, so its link leads nowhere.`,
453
+ severity: "warning",
454
+ suggestion: "Fix the root, or add a page at that route."
455
+ });
456
+ }
457
+ if (item.items) {
458
+ walk(item.items);
459
+ } else if (!(item.root || item.href)) {
460
+ diagnostics.push({
461
+ code: "BLUME_NAV_EMPTY_ITEM",
462
+ message: `Sidebar entry "${item.label}" has no page, href, root, or items, so it's left out of the sidebar.`,
463
+ severity: "warning",
464
+ suggestion: "Give it an href or a root to link somewhere, or items to make it a group."
465
+ });
466
+ }
467
+ }
468
+ };
469
+ walk(items);
470
+ return diagnostics;
471
+ };
330
472
  var duplicateLabelDiagnostics = (navigation) => {
331
473
  const diagnostics = [];
332
474
  const checkLevel = (nodes, where) => {
@@ -386,541 +528,6 @@ var validateNavStructure = (navigation, pages) => [
386
528
  ...hiddenInSidebarDiagnostics(navigation, pages)
387
529
  ];
388
530
 
389
- // src/core/navigation.ts
390
- import { extname } from "pathe";
391
- var NUMERIC_PREFIX = /^(?<order>\d+)[-_.]/u;
392
- var GROUP_FOLDER = /^\((?<label>.+)\)$/u;
393
- var WORD_SPLIT = /[-_]/u;
394
- var isUnderPath = (route, base) => base === "/" || route === base || route.startsWith(`${base}/`);
395
- var isRootTab = (tab, root) => isUnderPath(root, tab.path);
396
- var sidebarIcon = (page) => page.meta.sidebar.icon ?? page.meta.icon;
397
- var WORD_FORMS = new Map([
398
- ["ai", "AI"],
399
- ["api", "API"],
400
- ["apis", "APIs"],
401
- ["asyncapi", "AsyncAPI"],
402
- ["cli", "CLI"],
403
- ["css", "CSS"],
404
- ["faq", "FAQ"],
405
- ["faqs", "FAQs"],
406
- ["graphql", "GraphQL"],
407
- ["html", "HTML"],
408
- ["http", "HTTP"],
409
- ["https", "HTTPS"],
410
- ["id", "ID"],
411
- ["ids", "IDs"],
412
- ["ios", "iOS"],
413
- ["js", "JS"],
414
- ["json", "JSON"],
415
- ["jwt", "JWT"],
416
- ["llm", "LLM"],
417
- ["llms", "LLMs"],
418
- ["macos", "macOS"],
419
- ["mcp", "MCP"],
420
- ["oauth", "OAuth"],
421
- ["openapi", "OpenAPI"],
422
- ["rss", "RSS"],
423
- ["sdk", "SDK"],
424
- ["sdks", "SDKs"],
425
- ["seo", "SEO"],
426
- ["sql", "SQL"],
427
- ["sso", "SSO"],
428
- ["ts", "TS"],
429
- ["ui", "UI"],
430
- ["url", "URL"],
431
- ["urls", "URLs"],
432
- ["xml", "XML"],
433
- ["yaml", "YAML"]
434
- ]);
435
- var humanize = (segment) => segment.replace(NUMERIC_PREFIX, "").split(WORD_SPLIT).filter(Boolean).map((word) => WORD_FORMS.get(word) ?? word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
436
- var numericOrder = (segment) => {
437
- const value = segment.match(NUMERIC_PREFIX)?.groups?.order;
438
- return value ? Math.trunc(Number(value)) : Number.POSITIVE_INFINITY;
439
- };
440
- var segmentKey = (raw) => {
441
- const group = raw.match(GROUP_FOLDER)?.groups?.label;
442
- return (group ?? raw).replace(NUMERIC_PREFIX, "");
443
- };
444
- var isIndexStem = (stem) => stem.replace(NUMERIC_PREFIX, "") === "index";
445
- var stemOf = (filename) => filename.replace(extname(filename), "");
446
- var navStem = (navPath) => stemOf(navPath.split("/").at(-1) ?? navPath);
447
- var createGroup = (key, path, label, order) => ({
448
- children: [],
449
- index: new Map,
450
- key,
451
- kind: "group",
452
- label,
453
- order,
454
- path
455
- });
456
- var ensureGroup = (parent, rawSegment) => {
457
- const existing = parent.index.get(rawSegment);
458
- if (existing) {
459
- return existing;
460
- }
461
- const path = parent.path ? `${parent.path}/${rawSegment}` : rawSegment;
462
- const group = createGroup(segmentKey(rawSegment), path, humanize(rawSegment.match(GROUP_FOLDER)?.groups?.label ?? rawSegment), numericOrder(rawSegment));
463
- parent.index.set(rawSegment, group);
464
- parent.children.push(group);
465
- return group;
466
- };
467
- var pageOrder = (page, filename) => {
468
- if (page.meta.sidebar.order !== undefined) {
469
- return { order: page.meta.sidebar.order, orderIsAuthored: true };
470
- }
471
- if (isIndexStem(stemOf(filename))) {
472
- return { order: Number.NEGATIVE_INFINITY, orderIsAuthored: false };
473
- }
474
- if (page.contentType === "changelog") {
475
- const iso = page.meta.date ?? page.meta.changelog?.date;
476
- const time = iso ? Date.parse(iso) : Number.NaN;
477
- if (!Number.isNaN(time)) {
478
- return { order: -time, orderIsAuthored: false };
479
- }
480
- }
481
- const order = numericOrder(filename);
482
- return {
483
- order,
484
- orderIsAuthored: page.contentType !== "changelog" && Number.isFinite(order)
485
- };
486
- };
487
- var metaKey = (path, metaPrefix) => {
488
- if (!metaPrefix) {
489
- return path;
490
- }
491
- return path ? `${metaPrefix}/${path}` : metaPrefix;
492
- };
493
- var applyFolderMeta = (group, folderMeta, sharedMeta, metaPrefix, sharedMetaPrefix, indexDisplay, indexRoute) => {
494
- group.route = indexRoute.get(group.path);
495
- const meta = folderMeta.get(metaKey(group.path, metaPrefix)) ?? sharedMeta.get(metaKey(group.path, sharedMetaPrefix));
496
- group.display = indexDisplay.get(group.path) ?? meta?.display;
497
- if (meta) {
498
- group.label = meta.title ?? group.label;
499
- group.icon = meta.icon ?? group.icon;
500
- group.order = meta.order ?? group.order;
501
- group.collapsed = meta.collapsed ?? group.collapsed;
502
- if (meta.pages) {
503
- const rank = new Map(meta.pages.map((key, i) => [key, i]));
504
- for (const child of group.children) {
505
- const position = rank.get(child.key);
506
- if (position !== undefined) {
507
- child.order = position;
508
- if (child.kind === "page") {
509
- child.orderIsAuthored = true;
510
- }
511
- }
512
- }
513
- }
514
- }
515
- for (const child of group.children) {
516
- if (child.kind === "group") {
517
- applyFolderMeta(child, folderMeta, sharedMeta, metaPrefix, sharedMetaPrefix, indexDisplay, indexRoute);
518
- }
519
- }
520
- };
521
- var indexTitleMismatchDiagnostic = (page, folderPath, folderMeta, sharedMeta, metaPrefix, sharedMetaPrefix) => {
522
- if (!(page.meta.title && page.meta.sidebar.hidden) || page.fallback || folderPath === "") {
523
- return;
524
- }
525
- const meta = folderMeta.get(metaKey(folderPath, metaPrefix)) ?? sharedMeta.get(metaKey(folderPath, sharedMetaPrefix));
526
- if (!meta?.title || meta.title === page.title) {
527
- return;
528
- }
529
- return {
530
- code: "BLUME_NAV_INDEX_TITLE_MISMATCH",
531
- file: page.sourcePath ?? page.id,
532
- message: `Index page "${page.navPath}" has title "${page.title}", but its folder's meta.title is "${meta.title}" — the sidebar shows the folder title while the page's own <title>/heading still say "${page.title}".`,
533
- severity: "warning",
534
- suggestion: `Update the page's frontmatter title to match ("${meta.title}"), or leave it if the divergence is intentional.`
535
- };
536
- };
537
- var sidebarDisplayIgnoredDiagnostics = (pages, explicitSidebar) => {
538
- const diagnostics = [];
539
- for (const page of pages) {
540
- if (!page.meta.sidebar.display) {
541
- continue;
542
- }
543
- const isIndex = isIndexStem(navStem(page.navPath));
544
- if (explicitSidebar) {
545
- diagnostics.push({
546
- code: "BLUME_SIDEBAR_DISPLAY_IGNORED",
547
- file: page.sourcePath ?? page.id,
548
- message: `"${page.navPath}" sets sidebar.display, but the explicit navigation.sidebar config owns each group's display mode — the value is ignored.`,
549
- severity: "warning",
550
- suggestion: "Set display on the matching group in navigation.sidebar, or remove the frontmatter key."
551
- });
552
- continue;
553
- }
554
- if (isIndex && !page.navPath.includes("/")) {
555
- diagnostics.push({
556
- code: "BLUME_SIDEBAR_DISPLAY_IGNORED",
557
- file: page.sourcePath ?? page.id,
558
- message: `"${page.navPath}" sets sidebar.display, but the content root is not a sidebar group — the value is ignored.`,
559
- severity: "warning",
560
- suggestion: "Set navigation.sidebar.display in blume.config to change the sidebar-wide mode."
561
- });
562
- continue;
563
- }
564
- if (isIndex) {
565
- continue;
566
- }
567
- diagnostics.push({
568
- code: "BLUME_SIDEBAR_DISPLAY_IGNORED",
569
- file: page.sourcePath ?? page.id,
570
- message: `"${page.navPath}" sets sidebar.display, but only a folder's index page can set its group's display mode — the value is ignored.`,
571
- severity: "warning",
572
- suggestion: "Move display to the folder's index page frontmatter, or set it in the folder's meta.ts."
573
- });
574
- }
575
- return diagnostics;
576
- };
577
- var isAuthoredOrder = (node) => node.kind === "group" || node.orderIsAuthored;
578
- var duplicateOrderDiagnostics = (nodes) => {
579
- const byOrder = new Map;
580
- for (const node of nodes) {
581
- if (!Number.isFinite(node.order) || !isAuthoredOrder(node)) {
582
- continue;
583
- }
584
- const tied = byOrder.get(node.order);
585
- if (tied) {
586
- tied.push(node);
587
- } else {
588
- byOrder.set(node.order, [node]);
589
- }
590
- }
591
- const diagnostics = [];
592
- for (const [order, tied] of byOrder) {
593
- if (tied.length > 1) {
594
- const names = tied.map((node) => `"${node.label}"`);
595
- const list = names.length > 2 ? `${names.slice(0, -1).join(", ")}, and ${names.at(-1)}` : names.join(" and ");
596
- const verb = tied.length > 2 ? "all have" : "both have";
597
- const file = tied.find((node) => node.kind === "page")?.file;
598
- diagnostics.push({
599
- code: "BLUME_DUPLICATE_SIDEBAR_ORDER",
600
- file,
601
- message: `${list} ${verb} sidebar order ${order}; falling back to alphabetical order.`,
602
- severity: "warning",
603
- suggestion: "Give each item a distinct sidebar.order (or folder meta order)."
604
- });
605
- }
606
- }
607
- return diagnostics;
608
- };
609
- var sortNodes = (nodes, diagnostics) => {
610
- diagnostics.push(...duplicateOrderDiagnostics(nodes));
611
- nodes.sort((a, b) => {
612
- if (a.order !== b.order) {
613
- return a.order - b.order;
614
- }
615
- return a.label.localeCompare(b.label);
616
- });
617
- for (const node of nodes) {
618
- if (node.kind === "group") {
619
- sortNodes(node.children, diagnostics);
620
- }
621
- }
622
- };
623
- var hoistPages = (nodes, display, hoist) => {
624
- const groups = nodes.filter((node) => node.kind === "group");
625
- if (hoist || groups.some((group) => (group.display ?? display) === "flat")) {
626
- const pages = nodes.filter((node) => node.kind === "page");
627
- nodes.splice(0, nodes.length, ...pages, ...groups);
628
- }
629
- for (const group of groups) {
630
- hoistPages(group.children, display, false);
631
- }
632
- };
633
- var hoistTabSections = (nodes, tabPaths, display) => {
634
- for (const node of nodes) {
635
- if (node.kind !== "group") {
636
- continue;
637
- }
638
- if (node.routePath !== undefined && tabPaths.has(node.routePath)) {
639
- hoistPages(node.children, display, true);
640
- }
641
- hoistTabSections(node.children, tabPaths, display);
642
- }
643
- };
644
- var toNavNode = (node, display) => {
645
- if (node.kind === "page") {
646
- return {
647
- badge: node.badge,
648
- deprecated: node.deprecated || undefined,
649
- description: node.description,
650
- icon: node.icon,
651
- kind: "page",
652
- label: node.label,
653
- pageId: node.pageId,
654
- route: node.route
655
- };
656
- }
657
- return {
658
- children: node.children.map((child) => toNavNode(child, display)),
659
- collapsed: node.collapsed,
660
- display: node.display ?? display,
661
- icon: node.icon,
662
- kind: "group",
663
- label: node.label,
664
- path: node.routePath,
665
- route: node.route
666
- };
667
- };
668
- var buildFileSystemSidebar = (pages, folderMeta, sharedMeta, metaPrefix, sharedMetaPrefix, display, tabPaths, diagnostics = []) => {
669
- const root = createGroup("", "", "", 0);
670
- const indexDisplay = new Map;
671
- const indexRoute = new Map;
672
- for (const page of pages) {
673
- const parts = page.navPath.split("/");
674
- const filename = parts.at(-1) ?? page.navPath;
675
- const stem = stemOf(filename);
676
- const dirs = parts.slice(0, -1);
677
- if (isIndexStem(stem)) {
678
- const diagnostic = indexTitleMismatchDiagnostic(page, dirs.join("/"), folderMeta, sharedMeta, metaPrefix, sharedMetaPrefix);
679
- if (diagnostic) {
680
- diagnostics.push(diagnostic);
681
- }
682
- if (page.meta.sidebar.display && !page.fallback) {
683
- indexDisplay.set(dirs.join("/"), page.meta.sidebar.display);
684
- }
685
- if (dirs.length > 0) {
686
- indexRoute.set(dirs.join("/"), page.route);
687
- }
688
- }
689
- if (page.meta.sidebar.hidden) {
690
- continue;
691
- }
692
- const routeSegments = page.route.split("/").filter(Boolean);
693
- const folderParts = isIndexStem(stem) ? routeSegments : routeSegments.slice(0, -1);
694
- const routeDirCount = dirs.filter((dir) => !GROUP_FOLDER.test(dir)).length;
695
- const offset = Math.max(0, folderParts.length - routeDirCount);
696
- let parent = root;
697
- let consumed = offset;
698
- for (const dir of dirs) {
699
- parent = ensureGroup(parent, dir);
700
- if (!GROUP_FOLDER.test(dir)) {
701
- consumed += 1;
702
- }
703
- parent.routePath ??= `/${folderParts.slice(0, consumed).join("/")}`;
704
- }
705
- const { order, orderIsAuthored } = pageOrder(page, filename);
706
- parent.children.push({
707
- badge: page.meta.sidebar.badge,
708
- deprecated: page.meta.deprecated || undefined,
709
- description: page.description,
710
- file: page.sourcePath,
711
- icon: sidebarIcon(page),
712
- key: segmentKey(stem),
713
- kind: "page",
714
- label: page.meta.sidebar.label ?? page.title,
715
- order,
716
- orderIsAuthored,
717
- pageId: page.id,
718
- route: page.route
719
- });
720
- }
721
- applyFolderMeta(root, folderMeta, sharedMeta, metaPrefix, sharedMetaPrefix, indexDisplay, indexRoute);
722
- sortNodes(root.children, diagnostics);
723
- hoistPages(root.children, display, true);
724
- hoistTabSections(root.children, tabPaths, display);
725
- return root.children.map((child) => toNavNode(child, display));
726
- };
727
- var normalizeRef = (ref) => {
728
- if (ref === "index") {
729
- return "/";
730
- }
731
- const withSlash = ref.startsWith("/") ? ref : `/${ref}`;
732
- const noTrailing = withSlash.replace(/\/+$/u, "");
733
- const trimmed = noTrailing.endsWith("/index") ? noTrailing.slice(0, -"/index".length) : noTrailing;
734
- return trimmed === "" ? "/" : trimmed;
735
- };
736
- var routeForRef = (ref, byRoute, basePath) => {
737
- if (!ref) {
738
- return;
739
- }
740
- const normalized = normalizeRef(ref);
741
- return byRoute.get(normalized)?.route ?? withBasePath(basePath, normalized);
742
- };
743
- var isPageRef = (item) => typeof item === "string";
744
- var configItemToNode = (item, byRoute, basePath) => {
745
- if (isPageRef(item)) {
746
- const page = byRoute.get(normalizeRef(item));
747
- if (!page) {
748
- return null;
749
- }
750
- return {
751
- badge: page.meta.sidebar.badge,
752
- deprecated: page.meta.deprecated || undefined,
753
- description: page.description,
754
- icon: sidebarIcon(page),
755
- kind: "page",
756
- label: page.meta.sidebar.label ?? page.title,
757
- pageId: page.id,
758
- route: page.route
759
- };
760
- }
761
- if (item.root) {
762
- const page = byRoute.get(normalizeRef(item.root));
763
- return {
764
- badge: item.badge,
765
- deprecated: page?.meta.deprecated || undefined,
766
- icon: item.icon,
767
- kind: "page",
768
- label: item.label,
769
- pageId: page?.id ?? "",
770
- route: page?.route ?? withBasePath(basePath, normalizeRef(item.root))
771
- };
772
- }
773
- if (item.href) {
774
- return {
775
- badge: item.badge,
776
- icon: item.icon,
777
- kind: "page",
778
- label: item.label,
779
- pageId: "",
780
- route: withBasePath(basePath, item.href)
781
- };
782
- }
783
- return null;
784
- };
785
- var buildConfigSidebar = (items, byRoute, display, basePath) => {
786
- const nodes = [];
787
- for (const item of items) {
788
- if (!isPageRef(item) && item.items) {
789
- nodes.push({
790
- badge: item.badge,
791
- children: buildConfigSidebar(item.items, byRoute, display, basePath),
792
- collapsed: item.collapsed,
793
- directory: item.directory,
794
- display: item.display ?? display,
795
- icon: item.icon,
796
- kind: "group",
797
- label: item.label,
798
- route: routeForRef(item.root, byRoute, basePath)
799
- });
800
- continue;
801
- }
802
- const node = configItemToNode(item, byRoute, basePath);
803
- if (node) {
804
- nodes.push(node);
805
- }
806
- }
807
- return nodes;
808
- };
809
- var resolveTabHref = (sidebar, path, extraRoutes, basePath) => {
810
- if (extraRoutes.has(path)) {
811
- return path;
812
- }
813
- let first;
814
- const walk = (nodes) => {
815
- for (const node of nodes) {
816
- const { route } = node;
817
- if (route === path) {
818
- return true;
819
- }
820
- if (first === undefined && route !== undefined && route.startsWith(`${path}/`)) {
821
- first = route;
822
- }
823
- if (node.kind === "group" && walk(node.children)) {
824
- return true;
825
- }
826
- }
827
- return false;
828
- };
829
- if (walk(sidebar)) {
830
- return path;
831
- }
832
- const bare = stripBasePath(basePath, path);
833
- return extraRoutes.has(bare) ? bare : first ?? path;
834
- };
835
- var withTabHrefs = (tabs, sidebar, extraRoutes, basePath) => tabs.map((tab) => {
836
- const href = tab.href ?? resolveTabHref(sidebar, tab.path, extraRoutes, basePath);
837
- return href === tab.path ? tab : { ...tab, href };
838
- });
839
- var rebaseNavChrome = (basePath, options) => {
840
- const actions = options.actions ?? [];
841
- const cta = options.cta ?? null;
842
- const featured = options.featured ?? [];
843
- const selectors = options.selectors ?? [];
844
- const tabs = options.tabs ?? [];
845
- if (!basePath) {
846
- return { actions, cta, featured, selectors, tabs };
847
- }
848
- const rebaseHref = (item) => ({
849
- ...item,
850
- href: withBasePath(basePath, item.href)
851
- });
852
- const rebasePath = (item) => ({
853
- ...item,
854
- path: withBasePath(basePath, item.path)
855
- });
856
- return {
857
- actions: actions.map(rebaseHref),
858
- cta: cta ? rebaseHref(cta) : null,
859
- featured: featured.map(rebaseHref),
860
- selectors: selectors.map((selector) => ({
861
- ...selector,
862
- items: selector.items.map(rebasePath)
863
- })),
864
- tabs: tabs.map((tab) => {
865
- const rebased = {
866
- ...tab,
867
- items: tab.items?.map(rebasePath),
868
- path: withBasePath(basePath, tab.path)
869
- };
870
- if (tab.href) {
871
- rebased.href = withBasePath(basePath, tab.href);
872
- }
873
- return rebased;
874
- })
875
- };
876
- };
877
- var buildNavigation = (pages, options) => {
878
- const basePath = options.basePath ?? "";
879
- const display = options.display ?? "flat";
880
- const metaPrefix = options.metaPrefix ?? "";
881
- const sharedMetaPrefix = options.sharedMetaPrefix ?? "";
882
- const sharedFolderMeta = options.sharedFolderMeta ?? new Map;
883
- const extraRoutes = options.extraRoutes ?? new Set;
884
- const { actions, cta, featured, selectors, tabs } = rebaseNavChrome(basePath, options);
885
- const byRoute = new Map(pages.map((page) => [
886
- options.refByLogical ? page.translationKey : page.route,
887
- page
888
- ]));
889
- if (basePath && !options.refByLogical) {
890
- for (const page of pages) {
891
- const bare = stripBasePath(basePath, page.route);
892
- if (!byRoute.has(bare)) {
893
- byRoute.set(bare, page);
894
- }
895
- }
896
- }
897
- const rootTabPath = withBasePath(basePath, options.localizedRoot ?? "/");
898
- const diagnostics = options.diagnostics ?? [];
899
- diagnostics.push(...sidebarDisplayIgnoredDiagnostics(pages, Boolean(options.sidebar)));
900
- if (options.sidebar) {
901
- const sidebar = buildConfigSidebar(options.sidebar, byRoute, display, basePath);
902
- return {
903
- actions,
904
- cta,
905
- featured,
906
- root: rootTabPath,
907
- selectors,
908
- sidebar,
909
- tabs: withTabHrefs(tabs, sidebar, extraRoutes, basePath)
910
- };
911
- }
912
- const sidebar = buildFileSystemSidebar(pages, options.folderMeta, sharedFolderMeta, metaPrefix, sharedMetaPrefix, display, new Set(tabs.flatMap((tab) => isRootTab(tab, rootTabPath) ? [] : [tab.path])), diagnostics);
913
- return {
914
- actions,
915
- cta,
916
- featured,
917
- root: rootTabPath,
918
- selectors,
919
- sidebar,
920
- tabs: withTabHrefs(tabs, sidebar, extraRoutes, basePath)
921
- };
922
- };
923
-
924
531
  // src/core/graph.ts
925
532
  var collectRoutes = (pages) => {
926
533
  const routes = new Map;
@@ -953,7 +560,7 @@ var localePagesFor = (code, real, fallback, fallbackByKey, i18n, basePath) => {
953
560
  ...source,
954
561
  fallback: true,
955
562
  locale: code,
956
- route: withBasePath(basePath, localizeRoute(key, code, i18n))
563
+ route: mountBasePath(basePath, localizeRoute(key, code, i18n))
957
564
  });
958
565
  }
959
566
  }
@@ -966,6 +573,19 @@ var resolveLabel = (label, locale, defaultLocale) => {
966
573
  }
967
574
  return label[locale] ?? (defaultLocale === undefined ? undefined : label[defaultLocale]) ?? Object.values(label)[0] ?? "";
968
575
  };
576
+ var localizeSidebarHrefs = (items, localize) => items.map((item) => {
577
+ if (isPageRef(item)) {
578
+ return item;
579
+ }
580
+ const localized = { ...item };
581
+ if (item.href !== undefined) {
582
+ localized.href = localize(item.href);
583
+ }
584
+ if (item.items) {
585
+ localized.items = localizeSidebarHrefs(item.items, localize);
586
+ }
587
+ return localized;
588
+ });
969
589
  var resolveTabLabels = (tabs, locale, defaultLocale) => (tabs ?? []).map((tab) => ({
970
590
  ...tab,
971
591
  items: tab.items?.map((item) => ({
@@ -974,6 +594,7 @@ var resolveTabLabels = (tabs, locale, defaultLocale) => (tabs ?? []).map((tab) =
974
594
  })),
975
595
  label: resolveLabel(tab.label, locale, defaultLocale)
976
596
  }));
597
+ var mountConfiguredLink = (path, basePath, extraRoutes) => extraRoutes.has(path) ? path : withBasePath(basePath, path);
977
598
  var buildLocaleNavigation = (code, pages, fallback, fallbackByKey, options, i18n, diagnostics, version = "") => {
978
599
  const basePath = options.basePath ?? "";
979
600
  const real = pages.filter((page) => page.locale === code);
@@ -1009,12 +630,15 @@ var buildLocaleNavigation = (code, pages, fallback, fallbackByKey, options, i18n
1009
630
  }
1010
631
  return localized;
1011
632
  });
1012
- const localeDir = i18n.parser === "dir" && code !== i18n.defaultLocale ? code : "";
633
+ const localeDirOf = (locale) => i18n.parser === "dir" && locale !== i18n.defaultLocale ? locale : "";
634
+ const localeDir = localeDirOf(code);
635
+ const fallbackMetaPrefix = fallback && code !== fallback ? [version, localeDirOf(fallback)].filter(Boolean).join("/") : undefined;
1013
636
  const localizeHref = (item) => ({
1014
637
  ...item,
1015
638
  href: localizeServed(item.href, false)
1016
639
  });
1017
640
  const { actions, cta, featured } = options.navigation;
641
+ const sidebarItems = options.navigation.sidebar.items;
1018
642
  const navigation = buildNavigation(localePages, {
1019
643
  actions: actions?.map(localizeHref),
1020
644
  basePath,
@@ -1022,6 +646,7 @@ var buildLocaleNavigation = (code, pages, fallback, fallbackByKey, options, i18n
1022
646
  diagnostics,
1023
647
  display: options.navigation.sidebar.display,
1024
648
  extraRoutes: options.extraRoutes,
649
+ fallbackMetaPrefix,
1025
650
  featured: featured?.map(localizeHref),
1026
651
  folderMeta: options.folderMeta,
1027
652
  localizedRoot: localizeRoute(versionizeRoute("/", version), code, i18n),
@@ -1030,10 +655,19 @@ var buildLocaleNavigation = (code, pages, fallback, fallbackByKey, options, i18n
1030
655
  selectors: options.navigation.selectors,
1031
656
  sharedFolderMeta: options.sharedFolderMeta,
1032
657
  sharedMetaPrefix: version,
1033
- sidebar: version ? undefined : options.navigation.sidebar.items,
658
+ sidebar: version || !sidebarItems ? undefined : localizeSidebarHrefs(sidebarItems, (href) => localizeServed(href, false)),
659
+ tabRoot: localizeRoute("/", code, i18n),
1034
660
  tabs
1035
661
  });
1036
- return options.brandHref === undefined ? navigation : { ...navigation, brandHref: localizeServed(options.brandHref, false) };
662
+ const localizedBrand = (href) => {
663
+ const localized = localizeServed(href, false);
664
+ return localized === href ? localized : withBasePath(basePath, localized);
665
+ };
666
+ const branded = options.brandHref === undefined ? navigation : { ...navigation, brandHref: localizedBrand(options.brandHref) };
667
+ return options.bannerHref === undefined ? branded : {
668
+ ...branded,
669
+ bannerHref: mountConfiguredLink(localizeServed(options.bannerHref, false), basePath, extraRoutes)
670
+ };
1037
671
  };
1038
672
  var buildI18nNavigation = (pages, options, i18n, diagnostics, version = "") => {
1039
673
  const fallback = resolveFallbackLocale(i18n);
@@ -1088,6 +722,7 @@ var buildVersionNavigation = (id, versionPages, options, diagnostics) => {
1088
722
  selectors: options.navigation.selectors,
1089
723
  sharedFolderMeta: options.sharedFolderMeta,
1090
724
  sharedMetaPrefix: id,
725
+ tabRoot: "/",
1091
726
  tabs: resolveTabLabels(options.navigation.tabs, "")
1092
727
  })
1093
728
  };
@@ -1115,12 +750,24 @@ var buildContentGraph = (pages, options) => {
1115
750
  sidebar: options.navigation.sidebar.items,
1116
751
  tabs: resolveTabLabels(options.navigation.tabs, "")
1117
752
  });
753
+ if (options.bannerHref !== undefined) {
754
+ navigation = {
755
+ ...navigation,
756
+ bannerHref: mountConfiguredLink(options.bannerHref, options.basePath ?? "", options.extraRoutes ?? new Set)
757
+ };
758
+ }
1118
759
  }
1119
760
  const navigationByVersion = {};
1120
761
  for (const { id } of options.versions?.archived ?? []) {
1121
762
  navigationByVersion[id] = buildVersionNavigation(id, pages.filter((page) => page.version === id), options, diagnostics);
1122
763
  }
1123
764
  diagnostics.push(...validateNavIcons(navigation), ...validateNavStructure(navigation, currentPages));
765
+ const sidebarItems = options.navigation.sidebar.items;
766
+ if (sidebarItems) {
767
+ const byRef = pagesByRef(currentPages, options.basePath ?? "", Boolean(i18n));
768
+ const extraRoutes = options.extraRoutes ?? new Set;
769
+ diagnostics.push(...validateSidebarItems(sidebarItems, (route) => byRef.has(route) || extraRoutes.has(route)));
770
+ }
1124
771
  return {
1125
772
  diagnostics,
1126
773
  navigation,
@@ -1133,7 +780,8 @@ var buildContentGraph = (pages, options) => {
1133
780
 
1134
781
  // src/core/last-modified.ts
1135
782
  import { execFileSync } from "node:child_process";
1136
- import { relative } from "pathe";
783
+ import { realpathSync } from "node:fs";
784
+ import { normalize as normalize2, relative } from "pathe";
1137
785
  var GIT_LOCATION_VARS = new Set([
1138
786
  "GIT_COMMON_DIR",
1139
787
  "GIT_DIR",
@@ -1157,6 +805,13 @@ var parseGitLog = (output) => {
1157
805
  }
1158
806
  return times;
1159
807
  };
808
+ var realPath = (path) => {
809
+ try {
810
+ return normalize2(realpathSync.native(path));
811
+ } catch {
812
+ return path;
813
+ }
814
+ };
1160
815
  var gitRepositoryRoot = (root) => {
1161
816
  try {
1162
817
  return execFileSync("git", ["-C", root, "rev-parse", "--show-toplevel"], { encoding: "utf-8", env: gitEnv(), stdio: ["ignore", "pipe", "ignore"] }).trim();
@@ -1172,6 +827,7 @@ var gitLastModifiedTimes = (root, contentRoots, sourcePaths, repositoryRoot) =>
1172
827
  if (gitRoot === null) {
1173
828
  return new Map;
1174
829
  }
830
+ const top = realPath(gitRoot);
1175
831
  try {
1176
832
  const output = execFileSync("git", [
1177
833
  "-C",
@@ -1182,12 +838,12 @@ var gitLastModifiedTimes = (root, contentRoots, sourcePaths, repositoryRoot) =>
1182
838
  "--format=%x00%cI",
1183
839
  "--name-only",
1184
840
  "--",
1185
- ...contentRoots
841
+ ...contentRoots.map(realPath)
1186
842
  ], { encoding: "utf-8", env: gitEnv(), maxBuffer: 256 * 1024 * 1024 });
1187
843
  const byRepoPath = parseGitLog(output);
1188
844
  const result = new Map;
1189
845
  for (const sourcePath of sourcePaths) {
1190
- const iso = byRepoPath.get(relative(gitRoot, sourcePath));
846
+ const iso = byRepoPath.get(relative(top, realPath(sourcePath)));
1191
847
  if (iso) {
1192
848
  result.set(sourcePath, iso);
1193
849
  }
@@ -1224,7 +880,7 @@ var lastModifiedShallowWarning = (root, undatedCount) => {
1224
880
 
1225
881
  // src/core/meta.ts
1226
882
  import { basename, dirname, relative as relative2 } from "pathe";
1227
- import { glob } from "tinyglobby";
883
+ import { glob, isDynamicPattern } from "tinyglobby";
1228
884
  var META_FILES = [
1229
885
  "**/meta.ts",
1230
886
  "**/meta.js",
@@ -1235,6 +891,34 @@ var META_FILES = [
1235
891
  ];
1236
892
  var isMetaFactory = (mod) => typeof mod === "function";
1237
893
  var resolveMeta = async (mod) => isMetaFactory(mod) ? await mod() : mod;
894
+ var globBase = (pattern) => {
895
+ const literal = [];
896
+ for (const segment of pattern.split("/").slice(0, -1)) {
897
+ if (isDynamicPattern(segment)) {
898
+ break;
899
+ }
900
+ if (segment !== "" && segment !== ".") {
901
+ literal.push(segment);
902
+ }
903
+ }
904
+ return literal.join("/");
905
+ };
906
+ var onPathOf = (dir, base) => dir === base || base === "" || dir === "" || dir.startsWith(`${base}/`) || base.startsWith(`${dir}/`);
907
+ var withinInclude = (dir, include) => include === undefined || include.some((pattern) => !pattern.startsWith("!") && onPathOf(dir, globBase(pattern)));
908
+ var findFolderMetaFiles = async (source, patterns = META_FILES) => {
909
+ const found = await glob([...patterns], {
910
+ absolute: true,
911
+ cwd: source.root,
912
+ ignore: [
913
+ ...source.exclude ?? [],
914
+ "**/node_modules/**",
915
+ "**/.blume/**",
916
+ "**/dist/**"
917
+ ],
918
+ onlyFiles: true
919
+ });
920
+ return found.filter((file) => withinInclude(relative2(source.root, dirname(file)), source.include));
921
+ };
1238
922
  var metaKeyFor = (prefix, dir) => {
1239
923
  const clean = prefix ? trimChar(prefix, "/") : "";
1240
924
  if (!clean) {
@@ -1244,19 +928,14 @@ var metaKeyFor = (prefix, dir) => {
1244
928
  };
1245
929
  var discoverFolderMeta = async (sources, options = {}) => {
1246
930
  const list = Array.isArray(sources) ? sources : [{ root: sources }];
1247
- const localeDirs = new Set(options.localeDirs);
931
+ const localeDirs = new Map((options.localeDirs ?? []).map((code) => [code.toLowerCase(), code]));
1248
932
  const versionDirs = new Set(options.versionDirs);
1249
933
  const load = createModuleLoader();
1250
934
  const meta = new Map;
1251
935
  const shared = new Map;
1252
936
  const diagnostics = [];
1253
937
  const perSource = await Promise.all(list.map(async (source) => {
1254
- const files = await glob(META_FILES, {
1255
- absolute: true,
1256
- cwd: source.root,
1257
- ignore: ["**/node_modules/**", "**/.blume/**", "**/dist/**"],
1258
- onlyFiles: true
1259
- });
938
+ const files = await findFolderMetaFiles(source);
1260
939
  const loaded = await Promise.all(files.map(async (file) => {
1261
940
  try {
1262
941
  return {
@@ -1277,7 +956,7 @@ var discoverFolderMeta = async (sources, options = {}) => {
1277
956
  const version = head && versionDirs.has(head) ? head : "";
1278
957
  const afterVersion = version ? tail : [head ?? "", ...tail];
1279
958
  const [localeHead, ...localeTail] = afterVersion;
1280
- const locale = localeHead && localeDirs.has(localeHead) ? localeHead : "";
959
+ const locale = localeHead ? localeDirs.get(localeHead.toLowerCase()) ?? "" : "";
1281
960
  const rest = (locale ? localeTail : afterVersion).filter(Boolean).join("/");
1282
961
  const key = [version, locale, metaKeyFor(source.prefix, rest)].filter(Boolean).join("/");
1283
962
  if (!entry.ok) {
@@ -1397,30 +1076,31 @@ var embeddedEntry = (node, options) => {
1397
1076
  }
1398
1077
  return unsupported(`Contentful embedded entry${contentType ? `: ${contentType}` : ""}`, writesMdx(options.serializers));
1399
1078
  };
1400
- var renderInlines = (nodes, options) => nodes.map((node) => renderInlineNode(node, options)).join("");
1401
- var renderInlineNode = (node, options) => {
1079
+ var inlineParts = (nodes, options) => nodes.flatMap((node) => inlineNodeParts(node, options));
1080
+ var inlineNodeParts = (node, options) => {
1402
1081
  switch (asString(node.nodeType)) {
1403
1082
  case "text": {
1404
- return renderInline(asString(node.value) ?? "", marksOf(node));
1083
+ return [{ marks: marksOf(node), text: asString(node.value) ?? "" }];
1405
1084
  }
1406
1085
  case "hyperlink": {
1407
- return renderLink(renderInlines(children(node), options), asString(getPath(node, "data.uri")));
1086
+ return linkParts(inlineParts(children(node), options), asString(getPath(node, "data.uri")));
1408
1087
  }
1409
1088
  case "asset-hyperlink": {
1410
- return renderLink(renderInlines(children(node), options), linkedAsset(node, options)?.url);
1089
+ return linkParts(inlineParts(children(node), options), linkedAsset(node, options)?.url);
1411
1090
  }
1412
1091
  case "embedded-entry-inline": {
1413
- return embeddedEntry(node, options);
1092
+ return [{ markdown: embeddedEntry(node, options) }];
1414
1093
  }
1415
1094
  default: {
1416
- return renderInlines(children(node), options);
1095
+ return inlineParts(children(node), options);
1417
1096
  }
1418
1097
  }
1419
1098
  };
1099
+ var blockText = (node, options) => guardBlockStart(renderInline(inlineParts(children(node), options)));
1420
1100
  var renderList = (items, marker, options) => items.map((item, index) => listItem(marker(index), children(item).map((child) => renderBlock(child, options)).filter((block) => block !== "").join(`
1421
1101
  `))).join(`
1422
1102
  `);
1423
- var cellText = (cell, options) => children(cell).map((paragraph) => renderInlines(children(paragraph), options)).join(" ").replaceAll(CELL_BREAK, " ").replaceAll(CELL_PIPE, String.raw`\|`);
1103
+ var cellText = (cell, options) => children(cell).map((paragraph) => blockText(paragraph, options)).join(" ").replaceAll(CELL_BREAK, " ").replaceAll(CELL_PIPE, String.raw`\|`);
1424
1104
  var tableRow = (cells) => `| ${cells.join(" | ")} |`;
1425
1105
  var renderTable = (node, options) => {
1426
1106
  const rows = children(node).map((row) => children(row).map((cell) => cellText(cell, options)));
@@ -1439,11 +1119,11 @@ var renderBlock = (node, options) => {
1439
1119
  const type = asString(node.nodeType) ?? "";
1440
1120
  const heading = HEADING.exec(type)?.groups?.level;
1441
1121
  if (heading) {
1442
- return `${headingPrefix(Number(heading))}${renderInlines(children(node), options)}`;
1122
+ return `${headingPrefix(Number(heading))}${blockText(node, options)}`;
1443
1123
  }
1444
1124
  switch (type) {
1445
1125
  case "paragraph": {
1446
- return renderInlines(children(node), options);
1126
+ return blockText(node, options);
1447
1127
  }
1448
1128
  case "blockquote": {
1449
1129
  return blockquote(joinBlocks(children(node).map((child) => renderBlock(child, options))));
@@ -1480,6 +1160,7 @@ var remoteSource = (options, ctx) => {
1480
1160
  const cache = snapshotCache(ctx?.cacheDir ?? join(".blume", "cache", options.name));
1481
1161
  let snapshot = new Map;
1482
1162
  const load = async (refresh = ctx?.refresh ?? true) => {
1163
+ options.assertConfigured?.();
1483
1164
  const result = await loadWithCache(options.name, cache, options.fetchEntries, refresh);
1484
1165
  snapshot = new Map(result.entries.map((entry) => [entry.ref, entry]));
1485
1166
  return result;
@@ -1498,7 +1179,8 @@ var remoteSource = (options, ctx) => {
1498
1179
  prefix: options.prefix,
1499
1180
  read,
1500
1181
  staged: true,
1501
- watch: options.pollInterval ? pollingWatch(() => load(true), options.pollInterval, () => load()) : undefined
1182
+ watch: options.pollInterval ? pollingWatch(() => load(true), options.pollInterval, () => load()) : undefined,
1183
+ withContext: options.withContext
1502
1184
  };
1503
1185
  };
1504
1186
  var stagedEntry = (slug, data, markdown, lastModified, format = "md") => {
@@ -1552,8 +1234,8 @@ var documentEntry = (doc, fields, id, lower, draft = false, lowersToMdx = false)
1552
1234
  let markdown = "";
1553
1235
  let format = "md";
1554
1236
  if (isStringValue2(body)) {
1555
- markdown = `${body.trimEnd()}
1556
- `;
1237
+ markdown = neutralizeUnsafeLinks(`${body.trimEnd()}
1238
+ `);
1557
1239
  } else if (body !== undefined) {
1558
1240
  markdown = lower(body);
1559
1241
  format = lowersToMdx ? "mdx" : "md";
@@ -1588,14 +1270,20 @@ var contentfulSource = (options, ctx) => {
1588
1270
  serializers: options.serializers
1589
1271
  }) : "", false, writesMdx(options.serializers));
1590
1272
  };
1591
- const fetchEntries = async () => {
1592
- const preview = ctx?.preview ?? false;
1593
- const deliveryToken = options.token ?? process.env.CONTENTFUL_ACCESS_TOKEN;
1594
- const previewToken = options.previewToken ?? process.env.CONTENTFUL_PREVIEW_TOKEN;
1595
- if (preview && !previewToken) {
1596
- throw new Error("Contentful --preview needs a Preview API token: set previewToken or CONTENTFUL_PREVIEW_TOKEN.");
1273
+ const preview = ctx?.preview ?? false;
1274
+ const previewToken = () => options.previewToken ?? process.env.CONTENTFUL_PREVIEW_TOKEN;
1275
+ const assertConfigured = () => {
1276
+ if (preview && !previewToken()) {
1277
+ throw new BlumeError({
1278
+ code: "BLUME_SOURCE_MISCONFIGURED",
1279
+ message: `Source "${options.name}": Contentful --preview needs a Preview API token.`,
1280
+ severity: "error",
1281
+ suggestion: "Set previewToken on contentfulSource, or the CONTENTFUL_PREVIEW_TOKEN environment variable."
1282
+ });
1597
1283
  }
1598
- const token = preview ? previewToken : deliveryToken;
1284
+ };
1285
+ const fetchEntries = async () => {
1286
+ const token = preview ? previewToken() : options.token ?? process.env.CONTENTFUL_ACCESS_TOKEN;
1599
1287
  const client = {
1600
1288
  fetchImpl: options.fetchImpl,
1601
1289
  headers: token ? { authorization: `Bearer ${token}` } : {}
@@ -1630,17 +1318,19 @@ var contentfulSource = (options, ctx) => {
1630
1318
  return entries;
1631
1319
  };
1632
1320
  return remoteSource({
1321
+ assertConfigured,
1633
1322
  fetchEntries,
1634
1323
  name: options.name,
1635
1324
  pollInterval: options.pollInterval,
1636
- prefix: options.prefix
1325
+ prefix: options.prefix,
1326
+ withContext: (next) => contentfulSource(options, next)
1637
1327
  }, ctx);
1638
1328
  };
1639
1329
 
1640
1330
  // src/core/sources/filesystem.ts
1641
1331
  import { existsSync, watch as fsWatch } from "node:fs";
1642
1332
  import { readFile } from "node:fs/promises";
1643
- import { extname as extname2, isAbsolute, join as join2, relative as relative3, resolve } from "pathe";
1333
+ import { extname, isAbsolute, join as join2, relative as relative3, resolve } from "pathe";
1644
1334
  import { glob as glob2 } from "tinyglobby";
1645
1335
  var filesystemSource = (options) => {
1646
1336
  const contentRoot = isAbsolute(options.root) ? options.root : join2(resolve(options.projectRoot), options.root);
@@ -1652,9 +1342,19 @@ var filesystemSource = (options) => {
1652
1342
  onlyFiles: true
1653
1343
  });
1654
1344
  files.sort();
1655
- const entries = await Promise.all(files.map(async (file) => {
1345
+ const diagnostics = [];
1346
+ const loadable = [];
1347
+ for (const file of files) {
1348
+ const unloadable = unloadablePathDiagnostic(relative3(contentRoot, file), file);
1349
+ if (unloadable) {
1350
+ diagnostics.push(unloadable);
1351
+ } else {
1352
+ loadable.push(file);
1353
+ }
1354
+ }
1355
+ const entries = await Promise.all(loadable.map(async (file) => {
1656
1356
  const source = await readFile(file, "utf-8");
1657
- const ext = extname2(file).toLowerCase();
1357
+ const ext = extname(file).toLowerCase();
1658
1358
  const format = ext === ".mdx" ? "mdx" : "md";
1659
1359
  const parsed = frontmatter_default(source);
1660
1360
  return {
@@ -1665,7 +1365,7 @@ var filesystemSource = (options) => {
1665
1365
  sourcePath: file
1666
1366
  };
1667
1367
  }));
1668
- return { diagnostics: [], entries };
1368
+ return { diagnostics, entries };
1669
1369
  };
1670
1370
  const validate = () => {
1671
1371
  if (!existsSync(contentRoot)) {
@@ -1674,7 +1374,7 @@ var filesystemSource = (options) => {
1674
1374
  file: contentRoot,
1675
1375
  message: `Content root not found: ${options.root}`,
1676
1376
  severity: "error",
1677
- suggestion: `Create a "${options.root}" folder with at least one .md or .mdx file, or set content.root in blume.config.ts.`
1377
+ 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.`
1678
1378
  });
1679
1379
  }
1680
1380
  };
@@ -1691,6 +1391,8 @@ var filesystemSource = (options) => {
1691
1391
  };
1692
1392
  return {
1693
1393
  contentRoot,
1394
+ exclude: options.exclude,
1395
+ include: options.include,
1694
1396
  load,
1695
1397
  name: options.name,
1696
1398
  prefix: options.prefix,
@@ -1704,7 +1406,7 @@ var filesystemSource = (options) => {
1704
1406
  // src/core/sources/github-releases.ts
1705
1407
  import { fromMarkdown } from "mdast-util-from-markdown";
1706
1408
  import { gfmFromMarkdown } from "mdast-util-gfm";
1707
- import { toString as mdastToString } from "mdast-util-to-string";
1409
+ import { toString as mdastToString2 } from "mdast-util-to-string";
1708
1410
  import { gfm } from "micromark-extension-gfm";
1709
1411
  import stringWidth from "string-width";
1710
1412
  var DEFAULT_LIMIT = 100;
@@ -1723,7 +1425,7 @@ var releaseDescription = (body) => {
1723
1425
  extensions: [gfm()],
1724
1426
  mdastExtensions: [gfmFromMarkdown()]
1725
1427
  });
1726
- const text = tree.children.filter((node) => !NON_PROSE.has(node.type)).map((node) => mdastToString(node, { includeHtml: false, includeImageAlt: false })).join(" ").replaceAll(WHITESPACE, " ").trim();
1428
+ const text = tree.children.filter((node) => !NON_PROSE.has(node.type)).map((node) => mdastToString2(node, { includeHtml: false, includeImageAlt: false })).join(" ").replaceAll(WHITESPACE, " ").trim();
1727
1429
  if (!text) {
1728
1430
  return;
1729
1431
  }
@@ -1763,6 +1465,62 @@ var liftHeadings = (body) => {
1763
1465
  }
1764
1466
  return lifted;
1765
1467
  };
1468
+ var WEB_PROTOCOL = /^https?:$/u;
1469
+ var TITLE_SPECIALS = /["\\]/gu;
1470
+ var webOrigin = (site) => {
1471
+ const parsed = site ? URL.parse(site) : null;
1472
+ return parsed && WEB_PROTOCOL.test(parsed.protocol) ? parsed.origin : null;
1473
+ };
1474
+ var titleSuffix = (title) => title ? ` "${title.replaceAll(TITLE_SPECIALS, String.raw`\$&`)}"` : "";
1475
+ var rewrittenLink = (markdown, node, path) => {
1476
+ const target = `${destination(path)}${titleSuffix(node.title)}`;
1477
+ if (node.type === "definition") {
1478
+ return `[${node.label ?? node.identifier}]: ${target}`;
1479
+ }
1480
+ const start = node.position?.start.offset ?? 0;
1481
+ if (markdown[start] !== "[") {
1482
+ return `[${escapeMarkdownText(mdastToString2(node))}](${target})`;
1483
+ }
1484
+ const first = node.children.at(0)?.position?.start.offset ?? start + 1;
1485
+ const last = node.children.at(-1)?.position?.end.offset ?? first;
1486
+ return `[${markdown.slice(first, last)}](${target})`;
1487
+ };
1488
+ var relativizeOwnLinks = (markdown, site) => {
1489
+ const origin = webOrigin(site);
1490
+ if (!origin) {
1491
+ return markdown;
1492
+ }
1493
+ const spans = [];
1494
+ const visit = (node) => {
1495
+ if (node.type === "link" || node.type === "definition") {
1496
+ const parsed = URL.parse(node.url);
1497
+ if (parsed?.origin === origin) {
1498
+ spans.push({
1499
+ end: node.position?.end.offset ?? 0,
1500
+ start: node.position?.start.offset ?? 0,
1501
+ text: rewrittenLink(markdown, node, `${parsed.pathname}${parsed.search}${parsed.hash}`)
1502
+ });
1503
+ return;
1504
+ }
1505
+ }
1506
+ if ("children" in node) {
1507
+ for (const child of node.children) {
1508
+ visit(child);
1509
+ }
1510
+ }
1511
+ };
1512
+ visit(fromMarkdown(markdown, {
1513
+ extensions: [gfm()],
1514
+ mdastExtensions: [gfmFromMarkdown()]
1515
+ }));
1516
+ let out = "";
1517
+ let cursor = 0;
1518
+ for (const span of spans) {
1519
+ out += markdown.slice(cursor, span.start) + span.text;
1520
+ cursor = span.end;
1521
+ }
1522
+ return out + markdown.slice(cursor);
1523
+ };
1766
1524
  var slugifyTag = (tag) => tag.toLowerCase().replaceAll(NON_SLUG, "-").replaceAll(EDGE_DASHES, "");
1767
1525
  var githubHeaders = () => {
1768
1526
  const headers = new Headers({ Accept: "application/vnd.github+json" });
@@ -1772,14 +1530,14 @@ var githubHeaders = () => {
1772
1530
  }
1773
1531
  return headers;
1774
1532
  };
1775
- var releaseToEntry = (release) => {
1533
+ var releaseToEntry = (release, site) => {
1776
1534
  const version = release.tag_name.replace(LEADING_V, "");
1777
1535
  const title = release.name?.trim() || release.tag_name;
1778
1536
  const date = release.published_at ?? release.created_at;
1779
1537
  const category = release.prerelease ? "Prerelease" : "Release";
1780
- const body = liftHeadings(neutralizeUnsafeLinks((release.body ?? "").replaceAll(`\r
1538
+ const body = liftHeadings(relativizeOwnLinks(neutralizeUnsafeLinks((release.body ?? "").replaceAll(`\r
1781
1539
  `, `
1782
- `)).trim());
1540
+ `)).trim(), site));
1783
1541
  const description = releaseDescription(body);
1784
1542
  const data = {
1785
1543
  changelog: { category, version },
@@ -1813,11 +1571,21 @@ var githubReleasesSource = (options, ctx) => {
1813
1571
  const include = (release) => (options.drafts || !release.draft) && (options.prereleases || !release.prerelease);
1814
1572
  const fetchPage = async (page) => {
1815
1573
  const url = `${base}/repos/${options.owner}/${options.repo}/releases?per_page=${PER_PAGE}&page=${page}`;
1816
- const res = await doFetch(url, { headers: githubHeaders() });
1817
- if (!res.ok) {
1818
- throw new Error(`${url} -> ${res.status}`);
1574
+ try {
1575
+ const res = await doFetch(url, {
1576
+ headers: githubHeaders(),
1577
+ signal: AbortSignal.timeout(REMOTE_TIMEOUT_MS)
1578
+ });
1579
+ if (!res.ok) {
1580
+ throw new Error(`${url} -> ${res.status}`);
1581
+ }
1582
+ return await res.json();
1583
+ } catch (error) {
1584
+ if (error instanceof DOMException && error.name === "TimeoutError") {
1585
+ throw new Error(`${url} did not respond within ${REMOTE_TIMEOUT_MS / 1000}s`, { cause: error });
1586
+ }
1587
+ throw error;
1819
1588
  }
1820
- return await res.json();
1821
1589
  };
1822
1590
  const fetchReleases = async () => {
1823
1591
  const collected = [];
@@ -1836,7 +1604,7 @@ var githubReleasesSource = (options, ctx) => {
1836
1604
  try {
1837
1605
  const result = await loadWithCache(options.name, cache, async () => {
1838
1606
  const releases = await fetchReleases();
1839
- return releases.map(releaseToEntry);
1607
+ return releases.map((release) => releaseToEntry(release, options.site));
1840
1608
  }, refresh);
1841
1609
  snapshot = new Map(result.entries.map((entry) => [entry.ref, entry]));
1842
1610
  return result;
@@ -1870,7 +1638,8 @@ var githubReleasesSource = (options, ctx) => {
1870
1638
  prefix: options.prefix,
1871
1639
  read,
1872
1640
  staged: true,
1873
- watch: options.pollInterval ? pollingWatch(() => load(true), options.pollInterval, () => load()) : undefined
1641
+ watch: options.pollInterval ? pollingWatch(() => load(true), options.pollInterval, () => load()) : undefined,
1642
+ withContext: (next) => githubReleasesSource(options, next)
1874
1643
  };
1875
1644
  };
1876
1645
 
@@ -1890,15 +1659,29 @@ var githubHeaders2 = (url) => {
1890
1659
  }
1891
1660
  return GITHUB_HOSTS.has(host) ? { authorization: `Bearer ${token}` } : {};
1892
1661
  };
1662
+ var fetchText = async (url, doFetch) => {
1663
+ try {
1664
+ const res = await doFetch(url, {
1665
+ headers: githubHeaders2(url),
1666
+ signal: AbortSignal.timeout(REMOTE_TIMEOUT_MS)
1667
+ });
1668
+ if (!res.ok) {
1669
+ throw new Error(`${url} -> ${res.status}`);
1670
+ }
1671
+ return await res.text();
1672
+ } catch (error) {
1673
+ if (error instanceof DOMException && error.name === "TimeoutError") {
1674
+ throw new Error(`${url} did not respond within ${REMOTE_TIMEOUT_MS / 1000}s`, { cause: error });
1675
+ }
1676
+ throw error;
1677
+ }
1678
+ };
1893
1679
  var enumerateGithub = async (github, include, doFetch) => {
1894
1680
  const { owner, repo, ref } = github;
1895
1681
  const base = github.path.replaceAll(/^\/|\/$/gu, "");
1896
1682
  const treeUrl = `https://api.github.com/repos/${owner}/${repo}/git/trees/${ref}?recursive=1`;
1897
- const res = await doFetch(treeUrl, { headers: githubHeaders2(treeUrl) });
1898
- if (!res.ok) {
1899
- throw new Error(`${treeUrl} -> ${res.status}`);
1900
- }
1901
- const body = await res.json();
1683
+ const text = await fetchText(treeUrl, doFetch);
1684
+ const body = JSON.parse(text);
1902
1685
  const prefix = base ? `${base}/` : "";
1903
1686
  const included = picomatch(include);
1904
1687
  const refs = (body.tree ?? []).flatMap((node) => {
@@ -1943,13 +1726,7 @@ var mdxRemoteSource = (options, ctx) => {
1943
1726
  return { refs, truncated: false };
1944
1727
  };
1945
1728
  const fetchEntry = async (item) => {
1946
- const res = await doFetch(item.fetchUrl, {
1947
- headers: githubHeaders2(item.fetchUrl)
1948
- });
1949
- if (!res.ok) {
1950
- throw new Error(`${item.fetchUrl} -> ${res.status}`);
1951
- }
1952
- const text = await res.text();
1729
+ const text = await fetchText(item.fetchUrl, doFetch);
1953
1730
  const parsed = frontmatter_default(text);
1954
1731
  const format = item.ref.toLowerCase().endsWith(".mdx") ? "mdx" : "md";
1955
1732
  return {
@@ -1973,13 +1750,16 @@ var mdxRemoteSource = (options, ctx) => {
1973
1750
  severity: "warning"
1974
1751
  });
1975
1752
  }
1753
+ const reasons = [];
1976
1754
  const settled = await Promise.all(refs.map(async (ref) => {
1977
1755
  try {
1978
1756
  return await fetchEntry(ref);
1979
1757
  } catch (error) {
1758
+ const reason = error.message;
1759
+ reasons.push(reason);
1980
1760
  skipped.push({
1981
1761
  code: "BLUME_SOURCE_FETCH_FAILED",
1982
- message: `Source "${options.name}" skipped "${ref.ref}" (${error.message}); the rest were imported.`,
1762
+ message: `Source "${options.name}" skipped "${ref.ref}" (${reason}); the rest were imported.`,
1983
1763
  severity: "warning"
1984
1764
  });
1985
1765
  return null;
@@ -1988,7 +1768,7 @@ var mdxRemoteSource = (options, ctx) => {
1988
1768
  const entries = settled.filter((entry) => entry !== null);
1989
1769
  if (refs.length > 0 && entries.length === 0) {
1990
1770
  skipped.length = 0;
1991
- throw new Error(`all ${refs.length} remote file(s) failed to fetch`);
1771
+ throw new Error(`all ${refs.length} remote file(s) failed to fetch (${reasons[0]})`);
1992
1772
  }
1993
1773
  return entries;
1994
1774
  }, refresh);
@@ -2010,10 +1790,12 @@ var mdxRemoteSource = (options, ctx) => {
2010
1790
  return {
2011
1791
  load,
2012
1792
  name: options.name,
1793
+ orderedNames: true,
2013
1794
  prefix: options.prefix,
2014
1795
  read,
2015
1796
  staged: true,
2016
- watch: options.pollInterval ? pollingWatch(() => load(true), options.pollInterval, () => load()) : undefined
1797
+ watch: options.pollInterval ? pollingWatch(() => load(true), options.pollInterval, () => load()) : undefined,
1798
+ withContext: (next) => mdxRemoteSource(options, next)
2017
1799
  };
2018
1800
  };
2019
1801
 
@@ -2032,34 +1814,45 @@ import {
2032
1814
  rm,
2033
1815
  writeFile
2034
1816
  } from "node:fs/promises";
2035
- import { extname as extname3, join as join3 } from "pathe";
2036
- var MD_IMAGE = /!\[(?<alt>[^\]]*)\]\((?<url>[^)\s]+)\)/gu;
1817
+ import { extname as extname2, join as join3 } from "pathe";
1818
+ var MD_IMAGE = /!\[(?<alt>(?:\\.|[^\\\]])*)\]\((?:<(?<pointy>(?:\\.|[^\\<>\n])*)>|(?<bare>(?:\\.|[^\\\s)])+))\)/gu;
1819
+ var DESTINATION_ESCAPE = /\\(?<char>[!-/:-@[-`{-~])/gu;
2037
1820
  var HTML_VIDEO_SRC = /(?<open><video\b[^>]*?\ssrc=")(?<url>[^"]+)(?<close>")/gu;
2038
1821
  var REMOTE = /^https?:\/\//u;
2039
- var SAFE_EXT = /^\.[a-z0-9]+$/iu;
2040
1822
  var CODE_FENCE_BLOCK = /^(?<fence>`{3,}|~{3,})[^\n]*\n[\s\S]*?^\k<fence>[^\n]*(?=\n|$)/gmu;
2041
1823
  var FENCE_TOKEN = /\u0000blume-fence-(?<index>\d+)\u0000/gu;
2042
1824
  var EXT_BY_MIME = new Map([
1825
+ ["image/apng", ".apng"],
2043
1826
  ["image/avif", ".avif"],
1827
+ ["image/bmp", ".bmp"],
2044
1828
  ["image/gif", ".gif"],
2045
1829
  ["image/jpeg", ".jpg"],
1830
+ ["image/jpg", ".jpg"],
2046
1831
  ["image/png", ".png"],
2047
1832
  ["image/svg+xml", ".svg"],
1833
+ ["image/tiff", ".tiff"],
1834
+ ["image/vnd.microsoft.icon", ".ico"],
2048
1835
  ["image/webp", ".webp"],
1836
+ ["image/x-icon", ".ico"],
2049
1837
  ["video/mp4", ".mp4"],
2050
1838
  ["video/ogg", ".ogv"],
2051
1839
  ["video/quicktime", ".mov"],
2052
1840
  ["video/webm", ".webm"]
2053
1841
  ]);
2054
- var UNKNOWN_EXT = ".bin";
2055
- var NON_MEDIA_TYPE = /^(?:text\/|application\/(?:[\w.-]+\+)?(?:json|xml|javascript))/u;
1842
+ var MEDIA_EXTS = new Set([...EXT_BY_MIME.values(), ".jpeg"]);
1843
+ var OPAQUE_TYPES = new Set([
1844
+ "",
1845
+ "application/octet-stream",
1846
+ "binary/octet-stream"
1847
+ ]);
2056
1848
  var PART_SUFFIX = ".part";
2057
1849
  var DEFAULT_TIMEOUT_MS = 120000;
2058
1850
  var extFromUrl = (url) => {
2059
1851
  const clean = url.split("?")[0] ?? url;
2060
- const ext = extname3(clean);
2061
- return SAFE_EXT.test(ext) ? ext.toLowerCase() : null;
1852
+ const ext = extname2(clean).toLowerCase();
1853
+ return MEDIA_EXTS.has(ext) ? ext : null;
2062
1854
  };
1855
+ var imageUrl = (pointy, bare) => (pointy ?? bare ?? "").replaceAll(DESTINATION_ESCAPE, "$<char>");
2063
1856
  var mediaType = (res) => res.headers.get("content-type")?.split(";")[0]?.trim().toLowerCase() ?? "";
2064
1857
  var exists = async (path) => {
2065
1858
  try {
@@ -2076,9 +1869,16 @@ var completedFor = async (dir, stem) => {
2076
1869
  } catch {
2077
1870
  return null;
2078
1871
  }
2079
- const candidates = names.filter((name) => name.startsWith(`${stem}.`) && !name.endsWith(PART_SUFFIX));
1872
+ const candidates = names.filter((name) => name.startsWith(stem) && MEDIA_EXTS.has(name.slice(stem.length)));
2080
1873
  return candidates.length === 1 ? candidates[0] ?? null : null;
2081
1874
  };
1875
+ var extFor = (type, urlExt) => {
1876
+ const ext = EXT_BY_MIME.get(type) ?? (OPAQUE_TYPES.has(type) ? urlExt : null);
1877
+ if (ext) {
1878
+ return ext;
1879
+ }
1880
+ throw new Error(OPAQUE_TYPES.has(type) ? `responded with ${type || "no content type"} and its URL names no image or video file` : `responded with ${type}, not an image or video`);
1881
+ };
2082
1882
  var materializeAssets = async (markdown, ctx) => {
2083
1883
  const doFetch = ctx.fetchImpl ?? globalThis.fetch;
2084
1884
  const limit = ctx.limit ?? ((task) => task());
@@ -2090,21 +1890,17 @@ var materializeAssets = async (markdown, ctx) => {
2090
1890
  fences.push(block);
2091
1891
  return `\x00blume-fence-${fences.length - 1}\x00`;
2092
1892
  });
2093
- const urls = new Set;
2094
- for (const pattern of [MD_IMAGE, HTML_VIDEO_SRC]) {
2095
- for (const match of masked.matchAll(pattern)) {
2096
- const url = match.groups?.url;
2097
- if (url && REMOTE.test(url)) {
2098
- urls.add(url);
2099
- }
2100
- }
2101
- }
1893
+ const referenced = [
1894
+ ...[...masked.matchAll(MD_IMAGE)].map((match) => imageUrl(match.groups?.pointy, match.groups?.bare)),
1895
+ ...[...masked.matchAll(HTML_VIDEO_SRC)].map((match) => match.groups?.url ?? "")
1896
+ ];
1897
+ const urls = new Set(referenced.filter((url) => REMOTE.test(url)));
2102
1898
  const download = async (url, stem) => {
2103
1899
  const urlExt = extFromUrl(url);
2104
1900
  if (urlExt && await exists(join3(ctx.assetsDir, `${stem}${urlExt}`))) {
2105
1901
  return `${stem}${urlExt}`;
2106
1902
  }
2107
- const completed = urlExt ? null : await completedFor(ctx.assetsDir, stem);
1903
+ const completed = await completedFor(ctx.assetsDir, stem);
2108
1904
  if (completed) {
2109
1905
  return completed;
2110
1906
  }
@@ -2112,14 +1908,11 @@ var materializeAssets = async (markdown, ctx) => {
2112
1908
  if (!res.ok) {
2113
1909
  throw new Error(`${res.status}`);
2114
1910
  }
2115
- const type = mediaType(res);
2116
- if (NON_MEDIA_TYPE.test(type)) {
2117
- throw new Error(`responded with ${type}, not a media file`);
2118
- }
1911
+ const ext = extFor(mediaType(res), urlExt);
2119
1912
  if (!res.body) {
2120
1913
  throw new Error("empty response body");
2121
1914
  }
2122
- const file = `${stem}${urlExt ?? EXT_BY_MIME.get(type) ?? UNKNOWN_EXT}`;
1915
+ const file = `${stem}${ext}`;
2123
1916
  const target = join3(ctx.assetsDir, file);
2124
1917
  await mkdir(ctx.assetsDir, { recursive: true });
2125
1918
  const part = `${target}.${randomUUID()}${PART_SUFFIX}`;
@@ -2165,8 +1958,8 @@ var materializeAssets = async (markdown, ctx) => {
2165
1958
  });
2166
1959
  }
2167
1960
  }));
2168
- const rewritten = masked.replaceAll(MD_IMAGE, (match, alt, url) => {
2169
- const local = rewrites.get(url);
1961
+ const rewritten = masked.replaceAll(MD_IMAGE, (match, alt, pointy, bare) => {
1962
+ const local = rewrites.get(imageUrl(pointy, bare));
2170
1963
  return local ? `![${alt}](${local})` : match;
2171
1964
  }).replaceAll(HTML_VIDEO_SRC, (match, open, url, close) => {
2172
1965
  const local = rewrites.get(url);
@@ -2176,12 +1969,27 @@ var materializeAssets = async (markdown, ctx) => {
2176
1969
  };
2177
1970
 
2178
1971
  // src/core/sources/notion.ts
2179
- var richToMarkdown = (rich = []) => rich.map(({ annotations = {}, href, plain_text: text }) => renderLink(renderInline(text, {
2180
- bold: annotations.bold,
2181
- code: annotations.code,
2182
- italic: annotations.italic,
2183
- strike: annotations.strikethrough
2184
- }), href ?? undefined)).join("");
1972
+ var richToMarkdown = (rich = []) => {
1973
+ const linked = [];
1974
+ for (const { annotations = {}, href, plain_text: text } of rich) {
1975
+ const run = {
1976
+ marks: {
1977
+ bold: annotations.bold,
1978
+ code: annotations.code,
1979
+ italic: annotations.italic,
1980
+ strike: annotations.strikethrough
1981
+ },
1982
+ text
1983
+ };
1984
+ const last = linked.at(-1);
1985
+ if (last && last.href === (href ?? undefined)) {
1986
+ last.runs.push(run);
1987
+ } else {
1988
+ linked.push({ href: href ?? undefined, runs: [run] });
1989
+ }
1990
+ }
1991
+ return renderInline(linked.flatMap(({ href, runs }) => linkParts(runs, href)));
1992
+ };
2185
1993
  var isBlockPayload = (value) => typeof value === "object";
2186
1994
  var payloadOf = (block) => {
2187
1995
  const value = block[block.type];
@@ -2202,7 +2010,8 @@ var withNotionRetry = async (call, attempt = 0) => {
2202
2010
  if (status !== RATE_LIMITED || attempt === MAX_RETRIES) {
2203
2011
  throw error;
2204
2012
  }
2205
- const retryAfter = Number(error.headers?.["retry-after"]);
2013
+ const { headers } = error;
2014
+ const retryAfter = Number(headers?.get?.("retry-after"));
2206
2015
  const wait = retryAfter > 0 ? retryAfter * SECOND_MS : BASE_DELAY_MS * 2 ** attempt * (1 + Math.random());
2207
2016
  await sleep(wait);
2208
2017
  return withNotionRetry(call, attempt + 1);
@@ -2221,6 +2030,7 @@ var LIST_BLOCKS = new Set([
2221
2030
  var isListItem = (block) => block !== undefined && LIST_BLOCKS.has(block.type);
2222
2031
  var richToPlain = (rich = []) => rich.map((node) => node.plain_text).join("");
2223
2032
  var jsxString = (value) => `{${JSON.stringify(value)}}`;
2033
+ var FRAME_ESCAPED = /\\(?=[<>])/gu;
2224
2034
  var YOUTUBE_HOST = /(?:^|\.)(?:youtube(?:-nocookie)?\.com|youtu\.be)$/u;
2225
2035
  var isYouTubeUrl = (url) => {
2226
2036
  const host = URL.parse(url)?.hostname ?? "";
@@ -2233,14 +2043,14 @@ var renderVideo = (data) => {
2233
2043
  }
2234
2044
  const caption = richToMarkdown(data.caption);
2235
2045
  const title = caption ? ` title=${jsxString(richToPlain(data.caption))}` : "";
2236
- const media = isYouTubeUrl(url) ? `<YouTube${title} url=${JSON.stringify(url)} />` : `<video controls src=${JSON.stringify(url)} />`;
2237
- return caption ? `<Frame caption=${jsxString(caption)}>
2046
+ const media = isYouTubeUrl(url) ? `<YouTube${title} url=${jsxString(url)} />` : `<video controls src="${url.replaceAll('"', "%22")}" />`;
2047
+ return caption ? `<Frame caption=${jsxString(caption.replaceAll(FRAME_ESCAPED, ""))}>
2238
2048
  ${media}
2239
2049
  </Frame>` : media;
2240
2050
  };
2241
2051
  var renderLeaf = (block) => {
2242
2052
  const data = payloadOf(block) ?? {};
2243
- const text = richToMarkdown(blockField(block));
2053
+ const text = guardBlockStart(richToMarkdown(blockField(block)));
2244
2054
  switch (block.type) {
2245
2055
  case "paragraph": {
2246
2056
  return text;
@@ -2320,7 +2130,10 @@ var notionSource = (options, ctx) => {
2320
2130
  return render(client, await childrenOf(client, target.id));
2321
2131
  };
2322
2132
  if (block.type === "callout") {
2323
- const body = [richToMarkdown(blockField(block)), await children(block)].filter(Boolean).join(`
2133
+ const body = [
2134
+ guardBlockStart(richToMarkdown(blockField(block))),
2135
+ await children(block)
2136
+ ].filter(Boolean).join(`
2324
2137
 
2325
2138
  `);
2326
2139
  return `<Callout>
@@ -2420,7 +2233,7 @@ ${nested}`;
2420
2233
  data.sidebar = { order };
2421
2234
  }
2422
2235
  const slugProp = richToPlain(page.properties[props.slug ?? "Slug"]?.rich_text);
2423
- const slug = slugifyPath(slugProp || title) || page.id;
2236
+ const slug = (slugProp ? slugifyPath(slugProp) : slugify(title.replaceAll("/", "-"))) || page.id;
2424
2237
  return { data, slug };
2425
2238
  };
2426
2239
  const toEntry = async (client, page) => {
@@ -2494,7 +2307,8 @@ ${nested}`;
2494
2307
  prefix: options.prefix,
2495
2308
  read,
2496
2309
  staged: true,
2497
- watch: options.pollInterval ? pollingWatch(() => load(true), options.pollInterval, () => load()) : undefined
2310
+ watch: options.pollInterval ? pollingWatch(() => load(true), options.pollInterval, () => load()) : undefined,
2311
+ withContext: (next) => notionSource(options, next)
2498
2312
  };
2499
2313
  };
2500
2314
 
@@ -2741,7 +2555,7 @@ var buildLinkIndex = (notes, options) => {
2741
2555
  if (cached) {
2742
2556
  return cached;
2743
2557
  }
2744
- const route = resolveEntryRoute({ ref: note.rel, slug: entrySlugFor(note.placement.navPath) }, ".md", isStringValue(note.data.slug) ? note.data.slug : undefined, options);
2558
+ const route = resolveEntryRoute({ ref: note.rel, slug: entrySlugFor(note.placement.navPath) }, ".md", isStringValue(note.data.slug) ? note.data.slug : undefined, { ...options, orderingPrefixes: true });
2745
2559
  const built = {
2746
2560
  anchors: new Map,
2747
2561
  locales: route.locales,
@@ -2802,11 +2616,15 @@ var noteToEntry = (pair, index, options, unresolved) => {
2802
2616
  const keep = keptKeysFor(note.data, options);
2803
2617
  const data = Object.fromEntries(Object.entries(note.data).filter(([key]) => !OBSIDIAN_NATIVE_KEYS.has(key) && (PAGE_META_KEYS.has(key) || keep.has(key))));
2804
2618
  const merged = title === undefined ? data : { ...data, title };
2805
- const text = transformBody(note.content.trim(), index, pair.self, unresolved);
2619
+ const body = note.content.trim();
2620
+ const text = transformBody(body, index, pair.self, unresolved);
2806
2621
  const raw = frontmatter_default.stringify(`${text}
2807
2622
  `, merged);
2623
+ const leading = note.content.slice(0, note.content.indexOf(body));
2808
2624
  return {
2809
2625
  body: { format: "md", text },
2626
+ bodyLineOffset: note.frontmatterLines + leading.split(`
2627
+ `).length - 1,
2810
2628
  data: merged,
2811
2629
  hash: hashText(raw),
2812
2630
  raw,
@@ -2818,11 +2636,13 @@ var noteToEntry = (pair, index, options, unresolved) => {
2818
2636
  var readNote = async (vaultDir, rel, options) => {
2819
2637
  const absPath = join5(vaultDir, rel);
2820
2638
  try {
2821
- const { content, data } = frontmatter_default(await readFile2(absPath, "utf-8"));
2639
+ const file = await readFile2(absPath, "utf-8");
2640
+ const { content, data } = frontmatter_default(file);
2822
2641
  return {
2823
2642
  absPath,
2824
2643
  content,
2825
2644
  data,
2645
+ frontmatterLines: strippedLineOffset(file, content),
2826
2646
  placement: placeEntryRef(rel, ".md", options),
2827
2647
  rel
2828
2648
  };
@@ -2906,14 +2726,26 @@ var obsidianSource = (options, ctx) => {
2906
2726
  const vaultDir = resolve2(ctx.projectRoot, options.vault);
2907
2727
  const exclude = new Set([...BLUME_IGNORE_DIRS, ...options.exclude ?? []]);
2908
2728
  const load = async () => {
2909
- const files = await walkVault(vaultDir, vaultDir, exclude);
2729
+ const skipped = [];
2730
+ const files = [];
2731
+ for (const rel of await walkVault(vaultDir, vaultDir, exclude)) {
2732
+ const unloadable = unloadablePathDiagnostic(rel, join5(vaultDir, rel));
2733
+ if (unloadable) {
2734
+ skipped.push(unloadable);
2735
+ } else {
2736
+ files.push(rel);
2737
+ }
2738
+ }
2910
2739
  const read = await Promise.all(files.map((rel) => readNote(vaultDir, rel, options)));
2911
2740
  const notes = read.filter((note) => note !== null);
2912
2741
  const { index, pairs } = buildLinkIndex(notes, options);
2913
2742
  const unresolved = noTargets();
2914
2743
  const entries = pairs.map((pair) => noteToEntry(pair, index, options, unresolved));
2915
2744
  return {
2916
- diagnostics: unresolvedDiagnostics(options.name, unresolved),
2745
+ diagnostics: [
2746
+ ...skipped,
2747
+ ...unresolvedDiagnostics(options.name, unresolved)
2748
+ ],
2917
2749
  entries
2918
2750
  };
2919
2751
  };
@@ -2956,6 +2788,7 @@ var obsidianSource = (options, ctx) => {
2956
2788
  contentRoot: vaultDir,
2957
2789
  load,
2958
2790
  name: options.name,
2791
+ orderedNames: true,
2959
2792
  prefix: options.prefix,
2960
2793
  read,
2961
2794
  staged: true,
@@ -2990,31 +2823,33 @@ var customBlock = (node, options) => {
2990
2823
  }
2991
2824
  return unsupported(`Lexical block${blockType ? `: ${blockType}` : ""}`, writesMdx(options.serializers));
2992
2825
  };
2993
- var renderInlines2 = (nodes, options) => nodes.map((node) => renderInlineNode2(node, options)).join("");
2994
- var renderInlineNode2 = (node, options) => {
2826
+ var inlineParts2 = (nodes, options) => nodes.flatMap((node) => inlineNodeParts2(node, options));
2827
+ var inlineNodeParts2 = (node, options) => {
2995
2828
  switch (typeOf(node)) {
2996
2829
  case "text": {
2997
- return renderInline(asString(node.text) ?? "", marksOf2(node));
2830
+ return [{ marks: marksOf2(node), text: asString(node.text) ?? "" }];
2998
2831
  }
2999
2832
  case "linebreak": {
3000
- return `
3001
- `;
2833
+ return [{ markdown: `
2834
+ ` }];
3002
2835
  }
3003
2836
  case "tab": {
3004
- return "\t";
2837
+ return [{ markdown: "\t" }];
3005
2838
  }
3006
2839
  case "link":
3007
2840
  case "autolink": {
3008
- return renderLink(renderInlines2(children2(node), options), asString(getPath(node, "fields.url")) ?? asString(node.url));
2841
+ return linkParts(inlineParts2(children2(node), options), asString(getPath(node, "fields.url")) ?? asString(node.url));
3009
2842
  }
3010
2843
  case "inlineBlock": {
3011
- return customBlock(node, options);
2844
+ return [{ markdown: customBlock(node, options) }];
3012
2845
  }
3013
2846
  default: {
3014
- return renderInlines2(children2(node), options);
2847
+ return inlineParts2(children2(node), options);
3015
2848
  }
3016
2849
  }
3017
2850
  };
2851
+ var inlineText = (nodes, options) => guardBlockStart(renderInline(inlineParts2(nodes, options)));
2852
+ var blockText2 = (node, options) => inlineText(children2(node), options);
3018
2853
  var renderUpload = (node, options) => {
3019
2854
  const value = asObject(node.value);
3020
2855
  const url = value ? asString(value.url) : undefined;
@@ -3045,7 +2880,7 @@ var renderList2 = (node, options) => {
3045
2880
  if (listType === "check") {
3046
2881
  check = item.checked === true ? "[x] " : "[ ] ";
3047
2882
  }
3048
- const line = `${check}${renderInlines2(inline, options)}`;
2883
+ const line = `${check}${inlineText(inline, options)}`;
3049
2884
  items.push(listItem(lastMarker, sub ? `${line}
3050
2885
  ${sub}` : line));
3051
2886
  index += 1;
@@ -3056,14 +2891,14 @@ ${sub}` : line));
3056
2891
  var renderBlock2 = (node, options) => {
3057
2892
  switch (typeOf(node)) {
3058
2893
  case "paragraph": {
3059
- return renderInlines2(children2(node), options);
2894
+ return blockText2(node, options);
3060
2895
  }
3061
2896
  case "heading": {
3062
2897
  const level = Number((asString(node.tag) ?? "h1").slice(1));
3063
- return `${headingPrefix(level)}${renderInlines2(children2(node), options)}`;
2898
+ return `${headingPrefix(level)}${blockText2(node, options)}`;
3064
2899
  }
3065
2900
  case "quote": {
3066
- return blockquote(renderInlines2(children2(node), options));
2901
+ return blockquote(blockText2(node, options));
3067
2902
  }
3068
2903
  case "list": {
3069
2904
  return renderList2(node, options);
@@ -3145,7 +2980,8 @@ var payloadSource = (options, ctx) => {
3145
2980
  fetchEntries,
3146
2981
  name: options.name,
3147
2982
  pollInterval: options.pollInterval,
3148
- prefix: options.prefix
2983
+ prefix: options.prefix,
2984
+ withContext: (next) => payloadSource(options, next)
3149
2985
  }, ctx);
3150
2986
  };
3151
2987
 
@@ -3161,47 +2997,49 @@ var HEADING_STYLES = new Map([
3161
2997
  ["h5", "##### "],
3162
2998
  ["h6", "###### "]
3163
2999
  ]);
3164
- var renderSpan = (span, defs) => {
3165
- const isCode = span.marks?.includes("code") ?? false;
3166
- let text = isCode ? span.text ?? "" : escapeMarkdownText(span.text ?? "");
3167
- if (!span.marks || span.marks.length === 0) {
3168
- return text;
3169
- }
3170
- let link;
3171
- for (const mark of span.marks) {
3172
- switch (mark) {
3173
- case "strong": {
3174
- text = `**${text}**`;
3175
- break;
3176
- }
3177
- case "em": {
3178
- text = `*${text}*`;
3179
- break;
3180
- }
3181
- case "code": {
3182
- text = `\`${text}\``;
3183
- break;
3184
- }
3185
- case "strike-through": {
3186
- text = `~~${text}~~`;
3187
- break;
3188
- }
3189
- default: {
3190
- const def = defs.get(mark);
3191
- if (def?._type === "link") {
3192
- link = def;
3193
- }
3194
- }
3195
- }
3196
- }
3197
- return link?.href ? `[${text}](${link.href})` : text;
3000
+ var spanRun = (span) => {
3001
+ const marks = span.marks ?? [];
3002
+ return {
3003
+ marks: {
3004
+ bold: marks.includes("strong"),
3005
+ code: marks.includes("code"),
3006
+ italic: marks.includes("em"),
3007
+ strike: marks.includes("strike-through")
3008
+ },
3009
+ text: span.text ?? ""
3010
+ };
3198
3011
  };
3199
3012
  var renderChildren = (block) => {
3200
3013
  const defs = new Map((block.markDefs ?? []).map((def) => [def._key, def]));
3201
- return (block.children ?? []).map((span) => renderSpan(span, defs)).join("");
3014
+ const linked = [];
3015
+ for (const span of block.children ?? []) {
3016
+ const link = (span.marks ?? []).map((mark) => defs.get(mark)).findLast((def) => def?._type === "link");
3017
+ const last = linked.at(-1);
3018
+ if (last && last.link === link) {
3019
+ last.runs.push(spanRun(span));
3020
+ } else {
3021
+ linked.push({ link, runs: [spanRun(span)] });
3022
+ }
3023
+ }
3024
+ return guardBlockStart(renderInline(linked.flatMap(({ link, runs }) => linkParts(runs, link?.href))));
3202
3025
  };
3203
3026
  var isAltText = (value) => typeof value === "string";
3204
- var renderBlock3 = (block, options) => {
3027
+ var listMarker = (block) => block.listItem === "number" ? "1." : "-";
3028
+ var listIndents = (blocks) => {
3029
+ const columns = [];
3030
+ return blocks.map((block) => {
3031
+ if (!block.listItem) {
3032
+ columns.length = 0;
3033
+ return 0;
3034
+ }
3035
+ const depth = Math.min(Math.max(0, (block.level ?? 1) - 1), columns.length);
3036
+ const indent = columns[depth - 1] ?? 0;
3037
+ columns.length = depth;
3038
+ columns.push(indent + listMarker(block).length + 1);
3039
+ return indent;
3040
+ });
3041
+ };
3042
+ var renderBlock3 = (block, options, indent) => {
3205
3043
  const custom = options.serializers?.[block._type];
3206
3044
  if (custom) {
3207
3045
  return custom(block);
@@ -3216,9 +3054,7 @@ var renderBlock3 = (block, options) => {
3216
3054
  }
3217
3055
  const inline = renderChildren(block);
3218
3056
  if (block.listItem) {
3219
- const indent = " ".repeat(Math.max(0, (block.level ?? 1) - 1));
3220
- const marker = block.listItem === "number" ? "1." : "-";
3221
- return `${indent}${marker} ${inline}`;
3057
+ return `${" ".repeat(indent)}${listMarker(block)} ${inline}`;
3222
3058
  }
3223
3059
  if (block.style === "blockquote") {
3224
3060
  return `> ${inline}`;
@@ -3226,7 +3062,8 @@ var renderBlock3 = (block, options) => {
3226
3062
  return `${HEADING_STYLES.get(block.style ?? "normal") ?? ""}${inline}`;
3227
3063
  };
3228
3064
  var portableTextToMarkdown = (blocks, options = {}) => {
3229
- const lines = blocks.map((block) => renderBlock3(block, options));
3065
+ const indents = listIndents(blocks);
3066
+ const lines = blocks.map((block, i) => renderBlock3(block, options, indents[i] ?? 0));
3230
3067
  const out = [];
3231
3068
  for (let i = 0;i < lines.length; i += 1) {
3232
3069
  const line = lines[i] ?? "";
@@ -3335,37 +3172,44 @@ var sanitySource = (options, ctx) => {
3335
3172
  prefix: options.prefix,
3336
3173
  read,
3337
3174
  staged: true,
3338
- watch: options.pollInterval ? pollingWatch(() => load(true), options.pollInterval, () => load()) : undefined
3175
+ watch: options.pollInterval ? pollingWatch(() => load(true), options.pollInterval, () => load()) : undefined,
3176
+ withContext: (next) => sanitySource(options, next)
3339
3177
  };
3340
3178
  };
3341
3179
 
3342
3180
  // src/core/sources/strapi-blocks.ts
3343
3181
  var children3 = (node) => objectsIn(node.children);
3344
3182
  var typeOf2 = (node) => asString(node.type) ?? "";
3345
- var renderInlines3 = (nodes) => nodes.map((node) => renderInlineNode3(node)).join("");
3346
- var renderInlineNode3 = (node) => {
3183
+ var inlineParts3 = (nodes) => nodes.flatMap((node) => inlineNodeParts3(node));
3184
+ var inlineNodeParts3 = (node) => {
3347
3185
  switch (typeOf2(node)) {
3348
3186
  case "text": {
3349
- return renderInline(asString(node.text) ?? "", {
3350
- bold: node.bold === true,
3351
- code: node.code === true,
3352
- italic: node.italic === true,
3353
- strike: node.strikethrough === true
3354
- });
3187
+ return [
3188
+ {
3189
+ marks: {
3190
+ bold: node.bold === true,
3191
+ code: node.code === true,
3192
+ italic: node.italic === true,
3193
+ strike: node.strikethrough === true
3194
+ },
3195
+ text: asString(node.text) ?? ""
3196
+ }
3197
+ ];
3355
3198
  }
3356
3199
  case "link": {
3357
- return renderLink(renderInlines3(children3(node)), asString(node.url));
3200
+ return linkParts(inlineParts3(children3(node)), asString(node.url));
3358
3201
  }
3359
3202
  default: {
3360
- return renderInlines3(children3(node));
3203
+ return inlineParts3(children3(node));
3361
3204
  }
3362
3205
  }
3363
3206
  };
3207
+ var blockText3 = (nodes) => guardBlockStart(renderInline(inlineParts3(nodes)));
3364
3208
  var renderList3 = (node) => {
3365
3209
  const ordered = asString(node.format) === "ordered";
3366
3210
  return children3(node).map((item, index) => {
3367
3211
  const kids = children3(item);
3368
- const line = renderInlines3(kids.filter((kid) => typeOf2(kid) !== "list"));
3212
+ const line = blockText3(kids.filter((kid) => typeOf2(kid) !== "list"));
3369
3213
  const sub = kids.filter((kid) => typeOf2(kid) === "list").map((list) => renderList3(list)).join(`
3370
3214
  `);
3371
3215
  const body = sub ? `${line}
@@ -3385,13 +3229,13 @@ var renderImage = (node, options) => {
3385
3229
  var renderBlock4 = (node, options) => {
3386
3230
  switch (typeOf2(node)) {
3387
3231
  case "paragraph": {
3388
- return renderInlines3(children3(node));
3232
+ return blockText3(children3(node));
3389
3233
  }
3390
3234
  case "heading": {
3391
- return `${headingPrefix(asNumber(node.level) ?? 1)}${renderInlines3(children3(node))}`;
3235
+ return `${headingPrefix(asNumber(node.level) ?? 1)}${blockText3(children3(node))}`;
3392
3236
  }
3393
3237
  case "quote": {
3394
- return blockquote(renderInlines3(children3(node)));
3238
+ return blockquote(blockText3(children3(node)));
3395
3239
  }
3396
3240
  case "list": {
3397
3241
  return renderList3(node);
@@ -3491,7 +3335,8 @@ var strapiSource = (options, ctx) => {
3491
3335
  fetchEntries,
3492
3336
  name: options.name,
3493
3337
  pollInterval: options.pollInterval,
3494
- prefix: options.prefix
3338
+ prefix: options.prefix,
3339
+ withContext: (next) => strapiSource(options, next)
3495
3340
  }, ctx);
3496
3341
  };
3497
3342
 
@@ -3509,10 +3354,18 @@ var uniqueNamer = () => {
3509
3354
  return name;
3510
3355
  };
3511
3356
  };
3512
- var sourceContext = (context, name, runtime) => ({
3357
+ var snapshotKey = (adapter, runtime) => {
3358
+ const mode = runtime.preview ? "preview" : "published";
3359
+ if (adapter.kind === "custom") {
3360
+ return mode;
3361
+ }
3362
+ const { pollInterval: _paced, ...fetched } = adapter.options;
3363
+ return `${mode}-${hashText(JSON.stringify(fetched))}`;
3364
+ };
3365
+ var sourceContext = (context, name, runtime, snapshot) => ({
3513
3366
  assetsBaseUrl: `/blume-assets/${name}`,
3514
3367
  assetsDir: join7(context.outDir, "public", "blume-assets", name),
3515
- cacheDir: join7(context.outDir, "cache", name),
3368
+ cacheDir: snapshot === undefined ? join7(context.outDir, "cache", name) : join7(context.outDir, "cache", name, snapshot),
3516
3369
  mode: runtime.mode,
3517
3370
  preview: runtime.preview,
3518
3371
  projectRoot: context.root,
@@ -3523,29 +3376,30 @@ var typeFrontmatterKeys = (config) => Object.fromEntries(Object.entries(config.c
3523
3376
  Object.keys(def.frontmatter)
3524
3377
  ]));
3525
3378
  var buildSource = (adapter, name, config, context, runtime) => {
3379
+ const ctx = () => sourceContext(context, name, runtime, snapshotKey(adapter, runtime));
3526
3380
  switch (adapter.kind) {
3527
3381
  case "filesystem": {
3528
3382
  const { pollInterval: _ignored, ...options } = adapter.options;
3529
3383
  return filesystemSource({ ...options, name, projectRoot: context.root });
3530
3384
  }
3531
3385
  case "custom": {
3532
- const source = adapter.options;
3386
+ const source = adapter.options.withContext?.(ctx()) ?? adapter.options;
3533
3387
  return source.name === name ? source : { ...source, name };
3534
3388
  }
3535
3389
  case "sanity": {
3536
- return sanitySource({ ...adapter.options, name }, sourceContext(context, name, runtime));
3390
+ return sanitySource({ ...adapter.options, name }, ctx());
3537
3391
  }
3538
3392
  case "notion": {
3539
- return notionSource({ ...adapter.options, name }, sourceContext(context, name, runtime));
3393
+ return notionSource({ ...adapter.options, name }, ctx());
3540
3394
  }
3541
3395
  case "contentful": {
3542
- return contentfulSource({ ...adapter.options, name }, sourceContext(context, name, runtime));
3396
+ return contentfulSource({ ...adapter.options, name }, ctx());
3543
3397
  }
3544
3398
  case "payload": {
3545
- return payloadSource({ ...adapter.options, name }, sourceContext(context, name, runtime));
3399
+ return payloadSource({ ...adapter.options, name }, ctx());
3546
3400
  }
3547
3401
  case "strapi": {
3548
- return strapiSource({ ...adapter.options, name }, sourceContext(context, name, runtime));
3402
+ return strapiSource({ ...adapter.options, name }, ctx());
3549
3403
  }
3550
3404
  case "obsidian": {
3551
3405
  const { pollInterval: _ignored, ...options } = adapter.options;
@@ -3557,22 +3411,22 @@ var buildSource = (adapter, name, config, context, runtime) => {
3557
3411
  name,
3558
3412
  typeFrontmatterKeys: typeFrontmatterKeys(config),
3559
3413
  versions: config.versions
3560
- }, sourceContext(context, name, runtime));
3414
+ }, ctx());
3561
3415
  }
3562
3416
  case "github-releases": {
3563
- return githubReleasesSource({ ...adapter.options, name }, sourceContext(context, name, runtime));
3417
+ return githubReleasesSource({ ...adapter.options, name, site: config.deployment.options.site }, ctx());
3564
3418
  }
3565
3419
  default: {
3566
3420
  break;
3567
3421
  }
3568
3422
  }
3569
- return mdxRemoteSource({ ...adapter.options, name }, sourceContext(context, name, runtime));
3423
+ return mdxRemoteSource({ ...adapter.options, name }, ctx());
3570
3424
  };
3571
3425
  var baseName = (adapter) => {
3572
3426
  if (adapter.kind === "custom") {
3573
3427
  return adapter.options.name;
3574
3428
  }
3575
- return adapter.options.prefix ?? adapter.kind;
3429
+ return trimChar(adapter.options.prefix ?? "", "/") || adapter.kind;
3576
3430
  };
3577
3431
  var resolveSources = (config, context, runtime) => {
3578
3432
  const nameFor = uniqueNamer();
@@ -3640,6 +3494,7 @@ var normalizeLoadedEntries = (loaded, config) => {
3640
3494
  source: {
3641
3495
  monolingual: source.monolingual,
3642
3496
  name: source.name,
3497
+ orderedNames: source.orderedNames,
3643
3498
  prefix: source.prefix,
3644
3499
  staged: source.staged
3645
3500
  },
@@ -3651,12 +3506,24 @@ var normalizeLoadedEntries = (loaded, config) => {
3651
3506
  }
3652
3507
  pages.push(...normalized.pages);
3653
3508
  allDiagnostics.push(...normalized.diagnostics);
3509
+ if (normalized.pages.length > 0) {
3510
+ allDiagnostics.push(...unknownDirectiveDiagnostics(entry, source.name));
3511
+ }
3654
3512
  }
3655
3513
  }
3656
3514
  return { diagnostics: allDiagnostics, droppedPages, pages };
3657
3515
  };
3516
+ var gitContentRoots = (sources, gitRoot) => {
3517
+ if (gitRoot === null) {
3518
+ return [];
3519
+ }
3520
+ const top = realPath(gitRoot);
3521
+ return sources.flatMap((source) => source.contentRoot ? [source.contentRoot] : []).filter((dir) => isWithin(top, realPath(dir)));
3522
+ };
3658
3523
  var isLogoShorthand = (logo) => typeof logo === "string";
3659
3524
  var logoHref = (logo) => logo === undefined || isLogoShorthand(logo) ? "/" : logo.href ?? "/";
3525
+ var isBannerShorthand = (banner) => typeof banner === "string";
3526
+ var bannerLinkHref = (banner) => banner === undefined || isBannerShorthand(banner) ? undefined : banner.link?.href;
3660
3527
  var scanProject = async (root, options = {}) => {
3661
3528
  const mode = options.mode ?? "dev";
3662
3529
  const preview = options.preview ?? false;
@@ -3675,7 +3542,14 @@ var scanProject = async (root, options = {}) => {
3675
3542
  for (const source of sources) {
3676
3543
  source.validate?.();
3677
3544
  }
3678
- const metaSources = sources.flatMap((source) => source.staged || !source.contentRoot ? [] : [{ prefix: source.prefix, root: source.contentRoot }]);
3545
+ const metaSources = sources.flatMap((source) => source.staged || !source.contentRoot ? [] : [
3546
+ {
3547
+ exclude: source.exclude,
3548
+ include: source.include,
3549
+ prefix: source.prefix,
3550
+ root: source.contentRoot
3551
+ }
3552
+ ]);
3679
3553
  const localeDirs = config.i18n && config.i18n.parser === "dir" ? config.i18n.locales.flatMap((locale) => locale.code === config.i18n?.defaultLocale ? [] : [locale.code]) : undefined;
3680
3554
  const [loaded, folderMeta] = await Promise.all([
3681
3555
  Promise.all(sources.map(async (source) => ({ source, ...await source.load() }))),
@@ -3717,7 +3591,7 @@ var scanProject = async (root, options = {}) => {
3717
3591
  if (lastModified.enabled && lastModified.source === "git") {
3718
3592
  const fsPaths = pages.map((page) => page.sourcePath).filter((path) => path !== undefined);
3719
3593
  const gitRoot = gitRepositoryRoot(context.root);
3720
- const contentRoots = sources.flatMap((source) => source.contentRoot ? [source.contentRoot] : []).filter((dir) => gitRoot !== null && isWithin(gitRoot, dir));
3594
+ const contentRoots = gitContentRoots(sources, gitRoot);
3721
3595
  const gitTimes = gitLastModifiedTimes(context.root, contentRoots, fsPaths, gitRoot);
3722
3596
  for (const page of pages) {
3723
3597
  if (!page.lastModified && page.sourcePath) {
@@ -3729,6 +3603,7 @@ var scanProject = async (root, options = {}) => {
3729
3603
  }
3730
3604
  const customPages = context.pagesRoot ? await discoverPages(context.pagesRoot) : [];
3731
3605
  const graph = buildContentGraph(pages, {
3606
+ bannerHref: bannerLinkHref(config.banner),
3732
3607
  basePath: config.basePath,
3733
3608
  brandHref: logoHref(config.logo),
3734
3609
  extraRoutes: new Set([
@@ -3766,7 +3641,7 @@ var scanProject = async (root, options = {}) => {
3766
3641
  };
3767
3642
  };
3768
3643
 
3769
- export { validateSearchPopularIcons, validateNavTargets, contentIndexable, buildManifest, isWithin, scanProject };
3644
+ export { directiveSource, validateSearchPopularIcons, validateNavTargets, contentIndexable, buildManifest, findFolderMetaFiles, isWithin, scanProject };
3770
3645
 
3771
- //# debugId=EBFFBF92D39DE54464756E2164756E21
3772
- //# sourceMappingURL=chunk-fs23ddbb.js.map
3646
+ //# debugId=B7A86608D56AB00864756E2164756E21
3647
+ //# sourceMappingURL=chunk-j85vccga.js.map