blume 1.7.3 → 2.0.1

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 (690) hide show
  1. package/AGENTS.md +19 -0
  2. package/CHANGELOG.md +245 -0
  3. package/README.md +34 -21
  4. package/dist/cli/{chunk-vacwm2hv.js → chunk-27g6wdth.js} +2 -2
  5. package/dist/cli/{chunk-ps4m1xh4.js → chunk-2hn4b8z7.js} +513 -550
  6. package/dist/cli/chunk-2hn4b8z7.js.map +12 -0
  7. package/dist/cli/chunk-5shv93fd.js +39 -0
  8. package/dist/cli/chunk-5shv93fd.js.map +10 -0
  9. package/dist/cli/chunk-6crbhc3x.js +361 -0
  10. package/dist/cli/chunk-6crbhc3x.js.map +14 -0
  11. package/dist/cli/{chunk-8cjtbafj.js → chunk-6hsn950k.js} +109 -383
  12. package/dist/cli/chunk-6hsn950k.js.map +10 -0
  13. package/dist/cli/chunk-6vm74dry.js +148 -0
  14. package/dist/cli/chunk-6vm74dry.js.map +10 -0
  15. package/dist/cli/{chunk-yg63d42r.js → chunk-79jhk4py.js} +87 -58
  16. package/dist/cli/chunk-79jhk4py.js.map +35 -0
  17. package/dist/cli/chunk-82bbrxdn.js +51 -0
  18. package/dist/cli/chunk-82bbrxdn.js.map +10 -0
  19. package/dist/cli/chunk-abh8yjkn.js +31 -0
  20. package/dist/cli/chunk-abh8yjkn.js.map +10 -0
  21. package/dist/cli/chunk-ah61y8py.js +75 -0
  22. package/dist/cli/chunk-ah61y8py.js.map +11 -0
  23. package/dist/cli/{chunk-dwgcp5sm.js → chunk-ce574jw2.js} +1 -1
  24. package/dist/cli/chunk-ch6g3ar0.js +102 -0
  25. package/dist/cli/chunk-ch6g3ar0.js.map +10 -0
  26. package/dist/cli/chunk-dh8cwk36.js +279 -0
  27. package/dist/cli/chunk-dh8cwk36.js.map +10 -0
  28. package/dist/cli/{chunk-rqy0s5wh.js → chunk-epjnccmv.js} +17 -16
  29. package/dist/cli/{chunk-rqy0s5wh.js.map → chunk-epjnccmv.js.map} +3 -3
  30. package/dist/cli/chunk-f2z5v128.js +97 -0
  31. package/dist/cli/chunk-f2z5v128.js.map +10 -0
  32. package/dist/cli/{chunk-2z47ypj8.js → chunk-fa25z98p.js} +16 -3
  33. package/dist/cli/chunk-fa25z98p.js.map +11 -0
  34. package/dist/cli/{chunk-1jefwnfs.js → chunk-fs23ddbb.js} +1076 -2592
  35. package/dist/cli/chunk-fs23ddbb.js.map +35 -0
  36. package/dist/cli/{chunk-e7f42gdj.js → chunk-fxypxtvm.js} +2 -2
  37. package/dist/cli/{chunk-bvwwhd84.js → chunk-fz5wtpmh.js} +15 -15
  38. package/dist/cli/{chunk-bvwwhd84.js.map → chunk-fz5wtpmh.js.map} +1 -1
  39. package/dist/cli/chunk-hdpx1tax.js +91 -0
  40. package/dist/cli/chunk-hdpx1tax.js.map +10 -0
  41. package/dist/cli/{chunk-ct47dqpx.js → chunk-jts8mvcz.js} +67 -7
  42. package/dist/cli/{chunk-2mzebbbz.js.map → chunk-jts8mvcz.js.map} +6 -4
  43. package/dist/cli/{chunk-ahnw3kxw.js → chunk-jwyddg7y.js} +27 -22
  44. package/dist/cli/chunk-jwyddg7y.js.map +15 -0
  45. package/dist/cli/{chunk-cjtn640a.js → chunk-kdp5q7ke.js} +43 -20
  46. package/dist/cli/chunk-kdp5q7ke.js.map +10 -0
  47. package/dist/cli/{chunk-5g0w1e2c.js → chunk-kpf8rrjc.js} +28 -16
  48. package/dist/cli/{chunk-5g0w1e2c.js.map → chunk-kpf8rrjc.js.map} +4 -4
  49. package/dist/cli/chunk-m3vmjgmq.js +133 -0
  50. package/dist/cli/chunk-m3vmjgmq.js.map +10 -0
  51. package/dist/cli/{chunk-b27xqwn9.js → chunk-mb2919y2.js} +9 -5
  52. package/dist/cli/chunk-mb2919y2.js.map +10 -0
  53. package/dist/cli/{chunk-5qk08vmp.js → chunk-q5163e60.js} +133 -54
  54. package/dist/cli/chunk-q5163e60.js.map +11 -0
  55. package/dist/cli/chunk-qkqwkpte.js +12437 -0
  56. package/dist/cli/chunk-qkqwkpte.js.map +182 -0
  57. package/dist/cli/{chunk-4x36ddpw.js → chunk-qs4q5p4e.js} +81 -87
  58. package/dist/cli/chunk-qs4q5p4e.js.map +10 -0
  59. package/dist/cli/{chunk-0xjyb285.js → chunk-qwsrynx5.js} +15 -5
  60. package/dist/cli/{chunk-0xjyb285.js.map → chunk-qwsrynx5.js.map} +4 -4
  61. package/dist/cli/chunk-s1p84fyh.js +261 -0
  62. package/dist/cli/chunk-s1p84fyh.js.map +10 -0
  63. package/dist/cli/chunk-s6jhgk0q.js +176 -0
  64. package/dist/cli/chunk-s6jhgk0q.js.map +11 -0
  65. package/dist/cli/chunk-vtk4a6dg.js +374 -0
  66. package/dist/cli/chunk-vtk4a6dg.js.map +10 -0
  67. package/dist/cli/{chunk-k79xp7av.js → chunk-wgm7m9qk.js} +230 -700
  68. package/dist/cli/chunk-wgm7m9qk.js.map +36 -0
  69. package/dist/cli/chunk-wm7js3j9.js +145 -0
  70. package/dist/cli/chunk-wm7js3j9.js.map +11 -0
  71. package/dist/cli/chunk-yt5n7ppj.js +79 -0
  72. package/dist/cli/chunk-yt5n7ppj.js.map +10 -0
  73. package/dist/cli/{chunk-3r45185y.js → chunk-yw7dm696.js} +9 -11
  74. package/dist/cli/{chunk-3r45185y.js.map → chunk-yw7dm696.js.map} +3 -3
  75. package/dist/cli/{chunk-nn13znc2.js → chunk-zxcczpyx.js} +1 -1
  76. package/dist/cli/chunk-zxh4d9vy.js +122 -0
  77. package/dist/cli/chunk-zxh4d9vy.js.map +11 -0
  78. package/dist/cli/index.js +214 -57
  79. package/dist/cli/index.js.map +8 -7
  80. package/dist/types/ai/agent-readability.d.ts +52 -0
  81. package/dist/types/ai/ai-catalog.d.ts +42 -0
  82. package/dist/types/ai/api/paths.d.ts +17 -0
  83. package/dist/types/ai/api-catalog.d.ts +18 -0
  84. package/dist/types/ai/ask-context.d.ts +7 -7
  85. package/dist/types/ai/ask.d.ts +368 -0
  86. package/dist/types/ai/changelog-markdown.d.ts +2 -0
  87. package/dist/types/ai/component-markdown.d.ts +2 -2
  88. package/dist/types/ai/index.d.ts +24 -0
  89. package/dist/types/ai/link-headers.d.ts +24 -0
  90. package/dist/types/ai/llms.d.ts +25 -0
  91. package/dist/types/ai/markdown.d.ts +45 -0
  92. package/dist/types/ai/mcp/discovery.d.ts +68 -0
  93. package/dist/types/ai/mcp/tools.d.ts +16 -0
  94. package/dist/types/ai/openapi-components.d.ts +43 -0
  95. package/dist/types/ai/relative-links.d.ts +26 -0
  96. package/dist/types/ai/serializers.d.ts +15 -0
  97. package/dist/types/ai/skills.d.ts +42 -0
  98. package/dist/types/ai/tar.d.ts +25 -0
  99. package/dist/types/ai/visibility.d.ts +17 -0
  100. package/dist/types/ai/web-bot-auth.d.ts +16 -0
  101. package/dist/types/analytics/adobe.d.ts +35 -0
  102. package/dist/types/analytics/amplitude.d.ts +50 -0
  103. package/dist/types/analytics/clarity.d.ts +31 -0
  104. package/dist/types/analytics/clearbit.d.ts +29 -0
  105. package/dist/types/analytics/cloudflare.d.ts +41 -0
  106. package/dist/types/analytics/fathom.d.ts +41 -0
  107. package/dist/types/analytics/google-analytics.d.ts +44 -0
  108. package/dist/types/analytics/google-tag-manager.d.ts +40 -0
  109. package/dist/types/analytics/head.d.ts +30 -0
  110. package/dist/types/analytics/heap.d.ts +40 -0
  111. package/dist/types/analytics/hightouch.d.ts +44 -0
  112. package/dist/types/analytics/hotjar.d.ts +33 -0
  113. package/dist/types/analytics/index.d.ts +61 -0
  114. package/dist/types/analytics/inline.d.ts +17 -0
  115. package/dist/types/analytics/logrocket.d.ts +44 -0
  116. package/dist/types/analytics/mixpanel.d.ts +67 -0
  117. package/dist/types/analytics/pirsch.d.ts +42 -0
  118. package/dist/types/analytics/plausible.d.ts +56 -0
  119. package/dist/types/analytics/posthog.d.ts +45 -0
  120. package/dist/types/analytics/schema.d.ts +320 -0
  121. package/dist/types/analytics/script.d.ts +46 -0
  122. package/dist/types/analytics/segment.d.ts +50 -0
  123. package/dist/types/analytics/vercel.d.ts +48 -0
  124. package/dist/types/astro/integration.d.ts +76 -0
  125. package/dist/types/astro/markdown-negotiation.d.ts +23 -0
  126. package/dist/types/astro/module-types.d.ts +14 -0
  127. package/dist/types/astro/pages.d.ts +44 -0
  128. package/dist/types/cli/env.d.ts +12 -0
  129. package/dist/types/cli/init/scaffold.d.ts +154 -0
  130. package/dist/types/components/layout/nav-utils.d.ts +11 -0
  131. package/dist/types/core/adapter.d.ts +47 -0
  132. package/dist/types/core/api-name.d.ts +7 -0
  133. package/dist/types/core/changelog-index.d.ts +13 -0
  134. package/dist/types/core/config-input.d.ts +197 -526
  135. package/dist/types/core/config.d.ts +61 -35
  136. package/dist/types/core/content-assets.d.ts +76 -0
  137. package/dist/types/core/custom-pages.d.ts +33 -0
  138. package/dist/types/core/data.d.ts +30 -15
  139. package/dist/types/core/define-components.d.ts +12 -9
  140. package/dist/types/core/deployment-env.d.ts +6 -11
  141. package/dist/types/core/frontmatter.d.ts +10 -0
  142. package/dist/types/core/graph.d.ts +18 -0
  143. package/dist/types/core/heading-markers.d.ts +54 -0
  144. package/dist/types/core/i18n-ui.d.ts +12 -10
  145. package/dist/types/core/i18n.d.ts +87 -0
  146. package/dist/types/core/includes.d.ts +138 -0
  147. package/dist/types/core/last-modified.d.ts +47 -0
  148. package/dist/types/core/links.d.ts +95 -0
  149. package/dist/types/core/locale-links.d.ts +60 -0
  150. package/dist/types/core/manifest.d.ts +17 -0
  151. package/dist/types/core/meta.d.ts +38 -0
  152. package/dist/types/core/nav-diagnostics.d.ts +26 -0
  153. package/dist/types/core/navigation.d.ts +6 -0
  154. package/dist/types/core/node-require.d.ts +19 -0
  155. package/dist/types/core/package-json.d.ts +13 -0
  156. package/dist/types/core/probe.d.ts +42 -0
  157. package/dist/types/core/project-graph.d.ts +51 -0
  158. package/dist/types/core/project.d.ts +2 -0
  159. package/dist/types/core/safe-href.d.ts +2 -0
  160. package/dist/types/core/safe-links.d.ts +26 -0
  161. package/dist/types/core/schema.d.ts +2284 -454
  162. package/dist/types/core/site-url.d.ts +16 -0
  163. package/dist/types/core/sources/assets.d.ts +36 -0
  164. package/dist/types/core/sources/cache.d.ts +37 -0
  165. package/dist/types/core/sources/collection.d.ts +28 -0
  166. package/dist/types/core/sources/contentful-rich-text.d.ts +31 -0
  167. package/dist/types/core/sources/contentful.d.ts +40 -0
  168. package/dist/types/core/sources/filesystem.d.ts +24 -0
  169. package/dist/types/core/sources/github-releases.d.ts +31 -0
  170. package/dist/types/core/sources/json.d.ts +30 -0
  171. package/dist/types/core/sources/lexical.d.ts +19 -0
  172. package/dist/types/core/sources/lower.d.ts +75 -0
  173. package/dist/types/core/sources/mdx-remote.d.ts +28 -0
  174. package/dist/types/core/sources/normalize.d.ts +147 -0
  175. package/dist/types/core/sources/notion.d.ts +131 -0
  176. package/dist/types/core/sources/obsidian.d.ts +46 -0
  177. package/dist/types/core/sources/payload.d.ts +39 -0
  178. package/dist/types/core/sources/portable-text.d.ts +42 -0
  179. package/dist/types/core/sources/read.d.ts +23 -0
  180. package/dist/types/core/sources/remote.d.ts +74 -0
  181. package/dist/types/core/sources/resolve.d.ts +19 -0
  182. package/dist/types/core/sources/sanity.d.ts +43 -0
  183. package/dist/types/core/sources/strapi-blocks.d.ts +12 -0
  184. package/dist/types/core/sources/strapi.d.ts +33 -0
  185. package/dist/types/core/sources/types.d.ts +7 -0
  186. package/dist/types/core/sources/watch.d.ts +45 -0
  187. package/dist/types/core/text-width.d.ts +11 -0
  188. package/dist/types/core/types.d.ts +15 -1
  189. package/dist/types/core/unrecognized-keys.d.ts +7 -0
  190. package/dist/types/core/versions.d.ts +72 -0
  191. package/dist/types/core/yaml.d.ts +9 -0
  192. package/dist/types/deploy/adapter-output.d.ts +45 -0
  193. package/dist/types/deploy/adapters/cloudflare.d.ts +40 -0
  194. package/dist/types/deploy/adapters/index.d.ts +29 -0
  195. package/dist/types/deploy/adapters/netlify.d.ts +37 -0
  196. package/dist/types/deploy/adapters/node.d.ts +37 -0
  197. package/dist/types/deploy/adapters/registry.d.ts +133 -0
  198. package/dist/types/deploy/adapters/types.d.ts +71 -0
  199. package/dist/types/deploy/adapters/vercel.d.ts +38 -0
  200. package/dist/types/deploy/artifacts.d.ts +65 -0
  201. package/dist/types/deploy/cloudflare-negotiation.d.ts +196 -0
  202. package/dist/types/deploy/function-bundle.d.ts +80 -0
  203. package/dist/types/deploy/headers.d.ts +50 -0
  204. package/dist/types/deploy/node-headers.d.ts +42 -0
  205. package/dist/types/deploy/platforms/cloudflare.d.ts +40 -0
  206. package/dist/types/deploy/platforms/index.d.ts +18 -0
  207. package/dist/types/deploy/platforms/netlify.d.ts +13 -0
  208. package/dist/types/deploy/platforms/node.d.ts +11 -0
  209. package/dist/types/deploy/platforms/paths.d.ts +27 -0
  210. package/dist/types/deploy/platforms/static.d.ts +10 -0
  211. package/dist/types/deploy/platforms/types.d.ts +104 -0
  212. package/dist/types/deploy/platforms/vercel.d.ts +32 -0
  213. package/dist/types/deploy/redirects.d.ts +53 -0
  214. package/dist/types/deploy/robots.d.ts +8 -0
  215. package/dist/types/deploy/rss.d.ts +31 -0
  216. package/dist/types/deploy/sitemap.d.ts +21 -0
  217. package/dist/types/deploy/vercel-negotiation.d.ts +109 -0
  218. package/dist/types/markdown/code-title.d.ts +32 -0
  219. package/dist/types/markdown/fence-meta.d.ts +23 -0
  220. package/dist/types/markdown/themes.d.ts +3 -3
  221. package/dist/types/openapi/asyncapi.d.ts +129 -0
  222. package/dist/types/openapi/graphql-build.d.ts +8 -0
  223. package/dist/types/openapi/graphql.d.ts +122 -0
  224. package/dist/types/openapi/model.d.ts +158 -0
  225. package/dist/types/openapi/parse.d.ts +57 -0
  226. package/dist/types/openapi/references.d.ts +37 -25
  227. package/dist/types/openapi/render-mdx.d.ts +33 -0
  228. package/dist/types/openapi/sentence.d.ts +7 -0
  229. package/dist/types/openapi/signature.d.ts +10 -0
  230. package/dist/types/openapi/source.d.ts +22 -0
  231. package/dist/types/openapi/spec-dependency-error.d.ts +10 -0
  232. package/dist/types/reference/asyncapi.d.ts +166 -0
  233. package/dist/types/reference/graphql.d.ts +181 -0
  234. package/dist/types/reference/index.d.ts +32 -0
  235. package/dist/types/reference/openapi.d.ts +165 -0
  236. package/dist/types/reference/options.d.ts +157 -0
  237. package/dist/types/reference/scalar.d.ts +136 -0
  238. package/dist/types/reference/schema.d.ts +630 -0
  239. package/dist/types/search/adapters/algolia.d.ts +32 -0
  240. package/dist/types/search/adapters/flexsearch.d.ts +12 -0
  241. package/dist/types/search/adapters/index.d.ts +31 -0
  242. package/dist/types/search/adapters/mixedbread.d.ts +22 -0
  243. package/dist/types/search/adapters/orama-cloud.d.ts +33 -0
  244. package/dist/types/search/adapters/orama.d.ts +13 -0
  245. package/dist/types/search/adapters/pagefind.d.ts +12 -0
  246. package/dist/types/search/adapters/registry.d.ts +228 -0
  247. package/dist/types/search/adapters/types.d.ts +34 -0
  248. package/dist/types/search/adapters/typesense.d.ts +42 -0
  249. package/dist/types/search/build.d.ts +23 -0
  250. package/dist/types/search/documents.d.ts +90 -0
  251. package/dist/types/search/facets.d.ts +3 -0
  252. package/dist/types/search/orama-index.d.ts +1 -1
  253. package/dist/types/search/sync/algolia.d.ts +14 -0
  254. package/dist/types/search/sync/index.d.ts +14 -0
  255. package/dist/types/search/sync/orama-cloud.d.ts +10 -0
  256. package/dist/types/search/sync/typesense.d.ts +14 -0
  257. package/dist/types/sources/contentful.d.ts +68 -0
  258. package/dist/types/sources/custom.d.ts +20 -0
  259. package/dist/types/sources/filesystem.d.ts +42 -0
  260. package/dist/types/sources/github-releases.d.ts +46 -0
  261. package/dist/types/sources/index.d.ts +48 -0
  262. package/dist/types/sources/mdx-remote.d.ts +63 -0
  263. package/dist/types/sources/notion.d.ts +65 -0
  264. package/dist/types/sources/obsidian.d.ts +34 -0
  265. package/dist/types/sources/payload.d.ts +68 -0
  266. package/dist/types/sources/registry.d.ts +1414 -0
  267. package/dist/types/sources/sanity.d.ts +69 -0
  268. package/dist/types/sources/shared.d.ts +46 -0
  269. package/dist/types/sources/strapi.d.ts +66 -0
  270. package/dist/types/theme/icon-kind.d.ts +11 -0
  271. package/dist/types/theme/icons.d.ts +20 -0
  272. package/docs/01-quickstart.mdx +18 -18
  273. package/docs/02-deployment.mdx +52 -28
  274. package/docs/03-upgrading.mdx +364 -0
  275. package/docs/04-migrating.mdx +58 -0
  276. package/docs/08-faq.mdx +5 -5
  277. package/docs/advanced/blog.mdx +13 -6
  278. package/docs/advanced/changelog.mdx +23 -35
  279. package/docs/advanced/custom-pages.mdx +22 -10
  280. package/docs/advanced/meta.ts +1 -8
  281. package/docs/advanced/skills.mdx +8 -0
  282. package/docs/cli/audit.mdx +646 -0
  283. package/docs/cli/doctor.mdx +29 -0
  284. package/docs/{reference/eval.mdx → cli/evals.mdx} +12 -11
  285. package/docs/cli/index.mdx +105 -0
  286. package/docs/cli/meta.ts +7 -0
  287. package/docs/{reference → cli}/translate.mdx +9 -9
  288. package/docs/cli/validate.mdx +41 -0
  289. package/docs/cli/version.mdx +40 -0
  290. package/docs/configuration/analytics.mdx +350 -59
  291. package/docs/configuration/assistant.mdx +357 -0
  292. package/docs/configuration/customization.mdx +16 -10
  293. package/docs/configuration/index.mdx +50 -31
  294. package/docs/configuration/meta.ts +1 -1
  295. package/docs/configuration/search.mdx +76 -55
  296. package/docs/configuration/theming.mdx +24 -15
  297. package/docs/content/components.mdx +21 -5
  298. package/docs/{reference → content}/frontmatter.mdx +35 -1
  299. package/docs/content/i18n.mdx +9 -7
  300. package/docs/content/includes.mdx +2 -4
  301. package/docs/content/index.mdx +1 -1
  302. package/docs/content/islands.mdx +11 -6
  303. package/docs/content/meta.mdx +1 -1
  304. package/docs/content/meta.ts +1 -0
  305. package/docs/content/navigation.mdx +9 -7
  306. package/docs/content/sources.mdx +145 -50
  307. package/docs/content/syntax.mdx +16 -12
  308. package/docs/content/versioning.mdx +3 -3
  309. package/docs/discoverability/agent-discovery.mdx +12 -12
  310. package/docs/discoverability/index.mdx +4 -4
  311. package/docs/discoverability/json-api.mdx +4 -4
  312. package/docs/discoverability/llms-txt.mdx +6 -6
  313. package/docs/discoverability/markdown.mdx +4 -4
  314. package/docs/discoverability/mcp.mdx +11 -11
  315. package/docs/discoverability/sitemap-and-robots.mdx +3 -3
  316. package/docs/index.mdx +4 -4
  317. package/docs/references/asyncapi.mdx +59 -0
  318. package/docs/{advanced → references}/graphql.mdx +45 -32
  319. package/docs/{reference → references}/meta.ts +2 -2
  320. package/docs/references/openapi.mdx +171 -0
  321. package/docs/references/scalar.mdx +64 -0
  322. package/package.json +42 -8
  323. package/skills/blume/SKILL.md +23 -10
  324. package/skills/blume-migrate/SKILL.md +22 -21
  325. package/skills/blume-migrate/assets/oxfmt@0.67.0.patch +49 -0
  326. package/skills/blume-migrate/references/docusaurus.md +5 -4
  327. package/skills/blume-migrate/references/fumadocs.md +4 -4
  328. package/skills/blume-migrate/references/mintlify.md +23 -8
  329. package/skills/blume-migrate/references/monorepo.md +6 -6
  330. package/skills/blume-migrate/references/starlight.md +4 -4
  331. package/src/ai/agent-readability.ts +25 -26
  332. package/src/ai/ai-catalog.ts +50 -32
  333. package/src/ai/api/handlers.ts +46 -11
  334. package/src/ai/api/paths.ts +1 -1
  335. package/src/ai/api-catalog.ts +8 -8
  336. package/src/ai/ask-context.ts +7 -7
  337. package/src/ai/ask-data.ts +3 -3
  338. package/src/ai/ask.ts +637 -100
  339. package/src/ai/changelog-markdown.ts +91 -0
  340. package/src/ai/component-markdown.ts +328 -12
  341. package/src/ai/cors.ts +3 -3
  342. package/src/ai/index.ts +45 -0
  343. package/src/ai/link-headers.ts +7 -6
  344. package/src/ai/llms.ts +20 -15
  345. package/src/ai/markdown.ts +35 -5
  346. package/src/ai/mcp/data.ts +9 -5
  347. package/src/ai/openapi-components.ts +5 -2
  348. package/src/ai/relative-links.ts +170 -0
  349. package/src/ai/serializers.ts +3 -3
  350. package/src/ai/skills.ts +1 -1
  351. package/src/ai/visibility.ts +1 -1
  352. package/src/ai/web-bot-auth.ts +2 -2
  353. package/src/analytics/adobe.ts +46 -0
  354. package/src/analytics/amplitude.ts +79 -0
  355. package/src/analytics/clarity.ts +46 -0
  356. package/src/analytics/clearbit.ts +47 -0
  357. package/src/analytics/cloudflare.ts +67 -0
  358. package/src/analytics/fathom.ts +64 -0
  359. package/src/analytics/google-analytics.ts +84 -0
  360. package/src/analytics/google-tag-manager.ts +61 -0
  361. package/src/analytics/head.ts +130 -0
  362. package/src/analytics/heap.ts +65 -0
  363. package/src/analytics/hightouch.ts +77 -0
  364. package/src/analytics/hotjar.ts +47 -0
  365. package/src/analytics/index.ts +71 -0
  366. package/src/analytics/inline.ts +21 -0
  367. package/src/analytics/logrocket.ts +71 -0
  368. package/src/analytics/mixpanel.ts +92 -0
  369. package/src/analytics/pirsch.ts +66 -0
  370. package/src/analytics/plausible.ts +83 -0
  371. package/src/analytics/posthog.ts +78 -0
  372. package/src/analytics/schema.ts +60 -0
  373. package/src/analytics/script.ts +60 -0
  374. package/src/analytics/segment.ts +85 -0
  375. package/src/analytics/vercel.ts +50 -0
  376. package/src/astro/adapter-root.ts +7 -9
  377. package/src/astro/component-slots.ts +131 -91
  378. package/src/astro/generate.ts +129 -361
  379. package/src/astro/integration.ts +2 -6
  380. package/src/astro/module-types.ts +1 -1
  381. package/src/astro/pages.ts +13 -101
  382. package/src/astro/render-deps.ts +379 -0
  383. package/src/astro/runtime-deps.ts +201 -0
  384. package/src/astro/templates.ts +575 -562
  385. package/src/audit/agent.ts +24 -0
  386. package/src/audit/catalog.ts +2 -2
  387. package/src/audit/checks/assets.ts +2 -2
  388. package/src/audit/checks/dns-aid.ts +1 -1
  389. package/src/audit/checks/duplicates.ts +3 -1
  390. package/src/audit/checks/i18n.ts +1 -1
  391. package/src/audit/checks/indexability.ts +7 -7
  392. package/src/audit/checks/links.ts +2 -2
  393. package/src/audit/checks/llms.ts +6 -6
  394. package/src/audit/checks/network.ts +3 -3
  395. package/src/audit/checks/og-image.ts +2 -2
  396. package/src/audit/checks/robots.ts +1 -1
  397. package/src/audit/checks/sitemap.ts +2 -2
  398. package/src/audit/checks/social.ts +1 -1
  399. package/src/audit/run.ts +4 -3
  400. package/src/audit/terms.ts +31 -0
  401. package/src/audit/url.ts +13 -13
  402. package/src/blume-modules.d.ts +2 -2
  403. package/src/cli/command-meta.ts +10 -0
  404. package/src/cli/commands/audit.ts +40 -21
  405. package/src/cli/commands/build.ts +71 -316
  406. package/src/cli/commands/check.ts +1 -0
  407. package/src/cli/commands/dev.ts +40 -1
  408. package/src/cli/commands/doctor.ts +92 -14
  409. package/src/cli/commands/eject.ts +44 -7
  410. package/src/cli/commands/eval.ts +5 -5
  411. package/src/cli/commands/init.ts +161 -41
  412. package/src/cli/commands/migrate.ts +121 -0
  413. package/src/cli/commands/preview.ts +7 -1
  414. package/src/cli/commands/translate.ts +5 -5
  415. package/src/cli/commands/upgrade.ts +141 -0
  416. package/src/cli/commands/version.ts +57 -44
  417. package/src/cli/eject-scripts.ts +121 -6
  418. package/src/cli/index.ts +11 -1
  419. package/src/cli/init/install.ts +70 -0
  420. package/src/cli/init/questions.ts +7 -0
  421. package/src/cli/init/scaffold.ts +459 -75
  422. package/src/cli/lazy-command.ts +37 -1
  423. package/src/cli/prepare.ts +40 -6
  424. package/src/cli/required-secrets.ts +39 -12
  425. package/src/cli/unknown-flags.ts +266 -0
  426. package/src/cli/yarn-pnp.ts +52 -0
  427. package/src/components/content/AccordionItem.astro +7 -1
  428. package/src/components/content/Card.astro +4 -3
  429. package/src/components/content/ColorItem.astro +22 -4
  430. package/src/components/content/GithubInfo.astro +2 -2
  431. package/src/components/content/Prompt.astro +25 -25
  432. package/src/components/content/Tabs.astro +3 -1
  433. package/src/components/content/Tile.astro +2 -3
  434. package/src/components/content/Tooltip.astro +57 -9
  435. package/src/components/content/content-strings.ts +34 -0
  436. package/src/components/content/diff.ts +1 -1
  437. package/src/components/content/mermaid-element.ts +13 -2
  438. package/src/components/content/prompt-markdown.ts +292 -0
  439. package/src/components/content/tooltip-id.ts +41 -0
  440. package/src/components/copy-feedback.ts +1 -1
  441. package/src/components/islands/{AskAI.astro → Assistant.astro} +10 -10
  442. package/src/components/islands/{ask-ai.tsx → assistant.tsx} +23 -23
  443. package/src/components/islands/hooks.ts +36 -11
  444. package/src/components/layout/Analytics.astro +21 -79
  445. package/src/components/layout/Banner.astro +3 -1
  446. package/src/components/layout/DiscoveryLinks.astro +69 -0
  447. package/src/components/layout/Header.astro +93 -30
  448. package/src/components/layout/LanguageSwitcher.astro +4 -1
  449. package/src/components/layout/Logo.astro +23 -2
  450. package/src/components/layout/NavSelector.astro +13 -2
  451. package/src/components/layout/NavTree.astro +12 -3
  452. package/src/components/layout/NavTreeScript.astro +17 -3
  453. package/src/components/layout/PageActions.astro +3 -3
  454. package/src/components/layout/PageFeedback.astro +11 -1
  455. package/src/components/layout/PageLayout.astro +58 -11
  456. package/src/components/layout/Pagination.astro +7 -7
  457. package/src/components/layout/ReferenceLayout.astro +22 -13
  458. package/src/components/layout/RootLayout.astro +75 -72
  459. package/src/components/layout/Search.astro +43 -19
  460. package/src/components/layout/WebMcp.astro +1 -1
  461. package/src/components/layout/analytics-client.ts +73 -16
  462. package/src/components/layout/drawer-inert.ts +113 -15
  463. package/src/components/layout/dropdown-clamp.ts +105 -0
  464. package/src/components/layout/head-scripts.ts +15 -6
  465. package/src/components/layout/nav-utils.ts +17 -0
  466. package/src/components/layout/search/algolia.ts +8 -8
  467. package/src/components/layout/search/orama-cloud.ts +9 -7
  468. package/src/components/layout/search/typesense.ts +11 -17
  469. package/src/components/openapi/MessageComposer.astro +2 -2
  470. package/src/components/openapi/Playground.astro +2 -2
  471. package/src/components/openapi/description.ts +2 -2
  472. package/src/components/openapi/playground-client.ts +79 -10
  473. package/src/core/changelog-index.ts +24 -0
  474. package/src/core/code-fences.ts +1 -1
  475. package/src/core/component-overrides.ts +399 -154
  476. package/src/core/config-input.ts +201 -566
  477. package/src/core/config.ts +131 -42
  478. package/src/core/custom-pages.ts +105 -0
  479. package/src/core/data.ts +34 -15
  480. package/src/core/define-components.ts +12 -9
  481. package/src/core/deployment-env.ts +18 -74
  482. package/src/core/diagnostics.ts +14 -7
  483. package/src/core/graph.ts +69 -25
  484. package/src/core/i18n-ui.ts +41 -11
  485. package/src/core/i18n.ts +17 -0
  486. package/src/core/includes.ts +156 -38
  487. package/src/core/last-modified.ts +6 -11
  488. package/src/core/links.ts +166 -2
  489. package/src/core/manifest.ts +10 -3
  490. package/src/core/navigation.ts +115 -16
  491. package/src/core/new-tab.ts +35 -0
  492. package/src/core/node-require.ts +21 -0
  493. package/src/core/project-graph.ts +42 -26
  494. package/src/core/project.ts +9 -4
  495. package/src/core/request-body.ts +61 -0
  496. package/src/core/safe-href.ts +28 -0
  497. package/src/core/safe-links.ts +68 -0
  498. package/src/core/schema.ts +528 -745
  499. package/src/core/server-features.ts +10 -11
  500. package/src/core/sources/assets.ts +47 -11
  501. package/src/core/sources/collection.ts +67 -0
  502. package/src/core/sources/contentful-rich-text.ts +285 -0
  503. package/src/core/sources/contentful.ts +173 -0
  504. package/src/core/sources/github-releases.ts +51 -1
  505. package/src/core/sources/json.ts +71 -0
  506. package/src/core/sources/lexical.ts +195 -0
  507. package/src/core/sources/lower.ts +226 -0
  508. package/src/core/sources/normalize.ts +52 -2
  509. package/src/core/sources/notion.ts +39 -28
  510. package/src/core/sources/payload.ts +135 -0
  511. package/src/core/sources/portable-text.ts +11 -16
  512. package/src/core/sources/remote.ts +226 -0
  513. package/src/core/sources/resolve.ts +104 -166
  514. package/src/core/sources/sanity.ts +16 -49
  515. package/src/core/sources/strapi-blocks.ts +124 -0
  516. package/src/core/sources/strapi.ts +191 -0
  517. package/src/core/sources/types.ts +12 -1
  518. package/src/core/types.ts +15 -1
  519. package/src/core/ui-packs/ar.ts +10 -7
  520. package/src/core/ui-packs/bg.ts +10 -7
  521. package/src/core/ui-packs/bn.ts +10 -7
  522. package/src/core/ui-packs/ca.ts +7 -6
  523. package/src/core/ui-packs/cs.ts +10 -7
  524. package/src/core/ui-packs/da.ts +10 -7
  525. package/src/core/ui-packs/de.ts +10 -7
  526. package/src/core/ui-packs/el.ts +7 -6
  527. package/src/core/ui-packs/es.ts +7 -6
  528. package/src/core/ui-packs/fa.ts +10 -7
  529. package/src/core/ui-packs/fi.ts +10 -7
  530. package/src/core/ui-packs/fr.ts +7 -6
  531. package/src/core/ui-packs/he.ts +10 -7
  532. package/src/core/ui-packs/hi.ts +10 -7
  533. package/src/core/ui-packs/hr.ts +10 -7
  534. package/src/core/ui-packs/hu.ts +10 -7
  535. package/src/core/ui-packs/id.ts +10 -7
  536. package/src/core/ui-packs/it.ts +7 -6
  537. package/src/core/ui-packs/ja.ts +7 -6
  538. package/src/core/ui-packs/ko.ts +7 -6
  539. package/src/core/ui-packs/nl.ts +10 -7
  540. package/src/core/ui-packs/no.ts +10 -7
  541. package/src/core/ui-packs/pl.ts +10 -7
  542. package/src/core/ui-packs/pt-br.ts +7 -6
  543. package/src/core/ui-packs/pt.ts +7 -6
  544. package/src/core/ui-packs/ro.ts +10 -7
  545. package/src/core/ui-packs/ru.ts +10 -7
  546. package/src/core/ui-packs/sk.ts +10 -7
  547. package/src/core/ui-packs/sr.ts +10 -7
  548. package/src/core/ui-packs/sv.ts +10 -7
  549. package/src/core/ui-packs/th.ts +7 -6
  550. package/src/core/ui-packs/tr.ts +10 -7
  551. package/src/core/ui-packs/uk.ts +10 -7
  552. package/src/core/ui-packs/vi.ts +7 -6
  553. package/src/core/ui-packs/zh-tw.ts +7 -6
  554. package/src/core/ui-packs/zh.ts +7 -6
  555. package/src/core/unrecognized-keys.ts +10 -0
  556. package/src/core/version-cut.ts +116 -8
  557. package/src/deploy/adapter-output.ts +57 -97
  558. package/src/deploy/adapters/cloudflare.ts +39 -0
  559. package/src/deploy/adapters/index.ts +41 -0
  560. package/src/deploy/adapters/netlify.ts +34 -0
  561. package/src/deploy/adapters/node.ts +34 -0
  562. package/src/deploy/adapters/registry.ts +127 -0
  563. package/src/deploy/adapters/types.ts +92 -0
  564. package/src/deploy/adapters/vercel.ts +35 -0
  565. package/src/deploy/artifacts.ts +55 -27
  566. package/src/deploy/cloudflare-negotiation.ts +179 -100
  567. package/src/deploy/function-bundle.ts +18 -3
  568. package/src/deploy/headers.ts +124 -23
  569. package/src/deploy/node-headers.ts +198 -0
  570. package/src/deploy/platforms/cloudflare.ts +312 -0
  571. package/src/deploy/platforms/index.ts +67 -0
  572. package/src/deploy/platforms/netlify.ts +52 -0
  573. package/src/deploy/platforms/node.ts +40 -0
  574. package/src/deploy/platforms/paths.ts +42 -0
  575. package/src/deploy/platforms/static.ts +29 -0
  576. package/src/deploy/platforms/types.ts +112 -0
  577. package/src/deploy/platforms/vercel.ts +193 -0
  578. package/src/deploy/redirects.ts +30 -18
  579. package/src/deploy/robots.ts +3 -3
  580. package/src/deploy/rss.ts +2 -2
  581. package/src/deploy/sitemap.ts +2 -2
  582. package/src/deploy/vercel-negotiation.ts +2 -2
  583. package/src/eval/agents.ts +32 -1
  584. package/src/eval/findings.ts +19 -11
  585. package/src/eval/report.ts +10 -2
  586. package/src/markdown/external-links.ts +65 -0
  587. package/src/markdown/include.ts +45 -27
  588. package/src/markdown/index.ts +34 -9
  589. package/src/markdown/inline-code.ts +12 -6
  590. package/src/markdown/relative-links.ts +324 -0
  591. package/src/markdown/themes.ts +3 -3
  592. package/src/migrate/migrate.ts +149 -0
  593. package/src/openapi/parse.ts +64 -16
  594. package/src/openapi/proxy.ts +62 -10
  595. package/src/openapi/references.ts +147 -147
  596. package/src/openapi/render-mdx.ts +32 -10
  597. package/src/openapi/scalar.ts +15 -13
  598. package/src/openapi/sentence.ts +14 -0
  599. package/src/openapi/source.ts +37 -21
  600. package/src/openapi/spec-dependency-error.ts +15 -0
  601. package/src/reference/asyncapi.ts +83 -0
  602. package/src/reference/graphql.ts +89 -0
  603. package/src/reference/index.ts +40 -0
  604. package/src/reference/openapi.ts +83 -0
  605. package/src/reference/options.ts +201 -0
  606. package/src/reference/scalar.ts +136 -0
  607. package/src/reference/schema.ts +88 -0
  608. package/src/registry/eject.ts +109 -53
  609. package/src/search/adapters/algolia.ts +46 -0
  610. package/src/search/adapters/flexsearch.ts +29 -0
  611. package/src/search/adapters/index.ts +39 -0
  612. package/src/search/adapters/mixedbread.ts +39 -0
  613. package/src/search/adapters/orama-cloud.ts +51 -0
  614. package/src/search/adapters/orama.ts +24 -0
  615. package/src/search/adapters/pagefind.ts +27 -0
  616. package/src/search/adapters/registry.ts +131 -0
  617. package/src/search/adapters/types.ts +46 -0
  618. package/src/search/adapters/typesense.ts +57 -0
  619. package/src/search/build.ts +8 -4
  620. package/src/search/documents.ts +8 -2
  621. package/src/search/orama-index.ts +1 -1
  622. package/src/search/sync/algolia.ts +12 -11
  623. package/src/search/sync/index.ts +35 -20
  624. package/src/search/sync/orama-cloud.ts +12 -9
  625. package/src/search/sync/typesense.ts +15 -13
  626. package/src/sources/contentful.ts +63 -0
  627. package/src/sources/custom.ts +38 -0
  628. package/src/sources/filesystem.ts +51 -0
  629. package/src/sources/github-releases.ts +52 -0
  630. package/src/sources/index.ts +60 -0
  631. package/src/sources/mdx-remote.ts +76 -0
  632. package/src/sources/notion.ts +63 -0
  633. package/src/sources/obsidian.ts +38 -0
  634. package/src/sources/payload.ts +60 -0
  635. package/src/sources/registry.ts +182 -0
  636. package/src/sources/sanity.ts +66 -0
  637. package/src/sources/shared.ts +52 -0
  638. package/src/sources/strapi.ts +58 -0
  639. package/src/theme/entry.ts +24 -2
  640. package/src/translate/report.ts +40 -7
  641. package/src/upgrade/upgrade.ts +499 -0
  642. package/dist/cli/chunk-0qymqwzz.js +0 -164
  643. package/dist/cli/chunk-0qymqwzz.js.map +0 -15
  644. package/dist/cli/chunk-1jefwnfs.js.map +0 -48
  645. package/dist/cli/chunk-2mzebbbz.js +0 -69
  646. package/dist/cli/chunk-2z47ypj8.js.map +0 -11
  647. package/dist/cli/chunk-4x36ddpw.js.map +0 -11
  648. package/dist/cli/chunk-5093q3n7.js +0 -68
  649. package/dist/cli/chunk-5093q3n7.js.map +0 -10
  650. package/dist/cli/chunk-5qk08vmp.js.map +0 -11
  651. package/dist/cli/chunk-7s8hm3b6.js +0 -5347
  652. package/dist/cli/chunk-7s8hm3b6.js.map +0 -58
  653. package/dist/cli/chunk-8cjtbafj.js.map +0 -13
  654. package/dist/cli/chunk-97r59kpr.js +0 -381
  655. package/dist/cli/chunk-97r59kpr.js.map +0 -12
  656. package/dist/cli/chunk-ahnw3kxw.js.map +0 -15
  657. package/dist/cli/chunk-b27xqwn9.js.map +0 -10
  658. package/dist/cli/chunk-bf6bt1xt.js +0 -185
  659. package/dist/cli/chunk-bf6bt1xt.js.map +0 -11
  660. package/dist/cli/chunk-cjtn640a.js.map +0 -10
  661. package/dist/cli/chunk-ct47dqpx.js.map +0 -11
  662. package/dist/cli/chunk-esphfr8p.js +0 -107
  663. package/dist/cli/chunk-esphfr8p.js.map +0 -11
  664. package/dist/cli/chunk-ex56aa81.js +0 -1016
  665. package/dist/cli/chunk-ex56aa81.js.map +0 -13
  666. package/dist/cli/chunk-garjf5z9.js +0 -30
  667. package/dist/cli/chunk-garjf5z9.js.map +0 -10
  668. package/dist/cli/chunk-js7saxwm.js +0 -1045
  669. package/dist/cli/chunk-js7saxwm.js.map +0 -22
  670. package/dist/cli/chunk-k79xp7av.js.map +0 -39
  671. package/dist/cli/chunk-ps4m1xh4.js.map +0 -15
  672. package/dist/cli/chunk-q4rae3bg.js +0 -60
  673. package/dist/cli/chunk-q4rae3bg.js.map +0 -10
  674. package/dist/cli/chunk-rz9jmfhz.js +0 -108
  675. package/dist/cli/chunk-rz9jmfhz.js.map +0 -10
  676. package/dist/cli/chunk-vh9w1sgp.js +0 -73
  677. package/dist/cli/chunk-vh9w1sgp.js.map +0 -10
  678. package/dist/cli/chunk-vrfp10qk.js +0 -81
  679. package/dist/cli/chunk-vrfp10qk.js.map +0 -10
  680. package/dist/cli/chunk-yg63d42r.js.map +0 -34
  681. package/docs/advanced/api-reference.mdx +0 -240
  682. package/docs/configuration/ask-ai.mdx +0 -256
  683. package/docs/reference/cli.mdx +0 -197
  684. package/skills/blume-migrate/assets/oxfmt@0.55.0.patch +0 -20
  685. package/src/components/content/changelog-element.ts +0 -69
  686. package/src/search/providers.ts +0 -91
  687. /package/dist/cli/{chunk-vacwm2hv.js.map → chunk-27g6wdth.js.map} +0 -0
  688. /package/dist/cli/{chunk-dwgcp5sm.js.map → chunk-ce574jw2.js.map} +0 -0
  689. /package/dist/cli/{chunk-e7f42gdj.js.map → chunk-fxypxtvm.js.map} +0 -0
  690. /package/dist/cli/{chunk-nn13znc2.js.map → chunk-zxcczpyx.js.map} +0 -0
@@ -1,115 +1,78 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- getBlumeVersion,
4
- BlumeError,
5
- diagnosticsFromIssues,
6
- diagnosticsFromZod
7
- } from "./index.js";
8
- import {
9
- applyDeploymentEnv,
10
- slugify,
11
3
  blumeReferences,
12
- createModuleLoader,
13
- resolveProjectContext,
4
+ frontmatter_default,
5
+ resolveDocsCollection,
6
+ CHANGELOG_INDEX_ROUTE,
7
+ hasChangelogIndex,
8
+ discoverPages,
9
+ customStaticRoutes,
10
+ nextFenceState,
11
+ hashText,
12
+ pollingWatch,
13
+ snapshotCache,
14
+ loadWithCache,
15
+ resolveFallbackLocale,
16
+ localizeRoute,
17
+ localizeInternalPath,
18
+ i18nDiagnostics,
14
19
  PUBLIC_API_URL,
15
- UI_PACKS,
16
20
  pageMetaSchema,
17
21
  folderMetaSchema,
22
+ versionizeRoute,
23
+ versionsDiagnostics,
24
+ slugify,
25
+ slugifyPath,
26
+ extractHeadings,
27
+ strippedLineOffset,
28
+ isStringValue,
29
+ placeEntryRef,
30
+ resolveEntryRoute,
31
+ localizedRoute,
32
+ normalizeEntry,
33
+ hasIncludeStatements,
34
+ expandIncludes,
35
+ parseYouTubeId,
36
+ nodeRequire,
37
+ escapeMarkdownText,
38
+ renderInline,
39
+ renderLink,
40
+ headingPrefix,
41
+ blockquote,
42
+ listItem,
43
+ indent,
44
+ codeFence,
45
+ image,
46
+ writesMdx,
47
+ unsupported,
48
+ joinBlocks,
49
+ markdownDocument,
50
+ absoluteUrl,
51
+ neutralizeUnsafeLinks,
52
+ columnsPrefix,
53
+ openApiSource,
54
+ createModuleLoader,
55
+ resolveProjectContext,
18
56
  loadConfig
19
- } from "./chunk-7s8hm3b6.js";
57
+ } from "./chunk-qkqwkpte.js";
58
+ import {
59
+ getBlumeVersion,
60
+ BlumeError,
61
+ diagnosticsFromZod
62
+ } from "./index.js";
20
63
  import {
21
64
  trimChar,
22
65
  isInternalPath,
23
66
  withBasePath,
24
67
  stripBasePath
25
- } from "./chunk-2mzebbbz.js";
26
-
27
- // src/core/i18n.ts
28
- var KNOWN_LOCALES = new Set([...Object.keys(UI_PACKS), "en"].map((code) => code.toLowerCase()));
29
- var i18nEnabled = (config) => config.i18n !== undefined;
30
- var localeCodes = (i18n) => i18n.locales.map((locale) => locale.code);
31
- var resolveFallbackLocale = (i18n) => {
32
- if (i18n.fallbackLocale === null) {
33
- return null;
34
- }
35
- return i18n.fallbackLocale ?? i18n.defaultLocale;
36
- };
37
- var localePrefix = (code, i18n) => code === i18n.defaultLocale && i18n.hideDefaultLocalePrefix ? "" : `/${code}`;
38
- var localizeRoute = (logicalRoute, code, i18n) => {
39
- const prefix = localePrefix(code, i18n);
40
- if (!prefix) {
41
- return logicalRoute;
42
- }
43
- return logicalRoute === "/" ? prefix : `${prefix}${logicalRoute}`;
44
- };
45
- var detectLocale = (parts, i18n) => {
46
- const first = parts[0]?.toLowerCase();
47
- const matched = i18n.locales.find((locale) => locale.code !== i18n.defaultLocale && locale.code.toLowerCase() === first);
48
- if (matched) {
49
- return { locale: matched.code, rest: parts.slice(1) };
50
- }
51
- return { locale: i18n.defaultLocale, rest: parts };
52
- };
53
- var localePlacement = (rel, ext, i18n) => {
54
- const base = rel.slice(0, rel.length - ext.length);
55
- if (base.endsWith(".$")) {
56
- const shared = `${base.slice(0, -2)}${ext}`;
57
- return {
58
- locales: i18n.locales.map((locale) => locale.code),
59
- navPath: i18n.parser === "dir" ? detectLocale(shared.split("/"), i18n).rest.join("/") : shared
60
- };
61
- }
62
- if (i18n.parser === "dot") {
63
- const lastDot = base.lastIndexOf(".");
64
- if (lastDot > base.lastIndexOf("/")) {
65
- const suffix = base.slice(lastDot + 1).toLowerCase();
66
- const matched = i18n.locales.find((locale) => locale.code.toLowerCase() === suffix);
67
- if (matched) {
68
- return {
69
- locales: [matched.code],
70
- navPath: `${base.slice(0, lastDot)}${ext}`
71
- };
72
- }
73
- }
74
- return { locales: [i18n.defaultLocale], navPath: rel };
75
- }
76
- const { locale, rest } = detectLocale(rel.split("/"), i18n);
77
- return { locales: [locale], navPath: rest.join("/") };
78
- };
79
- var localeTargetPath = (rel, ext, locale, i18n) => {
80
- if (i18n.parser === "dot") {
81
- let base = rel.slice(0, rel.length - ext.length);
82
- const lastDot = base.lastIndexOf(".");
83
- if (lastDot > base.lastIndexOf("/")) {
84
- const suffix = base.slice(lastDot + 1).toLowerCase();
85
- if (i18n.locales.some((entry) => entry.code.toLowerCase() === suffix)) {
86
- base = base.slice(0, lastDot);
87
- }
88
- }
89
- return `${base}.${locale}${ext}`;
90
- }
91
- return `${locale}/${rel}`;
92
- };
93
- var i18nDiagnostics = (pages, i18n, versions) => {
94
- const configured = new Set(i18n.locales.map((locale) => locale.code.toLowerCase()));
95
- const versionDirs = new Set(versions?.archived.map((version) => version.id));
96
- const seen = new Set;
97
- const diagnostics = [];
98
- for (const page of pages) {
99
- const parts = page.source.ref.split("/");
100
- const first = (parts[0] && versionDirs.has(parts[0]) ? parts[1] : parts[0])?.toLowerCase();
101
- if (first && !seen.has(first) && KNOWN_LOCALES.has(first) && !configured.has(first)) {
102
- seen.add(first);
103
- diagnostics.push({
104
- code: "BLUME_I18N_UNCONFIGURED_LOCALE",
105
- message: `Folder "${first}/" looks like a locale, but "${first}" is not in i18n.locales — its pages are treated as "${i18n.defaultLocale}" content at /${first}/….`,
106
- severity: "warning",
107
- suggestion: `Add { code: "${first}", label: "…" } to i18n.locales, or rename the folder if it isn't a translation.`
108
- });
109
- }
110
- }
111
- return diagnostics;
112
- };
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";
113
76
 
114
77
  // src/core/manifest.ts
115
78
  var versionAlternateKey = (versionKey, locale) => `${versionKey}\x00${locale}`;
@@ -131,7 +94,7 @@ var buildFallbackRoutes = (graph, i18n, alternatesByKey, basePath, versionAltern
131
94
  if (!fallback) {
132
95
  return [];
133
96
  }
134
- const fallbackPages = new Map(graph.pages.flatMap((page) => page.locale === fallback ? [[page.translationKey, page]] : []));
97
+ const fallbackPages = new Map(graph.pages.flatMap((page) => page.locale === fallback && !page.monolingual ? [[page.translationKey, page]] : []));
135
98
  const routes = [];
136
99
  for (const { code } of i18n.locales) {
137
100
  if (code === fallback) {
@@ -161,6 +124,7 @@ var buildFallbackRoutes = (graph, i18n, alternatesByKey, basePath, versionAltern
161
124
  indexable: false,
162
125
  lastModified: source.lastModified,
163
126
  locale: code,
127
+ monolingual: source.monolingual,
164
128
  path,
165
129
  source: source.source,
166
130
  sourcePath: source.sourcePath,
@@ -174,7 +138,7 @@ var buildFallbackRoutes = (graph, i18n, alternatesByKey, basePath, versionAltern
174
138
  };
175
139
  var buildManifest = (options) => {
176
140
  const { context, config, graph } = options;
177
- const searchEnabled = config.search.provider !== "none";
141
+ const searchEnabled = config.search.provider.mode !== "none";
178
142
  const { i18n } = config;
179
143
  const alternatesByKey = new Map;
180
144
  if (i18n) {
@@ -203,6 +167,7 @@ var buildManifest = (options) => {
203
167
  indexable: searchEnabled && contentIndexable(page, config),
204
168
  lastModified: page.lastModified,
205
169
  locale: page.locale,
170
+ monolingual: page.monolingual,
206
171
  path: page.route,
207
172
  source: page.source,
208
173
  sourcePath: page.sourcePath,
@@ -223,7 +188,7 @@ var buildManifest = (options) => {
223
188
  return {
224
189
  blumeVersion: getBlumeVersion(),
225
190
  contentRoot: context.contentRoot,
226
- output: config.deployment.output,
191
+ output: config.deployment.options.output,
227
192
  projectRoot: context.root,
228
193
  routes,
229
194
  version: MANIFEST_VERSION
@@ -231,7 +196,7 @@ var buildManifest = (options) => {
231
196
  };
232
197
 
233
198
  // src/core/project-graph.ts
234
- import { isAbsolute as isAbsolute5, relative as relative6 } from "pathe";
199
+ import { isAbsolute as isAbsolute3, relative as relative5 } from "pathe";
235
200
 
236
201
  // src/theme/icon-kind.ts
237
202
  var IMAGE_ICON = /^(?:https?:\/\/|data:image\/|\/|\.{1,2}\/)|\.(?:avif|gif|jpe?g|png|svg|webp)$/iu;
@@ -428,7 +393,46 @@ var GROUP_FOLDER = /^\((?<label>.+)\)$/u;
428
393
  var WORD_SPLIT = /[-_]/u;
429
394
  var isUnderPath = (route, base) => base === "/" || route === base || route.startsWith(`${base}/`);
430
395
  var isRootTab = (tab, root) => isUnderPath(root, tab.path);
431
- var humanize = (segment) => segment.replace(NUMERIC_PREFIX, "").split(WORD_SPLIT).filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
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(" ");
432
436
  var numericOrder = (segment) => {
433
437
  const value = segment.match(NUMERIC_PREFIX)?.groups?.order;
434
438
  return value ? Math.trunc(Number(value)) : Number.POSITIVE_INFINITY;
@@ -515,7 +519,7 @@ var applyFolderMeta = (group, folderMeta, sharedMeta, metaPrefix, sharedMetaPref
515
519
  }
516
520
  };
517
521
  var indexTitleMismatchDiagnostic = (page, folderPath, folderMeta, sharedMeta, metaPrefix, sharedMetaPrefix) => {
518
- if (!page.meta.title || page.fallback || folderPath === "") {
522
+ if (!(page.meta.title && page.meta.sidebar.hidden) || page.fallback || folderPath === "") {
519
523
  return;
520
524
  }
521
525
  const meta = folderMeta.get(metaKey(folderPath, metaPrefix)) ?? sharedMeta.get(metaKey(folderPath, sharedMetaPrefix));
@@ -704,7 +708,7 @@ var buildFileSystemSidebar = (pages, folderMeta, sharedMeta, metaPrefix, sharedM
704
708
  deprecated: page.meta.deprecated || undefined,
705
709
  description: page.description,
706
710
  file: page.sourcePath,
707
- icon: page.meta.sidebar.icon,
711
+ icon: sidebarIcon(page),
708
712
  key: segmentKey(stem),
709
713
  kind: "page",
710
714
  label: page.meta.sidebar.label ?? page.title,
@@ -747,7 +751,7 @@ var configItemToNode = (item, byRoute, basePath) => {
747
751
  badge: page.meta.sidebar.badge,
748
752
  deprecated: page.meta.deprecated || undefined,
749
753
  description: page.description,
750
- icon: page.meta.sidebar.icon,
754
+ icon: sidebarIcon(page),
751
755
  kind: "page",
752
756
  label: page.meta.sidebar.label ?? page.title,
753
757
  pageId: page.id,
@@ -802,7 +806,10 @@ var buildConfigSidebar = (items, byRoute, display, basePath) => {
802
806
  }
803
807
  return nodes;
804
808
  };
805
- var resolveTabHref = (sidebar, path) => {
809
+ var resolveTabHref = (sidebar, path, extraRoutes, basePath) => {
810
+ if (extraRoutes.has(path)) {
811
+ return path;
812
+ }
806
813
  let first;
807
814
  const walk = (nodes) => {
808
815
  for (const node of nodes) {
@@ -819,10 +826,14 @@ var resolveTabHref = (sidebar, path) => {
819
826
  }
820
827
  return false;
821
828
  };
822
- return walk(sidebar) ? path : first ?? path;
829
+ if (walk(sidebar)) {
830
+ return path;
831
+ }
832
+ const bare = stripBasePath(basePath, path);
833
+ return extraRoutes.has(bare) ? bare : first ?? path;
823
834
  };
824
- var withTabHrefs = (tabs, sidebar) => tabs.map((tab) => {
825
- const href = tab.href ?? resolveTabHref(sidebar, tab.path);
835
+ var withTabHrefs = (tabs, sidebar, extraRoutes, basePath) => tabs.map((tab) => {
836
+ const href = tab.href ?? resolveTabHref(sidebar, tab.path, extraRoutes, basePath);
826
837
  return href === tab.path ? tab : { ...tab, href };
827
838
  });
828
839
  var rebaseNavChrome = (basePath, options) => {
@@ -869,6 +880,7 @@ var buildNavigation = (pages, options) => {
869
880
  const metaPrefix = options.metaPrefix ?? "";
870
881
  const sharedMetaPrefix = options.sharedMetaPrefix ?? "";
871
882
  const sharedFolderMeta = options.sharedFolderMeta ?? new Map;
883
+ const extraRoutes = options.extraRoutes ?? new Set;
872
884
  const { actions, cta, featured, selectors, tabs } = rebaseNavChrome(basePath, options);
873
885
  const byRoute = new Map(pages.map((page) => [
874
886
  options.refByLogical ? page.translationKey : page.route,
@@ -894,7 +906,7 @@ var buildNavigation = (pages, options) => {
894
906
  root: rootTabPath,
895
907
  selectors,
896
908
  sidebar,
897
- tabs: withTabHrefs(tabs, sidebar)
909
+ tabs: withTabHrefs(tabs, sidebar, extraRoutes, basePath)
898
910
  };
899
911
  }
900
912
  const sidebar = buildFileSystemSidebar(pages, options.folderMeta, sharedFolderMeta, metaPrefix, sharedMetaPrefix, display, new Set(tabs.flatMap((tab) => isRootTab(tab, rootTabPath) ? [] : [tab.path])), diagnostics);
@@ -905,48 +917,10 @@ var buildNavigation = (pages, options) => {
905
917
  root: rootTabPath,
906
918
  selectors,
907
919
  sidebar,
908
- tabs: withTabHrefs(tabs, sidebar)
920
+ tabs: withTabHrefs(tabs, sidebar, extraRoutes, basePath)
909
921
  };
910
922
  };
911
923
 
912
- // src/core/versions.ts
913
- var VERSION_LIKE = /^v\d/iu;
914
- var detectVersion = (parts, versions) => {
915
- const [first] = parts;
916
- if (first && versions.archived.some((version) => version.id === first)) {
917
- return { rest: parts.slice(1), version: first };
918
- }
919
- return { rest: parts, version: "" };
920
- };
921
- var detectVersionRef = (ref, versions) => {
922
- const { version, rest } = detectVersion(ref.split("/"), versions);
923
- return { rest: rest.join("/"), version };
924
- };
925
- var versionizeRoute = (logicalRoute, version) => {
926
- if (!version) {
927
- return logicalRoute;
928
- }
929
- return logicalRoute === "/" ? `/${version}` : `/${version}${logicalRoute}`;
930
- };
931
- var versionsDiagnostics = (pages, versions) => {
932
- const configured = new Set(versions.archived.map((version) => version.id));
933
- const seen = new Set;
934
- const diagnostics = [];
935
- for (const page of pages) {
936
- const [first] = page.source.ref.split("/");
937
- if (first && !seen.has(first) && VERSION_LIKE.test(first) && !configured.has(first)) {
938
- seen.add(first);
939
- diagnostics.push({
940
- code: "BLUME_VERSIONS_UNCONFIGURED_VERSION",
941
- message: `Folder "${first}/" looks like a version, but "${first}" is not in versions.archived — its pages are treated as current-version content at /${first}/….`,
942
- severity: "warning",
943
- suggestion: `Add { id: "${first}" } to versions.archived, or rename the folder if it isn't a snapshot.`
944
- });
945
- }
946
- }
947
- return diagnostics;
948
- };
949
-
950
924
  // src/core/graph.ts
951
925
  var collectRoutes = (pages) => {
952
926
  const routes = new Map;
@@ -1001,7 +975,26 @@ var resolveTabLabels = (tabs, locale, defaultLocale) => (tabs ?? []).map((tab) =
1001
975
  label: resolveLabel(tab.label, locale, defaultLocale)
1002
976
  }));
1003
977
  var buildLocaleNavigation = (code, pages, fallback, fallbackByKey, options, i18n, diagnostics, version = "") => {
1004
- const localizePath = (path) => path.startsWith("/") && !path.startsWith("//") ? localizeRoute(path, code, i18n) : path;
978
+ const basePath = options.basePath ?? "";
979
+ const real = pages.filter((page) => page.locale === code);
980
+ const localePages = localePagesFor(code, real, fallback, fallbackByKey, i18n, basePath);
981
+ const pageRoutes = [
982
+ ...new Set([...pages, ...localePages].map((page) => page.route))
983
+ ];
984
+ const extraRoutes = options.extraRoutes ?? new Set;
985
+ const serves = (path, section) => {
986
+ if (extraRoutes.has(path)) {
987
+ return true;
988
+ }
989
+ const based = withBasePath(basePath, path);
990
+ const prefix = based === "/" ? "/" : `${based}/`;
991
+ return pageRoutes.some((route) => route === based || section && route.startsWith(prefix));
992
+ };
993
+ const localizeServed = (path, section) => {
994
+ const localized = localizeInternalPath(path, code, i18n);
995
+ return localized === path || serves(localized, section) || !serves(path, section) ? localized : path;
996
+ };
997
+ const localizePath = (path) => localizeServed(path, true);
1005
998
  const tabs = resolveTabLabels(options.navigation.tabs, code, i18n.defaultLocale).map((tab) => {
1006
999
  const localized = {
1007
1000
  ...tab,
@@ -1012,24 +1005,23 @@ var buildLocaleNavigation = (code, pages, fallback, fallbackByKey, options, i18n
1012
1005
  path: localizePath(tab.path)
1013
1006
  };
1014
1007
  if (localized.href) {
1015
- localized.href = localizePath(localized.href);
1008
+ localized.href = localizeServed(localized.href, false);
1016
1009
  }
1017
1010
  return localized;
1018
1011
  });
1019
- const real = pages.filter((page) => page.locale === code);
1020
- const localePages = localePagesFor(code, real, fallback, fallbackByKey, i18n, options.basePath ?? "");
1021
1012
  const localeDir = i18n.parser === "dir" && code !== i18n.defaultLocale ? code : "";
1022
1013
  const localizeHref = (item) => ({
1023
1014
  ...item,
1024
- href: localizePath(item.href)
1015
+ href: localizeServed(item.href, false)
1025
1016
  });
1026
1017
  const { actions, cta, featured } = options.navigation;
1027
- return buildNavigation(localePages, {
1018
+ const navigation = buildNavigation(localePages, {
1028
1019
  actions: actions?.map(localizeHref),
1029
- basePath: options.basePath ?? "",
1020
+ basePath,
1030
1021
  cta: cta ? localizeHref(cta) : null,
1031
1022
  diagnostics,
1032
1023
  display: options.navigation.sidebar.display,
1024
+ extraRoutes: options.extraRoutes,
1033
1025
  featured: featured?.map(localizeHref),
1034
1026
  folderMeta: options.folderMeta,
1035
1027
  localizedRoot: localizeRoute(versionizeRoute("/", version), code, i18n),
@@ -1041,13 +1033,14 @@ var buildLocaleNavigation = (code, pages, fallback, fallbackByKey, options, i18n
1041
1033
  sidebar: version ? undefined : options.navigation.sidebar.items,
1042
1034
  tabs
1043
1035
  });
1036
+ return options.brandHref === undefined ? navigation : { ...navigation, brandHref: localizeServed(options.brandHref, false) };
1044
1037
  };
1045
1038
  var buildI18nNavigation = (pages, options, i18n, diagnostics, version = "") => {
1046
1039
  const fallback = resolveFallbackLocale(i18n);
1047
1040
  const fallbackByKey = new Map;
1048
1041
  if (fallback) {
1049
1042
  for (const page of pages) {
1050
- if (page.locale === fallback) {
1043
+ if (page.locale === fallback && !page.monolingual) {
1051
1044
  fallbackByKey.set(page.translationKey, page);
1052
1045
  }
1053
1046
  }
@@ -1087,6 +1080,7 @@ var buildVersionNavigation = (id, versionPages, options, diagnostics) => {
1087
1080
  cta: options.navigation.cta,
1088
1081
  diagnostics,
1089
1082
  display: options.navigation.sidebar.display,
1083
+ extraRoutes: options.extraRoutes,
1090
1084
  featured: options.navigation.featured,
1091
1085
  folderMeta: options.folderMeta,
1092
1086
  localizedRoot: versionizeRoute("/", id),
@@ -1113,6 +1107,7 @@ var buildContentGraph = (pages, options) => {
1113
1107
  cta: options.navigation.cta,
1114
1108
  diagnostics,
1115
1109
  display: options.navigation.sidebar.display,
1110
+ extraRoutes: options.extraRoutes,
1116
1111
  featured: options.navigation.featured,
1117
1112
  folderMeta: options.folderMeta,
1118
1113
  selectors: options.navigation.selectors,
@@ -1136,980 +1131,116 @@ var buildContentGraph = (pages, options) => {
1136
1131
  };
1137
1132
  };
1138
1133
 
1139
- // src/core/includes.ts
1140
- import { readFile } from "node:fs/promises";
1141
- import { dirname, extname as extname3, join, relative, resolve } from "pathe";
1142
-
1143
- // src/core/code-fences.ts
1144
- var CODE_FENCE = /^(?<run>`{3,}|~{3,})/u;
1145
- var nextFenceState = (line, fence) => {
1146
- const trimmed = line.trimStart();
1147
- const run = trimmed.match(CODE_FENCE)?.groups?.run;
1148
- if (run === undefined) {
1149
- return fence;
1150
- }
1151
- const rest = trimmed.slice(run.length);
1152
- const delimiter = run.startsWith("`") ? "`" : "~";
1153
- if (fence === null) {
1154
- if (delimiter === "`" && rest.includes("`")) {
1155
- return fence;
1156
- }
1157
- return { delimiter, length: run.length };
1158
- }
1159
- return fence.delimiter === delimiter && run.length >= fence.length && rest.trim() === "" ? null : fence;
1160
- };
1161
-
1162
- // src/core/frontmatter.ts
1163
- import baseMatter from "gray-matter";
1164
- import { dump, load } from "js-yaml";
1165
-
1166
- // src/core/yaml.ts
1167
- import {
1168
- binaryTag,
1169
- CORE_SCHEMA,
1170
- mergeTag,
1171
- omapTag,
1172
- pairsTag,
1173
- setTag,
1174
- timestampTag
1175
- } from "js-yaml";
1176
- var YAML_SCHEMA = CORE_SCHEMA.withTags(timestampTag, mergeTag, binaryTag, omapTag, pairsTag, setTag);
1177
-
1178
- // src/core/frontmatter.ts
1179
- var yamlEngine = {
1180
- parse: (input) => load(input, { schema: YAML_SCHEMA }) ?? {},
1181
- stringify: (data) => dump(data)
1182
- };
1183
- var withYamlEngine = (options) => ({
1184
- ...options,
1185
- engines: {
1186
- yaml: yamlEngine,
1187
- ...options?.engines
1188
- }
1189
- });
1190
- var opensWithThematicBreak = (input) => {
1191
- const [first = "", second] = input.split(/\r?\n/u, 2);
1192
- if (!/^-{3}\s*$/u.test(first)) {
1193
- return false;
1194
- }
1195
- if (second === undefined || second.trim() === "") {
1196
- return true;
1197
- }
1198
- return !input.includes(`
1199
- ---`, 1);
1200
- };
1201
- var passthrough = (input) => {
1202
- const file = {
1203
- content: input,
1204
- data: {},
1205
- excerpt: "",
1206
- isEmpty: false,
1207
- language: "",
1208
- matter: "",
1209
- orig: input,
1210
- stringify: () => input
1211
- };
1212
- return file;
1213
- };
1214
- var isStringInput = (input) => typeof input === "string";
1215
- var matter = Object.assign((input, options) => isStringInput(input) && opensWithThematicBreak(input) ? passthrough(input) : baseMatter(input, withYamlEngine(options)), baseMatter, {
1216
- read: (filepath, options) => baseMatter.read(filepath, withYamlEngine(options)),
1217
- stringify: (file, data, options) => baseMatter.stringify(isStringInput(file) ? { content: file } : file, data, withYamlEngine(options))
1218
- });
1219
- var frontmatter_default = matter;
1220
-
1221
- // src/core/sources/normalize.ts
1222
- import { existsSync, readFileSync } from "node:fs";
1223
- import GithubSlugger from "github-slugger";
1224
- import { extname as extname2 } from "pathe";
1225
-
1226
- // src/core/heading-markers.ts
1227
- var TOC_HIDDEN_KEY = "__blumeTocHidden";
1228
- var occupySlug = (slugger, id) => {
1229
- slugger.occurrences[id] ??= 0;
1230
- };
1231
- var MARKER = /\s*(?:\{#(?<curlyId>[^\s}]+)\}|\[(?:#(?<id>[^\s\]]+)|(?<hide>!)?toc)\])\s*$/u;
1232
- var parseHeadingMarkers = (text, isRefDefined) => {
1233
- let remaining = text;
1234
- let id;
1235
- let toc;
1236
- for (let match = MARKER.exec(remaining);match?.groups; match = MARKER.exec(remaining)) {
1237
- const explicitId = match.groups.curlyId ?? match.groups.id;
1238
- const label = explicitId === undefined ? `${match.groups.hide ?? ""}toc` : `#${explicitId}`;
1239
- if (match.groups.curlyId === undefined && isRefDefined?.(label)) {
1240
- break;
1241
- }
1242
- remaining = remaining.slice(0, match.index);
1243
- if (explicitId === undefined) {
1244
- toc ??= match.groups.hide === undefined ? "only" : "hide";
1245
- } else {
1246
- id ??= explicitId;
1247
- }
1248
- }
1249
- return { id, text: remaining, toc };
1250
- };
1251
-
1252
- // src/core/sources/normalize.ts
1253
- var NUMERIC_PREFIX2 = /^\d+[-_.]/u;
1254
- var GROUP_FOLDER2 = /^\((?<label>.+)\)$/u;
1255
- var WORD_SPLIT2 = /[-_]/u;
1256
- var stripNumericPrefix = (segment) => segment.replace(NUMERIC_PREFIX2, "");
1257
- var groupLabel = (segment) => segment.match(GROUP_FOLDER2)?.groups?.label ?? null;
1258
- var slugify2 = (text) => text.normalize("NFC").toLowerCase().trim().replaceAll(/[^\p{L}\p{M}\p{N}\s_-]/gu, "").replaceAll(/[\s_]+/gu, "-").replaceAll(/-+/gu, "-").replaceAll(/^-|-$/gu, "");
1259
- var slugifyPath = (text) => text.split("/").map(slugify2).filter(Boolean).join("/");
1260
- var titleCase = (value) => value.split(WORD_SPLIT2).filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
1261
- var sanitizeSegment = (segment) => segment.replaceAll(/[:\p{Cc}]/gu, "");
1262
- var addRouteSegment = (part, segments, groups) => {
1263
- if (part === "") {
1264
- return;
1265
- }
1266
- const group = groupLabel(part);
1267
- if (group !== null) {
1268
- groups.push(group);
1269
- return;
1270
- }
1271
- const clean = stripNumericPrefix(part);
1272
- if (clean === "index") {
1273
- return;
1274
- }
1275
- const safe = sanitizeSegment(clean);
1276
- if (safe === "") {
1277
- return;
1278
- }
1279
- segments.push(safe);
1280
- };
1281
- var mapRoute = (relativePath) => {
1282
- const withoutExt = relativePath.slice(0, relativePath.length - extname2(relativePath).length);
1283
- const rawParts = withoutExt.split("/");
1284
- const segments = [];
1285
- const groups = [];
1286
- for (const part of rawParts) {
1287
- addRouteSegment(part, segments, groups);
1288
- }
1289
- const route = segments.length === 0 ? "/" : `/${segments.join("/")}`;
1290
- return { groups, route, segments };
1291
- };
1292
- var ATX_HEADING = /^ {0,3}(?<hashes>#{1,6})\s+(?<text>.+?)(?:\s+#+)?\s*$/u;
1293
- var SETEXT_UNDERLINE = /^ {0,3}(?<marker>=+|-+)\s*$/u;
1294
- var PARAGRAPH_INTERRUPT = /^ {0,3}(?:[-+*][ \t]|\d{1,9}[.)][ \t]|>)/u;
1295
- var THEMATIC_BREAK = /^ {0,3}(?:(?:-[ \t]*){3,}|(?:\*[ \t]*){3,}|(?:_[ \t]*){3,})$/u;
1296
- var FRONT_MATTER_CLOSE = /^(?:-{3}|\.{3})\s*$/u;
1297
- var PROMPT_OPEN = /^<Prompt(?![\w-])/u;
1298
- var PROMPT_CLOSE = /<\/Prompt>/u;
1299
- var linesWithoutFrontMatter = (body) => {
1300
- const lines = body.split(`
1301
- `);
1302
- if (!/^-{3}\s*$/u.test(lines[0] ?? "")) {
1303
- return { lines, offset: 0 };
1304
- }
1305
- if ((lines[1] ?? "").trim() === "") {
1306
- return { lines, offset: 0 };
1307
- }
1308
- const close = lines.findIndex((line, index) => index > 0 && FRONT_MATTER_CLOSE.test(line));
1309
- return close === -1 ? { lines, offset: 0 } : { lines: lines.slice(close + 1), offset: close + 1 };
1310
- };
1311
- var BARE_CURLY_MARKER = /(?<!\\)\{#(?<id>[^\s}]+)\}(?:\s*\[(?:#[^\s\]]+|!?toc)\])*\s*$/u;
1312
- var HTML_ID = /<[a-z][a-z0-9-]*(?:\s[^<>]*?)?\sid=(?:(?<quote>["'])(?<quoted>[^"'<>]+)\k<quote>|\{(?<jsxQuote>["'])(?<jsx>[^"'<>]+)\k<jsxQuote>\}|(?<bare>[^\s"'=<>`{}]+))/gu;
1313
- var HTML_COMMENT = /<!--[\s\S]*?-->/gu;
1314
- var INLINE_CODE = /`[^`]*`/gu;
1315
- var finishPromptTag = (line, start, state) => {
1316
- const end = line.indexOf(">", start);
1317
- if (end === -1) {
1318
- state.promptTag = true;
1319
- return;
1320
- }
1321
- state.promptTag = false;
1322
- if (line[end - 1] === "/" || line.includes("</Prompt>", end)) {
1323
- return;
1324
- }
1325
- state.promptDepth += 1;
1326
- };
1327
- var ESCAPED_PUNCTUATION = /\\(?<char>[!-/:-@[-`{-~])/gu;
1328
- var REF_DEFINITION = /^(?: {0,3}|[ \t]*(?:(?:>|(?:[-*+]|\d{1,9}[.)])[ \t])[ \t]*)+)\[(?<label>[^\]]+)\]:[ \t]*(?<rest>.*)$/u;
1329
- var refDefinitionLabels = (lines) => {
1330
- const labels = new Set;
1331
- let fence = null;
1332
- for (const [index, line] of lines.entries()) {
1333
- const next = nextFenceState(line, fence);
1334
- if (fence !== null || next !== null) {
1335
- fence = next;
1336
- continue;
1337
- }
1338
- const groups = line.match(REF_DEFINITION)?.groups;
1339
- if (groups?.label === undefined || groups.label.trim() === "") {
1340
- continue;
1341
- }
1342
- const destination = groups.rest ?? "";
1343
- if (destination === "" && (lines[index + 1] ?? "").trim() === "") {
1344
- continue;
1134
+ // src/core/last-modified.ts
1135
+ import { execFileSync } from "node:child_process";
1136
+ import { relative } from "pathe";
1137
+ var GIT_LOCATION_VARS = new Set([
1138
+ "GIT_COMMON_DIR",
1139
+ "GIT_DIR",
1140
+ "GIT_INDEX_FILE",
1141
+ "GIT_OBJECT_DIRECTORY",
1142
+ "GIT_PREFIX",
1143
+ "GIT_WORK_TREE"
1144
+ ]);
1145
+ var gitEnv = () => Object.fromEntries(Object.entries(process.env).filter(([key]) => !GIT_LOCATION_VARS.has(key)));
1146
+ var resolveLastModifiedConfig = (value) => value === false ? { enabled: false, source: "git" } : { enabled: true, source: value };
1147
+ var parseGitLog = (output) => {
1148
+ const times = new Map;
1149
+ let current = null;
1150
+ for (const line of output.split(`
1151
+ `)) {
1152
+ if (line.startsWith("\x00")) {
1153
+ current = line.slice(1);
1154
+ } else if (line && current && !times.has(line)) {
1155
+ times.set(line, current);
1345
1156
  }
1346
- labels.add(groups.label.trim().replaceAll(/\s+/gu, " ").toLowerCase());
1347
- }
1348
- return labels;
1349
- };
1350
- var toHeading = (depth, raw, slugger, isRefDefined) => {
1351
- const unescaped = raw.replaceAll(ESCAPED_PUNCTUATION, "$<char>");
1352
- const markers = parseHeadingMarkers(unescaped, isRefDefined);
1353
- const text = markers.text.trim();
1354
- if (text === "" && (markers.id !== undefined || markers.toc !== undefined)) {
1355
- return {
1356
- heading: { depth, slug: slugger.slug(unescaped), text: unescaped },
1357
- pinned: false
1358
- };
1359
- }
1360
- if (markers.id !== undefined) {
1361
- occupySlug(slugger, markers.id);
1362
- return { heading: { depth, slug: markers.id, text }, pinned: true };
1363
- }
1364
- return { heading: { depth, slug: slugger.slug(text), text }, pinned: false };
1365
- };
1366
- var pushHeading = (headings, state, scanned, line) => {
1367
- headings.push(scanned.heading);
1368
- state.sites.push({ line, pinned: scanned.pinned });
1369
- };
1370
- var noteCurlyMarker = (text, line, state) => {
1371
- const id = text.match(BARE_CURLY_MARKER)?.groups?.id;
1372
- if (id !== undefined) {
1373
- state.curlyMarkers.push({ id, line });
1374
1157
  }
1158
+ return times;
1375
1159
  };
1376
- var scanContentLine = (line, state, slugger, headings, isRefDefined) => {
1377
- state.anchorLines.push(line.replaceAll(INLINE_CODE, ""));
1378
- const atx = line.match(ATX_HEADING);
1379
- if (atx?.groups) {
1380
- const depth = atx.groups.hashes?.length ?? 1;
1381
- const text = (atx.groups.text ?? "").trim();
1382
- pushHeading(headings, state, toHeading(depth, text, slugger, isRefDefined), state.line);
1383
- noteCurlyMarker(text, state.line, state);
1384
- state.paragraph = [];
1385
- return;
1386
- }
1387
- const setext = line.match(SETEXT_UNDERLINE);
1388
- if (setext?.groups && state.paragraph.length > 0) {
1389
- const depth = setext.groups.marker?.startsWith("=") ? 1 : 2;
1390
- const text = state.paragraph.join(" ").trim();
1391
- pushHeading(headings, state, toHeading(depth, text, slugger, isRefDefined), state.line - 1);
1392
- noteCurlyMarker(text, state.paragraphStart, state);
1393
- state.paragraph = [];
1394
- return;
1395
- }
1396
- if (line.trim() === "" || THEMATIC_BREAK.test(line) || PARAGRAPH_INTERRUPT.test(line)) {
1397
- state.paragraph = [];
1398
- return;
1399
- }
1400
- if (state.paragraph.length === 0) {
1401
- state.paragraphStart = state.line;
1160
+ var gitRepositoryRoot = (root) => {
1161
+ try {
1162
+ return execFileSync("git", ["-C", root, "rev-parse", "--show-toplevel"], { encoding: "utf-8", env: gitEnv(), stdio: ["ignore", "pipe", "ignore"] }).trim();
1163
+ } catch {
1164
+ return null;
1402
1165
  }
1403
- state.paragraph.push(line.trim());
1404
1166
  };
1405
- var scanHeadingLine = (line, state, slugger, headings, isRefDefined) => {
1406
- const next = nextFenceState(line, state.fence);
1407
- if (state.fence !== null || next !== null) {
1408
- state.fence = next;
1409
- state.paragraph = [];
1410
- return;
1411
- }
1412
- const trimmed = line.trimStart();
1413
- if (state.promptTag) {
1414
- finishPromptTag(trimmed, 0, state);
1415
- state.paragraph = [];
1416
- return;
1417
- }
1418
- if (PROMPT_OPEN.test(trimmed)) {
1419
- finishPromptTag(trimmed, "<Prompt".length, state);
1420
- state.paragraph = [];
1421
- return;
1422
- }
1423
- if (state.promptDepth > 0) {
1424
- if (PROMPT_CLOSE.test(line)) {
1425
- state.promptDepth -= 1;
1426
- }
1427
- state.paragraph = [];
1428
- return;
1167
+ var gitLastModifiedTimes = (root, contentRoots, sourcePaths, repositoryRoot) => {
1168
+ if (sourcePaths.length === 0 || contentRoots.length === 0) {
1169
+ return new Map;
1429
1170
  }
1430
- scanContentLine(line, state, slugger, headings, isRefDefined);
1431
- };
1432
- var scanBody = (body) => {
1433
- const headings = [];
1434
- const slugger = new GithubSlugger;
1435
- const state = {
1436
- anchorLines: [],
1437
- curlyMarkers: [],
1438
- fence: null,
1439
- line: 0,
1440
- paragraph: [],
1441
- paragraphStart: 0,
1442
- promptDepth: 0,
1443
- promptTag: false,
1444
- sites: []
1445
- };
1446
- const { lines, offset } = linesWithoutFrontMatter(body);
1447
- const definedLabels = refDefinitionLabels(lines);
1448
- const isRefDefined = (label) => definedLabels.has(label.toLowerCase());
1449
- for (const [index, line] of lines.entries()) {
1450
- state.line = index + offset + 1;
1451
- scanHeadingLine(line, state, slugger, headings, isRefDefined);
1452
- }
1453
- const anchors = new Set;
1454
- const markup = state.anchorLines.join(`
1455
- `).replaceAll(HTML_COMMENT, " ");
1456
- for (const match of markup.matchAll(HTML_ID)) {
1457
- const id = match.groups?.quoted ?? match.groups?.jsx ?? match.groups?.bare;
1458
- if (id !== undefined) {
1459
- anchors.add(id);
1460
- }
1171
+ const gitRoot = repositoryRoot === undefined ? gitRepositoryRoot(root) : repositoryRoot;
1172
+ if (gitRoot === null) {
1173
+ return new Map;
1461
1174
  }
1462
- return {
1463
- anchors: [...anchors],
1464
- curlyMarkers: state.curlyMarkers,
1465
- headings,
1466
- sites: state.sites
1467
- };
1468
- };
1469
- var extractHeadings = (body) => scanBody(body).headings;
1470
- var MD_LINK = /\[(?<label>(?:[^[\]]|\[[^\]]*\])*)\]\((?<target>(?:[^()\s]|\([^()\s]*\))+)(?<title>\s+"[^"]*")?\)/gu;
1471
- var MD_IMAGE = /!\[[^\]]*\]\((?<target>(?:[^()\s]|\([^()\s]*\))+)(?<title>\s+"[^"]*")?\)/gu;
1472
- var targetOffsetIn = (matched, target, title) => matched.length - 1 - (title?.length ?? 0) - target.length;
1473
- var scanLinkLine = (line, lineNumber, fence, links) => {
1474
- const next = nextFenceState(line, fence);
1475
- if (fence !== null || next !== null) {
1476
- return next;
1477
- }
1478
- const masked = line.replaceAll(INLINE_CODE, (span) => " ".repeat(span.length));
1479
- for (const match of masked.matchAll(MD_LINK)) {
1480
- const target = match.groups?.target;
1481
- if (target === undefined || match.index === undefined) {
1482
- continue;
1483
- }
1484
- const targetOffset = targetOffsetIn(match[0], target, match.groups?.title);
1485
- const entry = {
1486
- column: match.index + targetOffset + 1,
1487
- line: lineNumber,
1488
- target
1489
- };
1490
- if (masked[match.index - 1] === "!") {
1491
- entry.image = true;
1492
- }
1493
- links.push(entry);
1494
- const label = match[0].slice(0, targetOffset - "](".length);
1495
- for (const image of label.matchAll(MD_IMAGE)) {
1496
- const imageTarget = image.groups?.target;
1497
- if (imageTarget === undefined || image.index === undefined) {
1498
- continue;
1175
+ try {
1176
+ const output = execFileSync("git", [
1177
+ "-C",
1178
+ root,
1179
+ "-c",
1180
+ "core.quotePath=false",
1181
+ "log",
1182
+ "--format=%x00%cI",
1183
+ "--name-only",
1184
+ "--",
1185
+ ...contentRoots
1186
+ ], { encoding: "utf-8", env: gitEnv(), maxBuffer: 256 * 1024 * 1024 });
1187
+ const byRepoPath = parseGitLog(output);
1188
+ const result = new Map;
1189
+ for (const sourcePath of sourcePaths) {
1190
+ const iso = byRepoPath.get(relative(gitRoot, sourcePath));
1191
+ if (iso) {
1192
+ result.set(sourcePath, iso);
1499
1193
  }
1500
- links.push({
1501
- column: match.index + image.index + targetOffsetIn(image[0], imageTarget, image.groups?.title) + 1,
1502
- image: true,
1503
- line: lineNumber,
1504
- target: imageTarget
1505
- });
1506
1194
  }
1195
+ return result;
1196
+ } catch {
1197
+ return new Map;
1507
1198
  }
1508
- return next;
1509
1199
  };
1510
- var extractLinks = (body, lineOffset = 0) => {
1511
- const links = [];
1512
- let fence = null;
1513
- let lineNumber = lineOffset;
1514
- for (const line of body.split(`
1515
- `)) {
1516
- lineNumber += 1;
1517
- fence = scanLinkLine(line, lineNumber, fence, links);
1200
+ var isShallowGitRepository = (root) => {
1201
+ try {
1202
+ return execFileSync("git", ["-C", root, "rev-parse", "--is-shallow-repository"], {
1203
+ encoding: "utf-8",
1204
+ env: gitEnv(),
1205
+ stdio: ["ignore", "pipe", "ignore"]
1206
+ }).trim() === "true";
1207
+ } catch {
1208
+ return false;
1518
1209
  }
1519
- return links;
1520
1210
  };
1521
- var DOUBLE_QUOTED = /"[^"]*"/gu;
1522
- var JSX_OPEN = /<(?<tag>[A-Z][A-Za-z0-9]*)/gu;
1523
- var scanTagLine = (line, fence, tags) => {
1524
- const next = nextFenceState(line, fence);
1525
- if (fence !== null || next !== null) {
1526
- return next;
1211
+ var lastModifiedShallowWarning = (root, undatedCount) => {
1212
+ if (undatedCount === 0 || !isShallowGitRepository(root)) {
1213
+ return [];
1527
1214
  }
1528
- const clean = line.replaceAll(INLINE_CODE, "").replaceAll(DOUBLE_QUOTED, "");
1529
- for (const match of clean.matchAll(JSX_OPEN)) {
1530
- const tag = match.groups?.tag;
1531
- if (tag) {
1532
- tags.add(tag);
1215
+ return [
1216
+ {
1217
+ code: "BLUME_SHALLOW_GIT_HISTORY",
1218
+ message: `lastModified is on, but this build runs in a shallow git clone, so ${undatedCount} page(s) have no git-derived date — their sitemap <lastmod> and "Last updated" stamps are omitted.`,
1219
+ severity: "warning",
1220
+ suggestion: "Fetch full history in CI: set the VERCEL_DEEP_CLONE=true environment variable on Vercel, or fetch-depth: 0 for actions/checkout."
1533
1221
  }
1534
- }
1535
- return next;
1222
+ ];
1536
1223
  };
1537
- var extractComponentTags = (body) => {
1538
- const tags = new Set;
1539
- let fence = null;
1540
- for (const line of body.split(`
1541
- `)) {
1542
- fence = scanTagLine(line, fence, tags);
1224
+
1225
+ // src/core/meta.ts
1226
+ import { basename, dirname, relative as relative2 } from "pathe";
1227
+ import { glob } from "tinyglobby";
1228
+ var META_FILES = [
1229
+ "**/meta.ts",
1230
+ "**/meta.js",
1231
+ "**/meta.mjs",
1232
+ "**/meta.$.ts",
1233
+ "**/meta.$.js",
1234
+ "**/meta.$.mjs"
1235
+ ];
1236
+ var isMetaFactory = (mod) => typeof mod === "function";
1237
+ var resolveMeta = async (mod) => isMetaFactory(mod) ? await mod() : mod;
1238
+ var metaKeyFor = (prefix, dir) => {
1239
+ const clean = prefix ? trimChar(prefix, "/") : "";
1240
+ if (!clean) {
1241
+ return dir;
1543
1242
  }
1544
- return [...tags];
1545
- };
1546
- var strippedLineOffset = (raw, body) => raw ? Math.max(0, raw.split(`
1547
- `).length - body.split(`
1548
- `).length) : 0;
1549
- var remapExpandedLinks = (links, origins, sourcePath) => links.map((link) => {
1550
- const origin = origins[link.line - 1];
1551
- if (!origin) {
1552
- return link;
1553
- }
1554
- const remapped = { ...link, line: origin.line };
1555
- if (origin.file !== sourcePath) {
1556
- remapped.file = origin.file;
1557
- }
1558
- return remapped;
1559
- });
1560
- var entryIncludes = (entry) => entry.expanded && entry.expanded.includes.length > 0 ? entry.expanded.includes : undefined;
1561
- var entryLinks = (entry) => entry.expanded ? remapExpandedLinks(extractLinks(entry.expanded.text), entry.expanded.origins, entry.sourcePath) : extractLinks(entry.body.text, strippedLineOffset(entry.raw, entry.body.text));
1562
- var curlyMarkerDiagnostics = (entry, markers, sourceName) => markers.map(({ id, line }) => {
1563
- const origin = entry.expanded?.origins[line - 1];
1564
- const page = entry.sourcePath ?? `${sourceName}:${entry.ref}`;
1565
- const inPartial = origin !== undefined && origin.file !== entry.sourcePath;
1566
- return {
1567
- code: "BLUME_MDX_CURLY_ANCHOR",
1568
- file: origin?.file ?? page,
1569
- line: origin?.line ?? line + strippedLineOffset(entry.raw, entry.body.text),
1570
- message: inPartial ? `\`{#${id}}\` is a JSX expression once this partial is included in ${page} (.mdx), so that page fails to compile.` : `\`{#${id}}\` is a JSX expression in .mdx, so this page fails to compile.`,
1571
- severity: "error",
1572
- suggestion: `Write \`[#${id}]\` or escape it as \`\\{#${id}\\}\` — both pin the same anchor in .md and .mdx.`
1573
- };
1574
- });
1575
- var deriveTitle = (meta, headings, id) => {
1576
- if (meta.title) {
1577
- return meta.title;
1578
- }
1579
- const firstHeading = headings.find((h) => h.depth === 1) ?? headings[0];
1580
- if (firstHeading) {
1581
- return firstHeading.text;
1582
- }
1583
- const base = id.split("/").pop() ?? id;
1584
- return titleCase(stripNumericPrefix(base.replace(extname2(base), "")));
1585
- };
1586
- var trimSlashes = (value) => trimChar(value, "/");
1587
- var isStringValue = (value) => typeof value === "string";
1588
- var withPrefix = (prefix, path) => {
1589
- const clean = prefix ? trimSlashes(prefix) : "";
1590
- return clean ? `${clean}/${path}` : path;
1591
- };
1592
- var placeEntryRef = (ref, ext, ctx) => {
1593
- const { version, rest } = ctx.versions ? detectVersionRef(ref, ctx.versions) : { rest: ref, version: "" };
1594
- const { navPath, locales } = ctx.i18n ? localePlacement(rest, ext, ctx.i18n) : { locales: [""], navPath: rest };
1595
- return { locales, navPath, version };
1596
- };
1597
- var resolveEntryRoute = (entry, ext, frontmatterSlug, ctx) => {
1598
- const { locales, navPath, version } = placeEntryRef(entry.ref, ext, ctx);
1599
- const slugInput = frontmatterSlug ?? entry.slug;
1600
- const slug = slugInput ? trimSlashes(slugInput) : "";
1601
- const routeInput = withPrefix(ctx.prefix, slug ? `${slug}${ext}` : navPath);
1602
- const { segments, groups, route: versionKey } = mapRoute(routeInput);
1603
- return {
1604
- groups,
1605
- locales,
1606
- logicalRoute: versionizeRoute(versionKey, version),
1607
- navPath: withPrefix(ctx.prefix, navPath),
1608
- segments,
1609
- version,
1610
- versionKey
1611
- };
1612
- };
1613
- var localizedRoute = (logicalRoute, locale, i18n) => i18n ? localizeRoute(logicalRoute, locale, i18n) : logicalRoute;
1614
- var isKeyCarrier = (segment) => typeof segment === "object" && segment !== null;
1615
- var isSymbolKey = (key) => typeof key === "symbol";
1616
- var segmentKey2 = (segment) => {
1617
- const key = isKeyCarrier(segment) ? segment.key : segment;
1618
- return isSymbolKey(key) ? String(key) : key;
1619
- };
1620
- var validateCustomKeys = (data, extend) => {
1621
- const custom = {};
1622
- const issues = [];
1623
- for (const [key, schema] of Object.entries(extend)) {
1624
- const outcome = schema["~standard"].validate(data[key]);
1625
- if (outcome instanceof Promise) {
1626
- issues.push({
1627
- message: "Async schemas are not supported for custom frontmatter keys.",
1628
- path: [key]
1629
- });
1630
- continue;
1631
- }
1632
- if (outcome.issues !== undefined) {
1633
- issues.push(...outcome.issues.map((issue) => ({
1634
- message: issue.message,
1635
- path: [key, ...(issue.path ?? []).map(segmentKey2)]
1636
- })));
1637
- continue;
1638
- }
1639
- if (outcome.value !== undefined || Object.hasOwn(data, key)) {
1640
- custom[key] = outcome.value;
1641
- }
1642
- }
1643
- return {
1644
- custom: Object.keys(custom).length > 0 ? custom : undefined,
1645
- issues
1646
- };
1647
- };
1648
- var parseEntryMeta = (entry, ctx) => {
1649
- const entryType = isStringValue(entry.data.type) ? entry.data.type : ctx.defaultType;
1650
- const typeExtend = ctx.typeFrontmatter?.[entryType];
1651
- const extend = ctx.frontmatterExtend || typeExtend ? { ...ctx.frontmatterExtend, ...typeExtend } : undefined;
1652
- const known = extend ? Object.fromEntries(Object.entries(entry.data).filter(([key]) => !Object.hasOwn(extend, key))) : entry.data;
1653
- const result = pageMetaSchema.safeParse(known);
1654
- const customResult = extend ? validateCustomKeys(entry.data, extend) : null;
1655
- if (result.success && (customResult?.issues.length ?? 0) === 0) {
1656
- return { custom: customResult?.custom, meta: result.data };
1657
- }
1658
- const source = entry.raw ?? (entry.sourcePath && existsSync(entry.sourcePath) ? readFileSync(entry.sourcePath, "utf-8") : undefined);
1659
- const location = {
1660
- code: "BLUME_FRONTMATTER_INVALID",
1661
- file: entry.sourcePath ?? `${ctx.source.name}:${entry.ref}`,
1662
- source
1663
- };
1664
- return {
1665
- diagnostics: [
1666
- ...result.success ? [] : diagnosticsFromZod(result.error, location),
1667
- ...customResult ? diagnosticsFromIssues(customResult.issues, location) : []
1668
- ]
1669
- };
1670
- };
1671
- var normalizeEntry = (entry, ctx) => {
1672
- const { format } = entry.body;
1673
- const ext = format === "mdx" ? ".mdx" : ".md";
1674
- const parsed = parseEntryMeta(entry, ctx);
1675
- if (parsed.diagnostics) {
1676
- return { diagnostics: parsed.diagnostics, pages: [] };
1677
- }
1678
- const { meta } = parsed;
1679
- if (meta.hidden) {
1680
- meta.sidebar.hidden = true;
1681
- }
1682
- if (meta.noindex) {
1683
- meta.seo.noindex = true;
1684
- }
1685
- const {
1686
- groups,
1687
- locales,
1688
- logicalRoute,
1689
- navPath,
1690
- segments,
1691
- version,
1692
- versionKey
1693
- } = resolveEntryRoute(entry, ext, meta.slug, {
1694
- i18n: ctx.i18n,
1695
- prefix: ctx.source.prefix,
1696
- versions: ctx.versions
1697
- });
1698
- const bodyText = entry.expanded?.text ?? entry.body.text;
1699
- const { anchors, curlyMarkers, headings } = scanBody(bodyText);
1700
- const { staged } = ctx.source;
1701
- const base = {
1702
- anchors,
1703
- body: staged ? { format, text: entry.raw ?? entry.body.text } : undefined,
1704
- collection: staged ? "staged" : undefined,
1705
- componentsUsed: format === "mdx" ? extractComponentTags(bodyText) : undefined,
1706
- contentType: meta.type ?? ctx.defaultType,
1707
- custom: parsed.custom,
1708
- description: meta.description,
1709
- editUrl: entry.editUrl,
1710
- entryId: staged ? `${ctx.source.name}/${entry.ref}` : undefined,
1711
- format,
1712
- groups,
1713
- headings,
1714
- id: `${ctx.source.name}:${entry.ref}`,
1715
- includes: entryIncludes(entry),
1716
- lastModified: meta.lastModified ?? entry.lastModified,
1717
- links: entryLinks(entry),
1718
- meta,
1719
- navPath,
1720
- segments,
1721
- source: { name: ctx.source.name, ref: entry.ref },
1722
- sourcePath: entry.sourcePath,
1723
- title: deriveTitle(meta, headings, navPath),
1724
- translationKey: logicalRoute,
1725
- version,
1726
- versionKey
1727
- };
1728
- const pages = locales.map((locale) => ({
1729
- ...base,
1730
- locale,
1731
- route: withBasePath(ctx.basePath ?? "", localizedRoute(logicalRoute, locale, ctx.i18n))
1732
- }));
1733
- return {
1734
- diagnostics: format === "mdx" ? curlyMarkerDiagnostics(entry, curlyMarkers, ctx.source.name) : [],
1735
- pages
1736
- };
1737
- };
1738
-
1739
- // src/core/includes.ts
1740
- var CONTENT_EXTENSIONS = new Set([".md", ".mdx"]);
1741
- var INCLUDE_STATEMENT = /^<include(?<attrs>(?:\s+[a-z][\w-]*(?:\s*=\s*(?:"[^"]*"|'[^']*'))?)*)\s*>\s*(?<target>[^<>\s](?:[^<>]*[^<>\s])?)\s*<\/include\s*>$/u;
1742
- var INCLUDE_ATTRIBUTE = /(?<name>[a-z][\w-]*)(?:\s*=\s*(?:"(?<double>[^"]*)"|'(?<single>[^']*)'))?/gu;
1743
- var hasIncludeStatements = (text) => text.includes("<include");
1744
- var INDENTED_CODE = /^(?: {4}|\t)/u;
1745
- var SETEXT_UNDERLINE2 = /^ {0,3}(?:=+|-+)\s*$/u;
1746
- var COMMENT_DELIMITERS = {
1747
- md: { close: "-->", open: "<!--" },
1748
- mdx: { close: "*/}", open: "{/*" }
1749
- };
1750
- var advanceCommentState = (line, delimiters, inComment) => {
1751
- let inside = inComment;
1752
- let cursor = 0;
1753
- for (;; ) {
1754
- const marker = inside ? delimiters.close : delimiters.open;
1755
- const index = line.indexOf(marker, cursor);
1756
- if (index === -1) {
1757
- return inside;
1758
- }
1759
- inside = !inside;
1760
- cursor = index + marker.length;
1761
- }
1762
- };
1763
- var parseIncludeStatement = (line) => {
1764
- const match = INCLUDE_STATEMENT.exec(line);
1765
- if (!match?.groups) {
1766
- return null;
1767
- }
1768
- const attributes = {};
1769
- for (const attr of (match.groups.attrs ?? "").matchAll(INCLUDE_ATTRIBUTE)) {
1770
- const name = attr.groups?.name;
1771
- const value = attr.groups?.double ?? attr.groups?.single;
1772
- if (name === "lang" && value) {
1773
- attributes.lang = value;
1774
- }
1775
- if (name === "meta" && value) {
1776
- attributes.meta = value;
1777
- }
1778
- }
1779
- return { attributes, target: match.groups.target ?? "" };
1780
- };
1781
- var parseIncludeLine = (line) => INDENTED_CODE.test(line) ? null : parseIncludeStatement(line.trim());
1782
- var DOCS_SUGGESTION = "Include paths resolve relative to the including file; paths starting with / resolve from the content root.";
1783
- var includeError = (code, message, file, line, suggestion) => ({ code, file, line, message, severity: "error", suggestion });
1784
- var escapesRoot = (root, path) => {
1785
- const rel = relative(root, path);
1786
- return rel === ".." || rel.startsWith("../");
1787
- };
1788
- var resolveIncludePath = (target, filePath, ctx, line) => {
1789
- if (target.startsWith("/")) {
1790
- if (!ctx.contentRoot) {
1791
- return {
1792
- error: includeError("BLUME_INCLUDE_OUTSIDE_ROOT", `Include target ${target} is root-relative, but no content root is configured here.`, filePath, line, "Use a path relative to the including file instead.")
1793
- };
1794
- }
1795
- const path = join(ctx.contentRoot, target);
1796
- if (escapesRoot(ctx.contentRoot, path)) {
1797
- return {
1798
- error: includeError("BLUME_INCLUDE_OUTSIDE_ROOT", `Include target ${target} resolves outside the content root.`, filePath, line, "Root-relative include paths resolve from the content root and cannot climb above it.")
1799
- };
1800
- }
1801
- return { path };
1802
- }
1803
- const path = resolve(dirname(filePath), target);
1804
- if (ctx.contentRoot && escapesRoot(ctx.contentRoot, path)) {
1805
- return {
1806
- error: includeError("BLUME_INCLUDE_OUTSIDE_ROOT", `Include target ${target} resolves outside the content root.`, filePath, line, "Move the included file under the content root so builds, version snapshots, and ejects all see it.")
1807
- };
1808
- }
1809
- return { path };
1810
- };
1811
- var rebaseImageLine = (line, fromDir, toDir) => {
1812
- const masked = line.replaceAll(INLINE_CODE, (span) => " ".repeat(span.length));
1813
- let out = "";
1814
- let cursor = 0;
1815
- for (const match of masked.matchAll(MD_IMAGE)) {
1816
- const target = match.groups?.target ?? "";
1817
- if (target.startsWith("/") || target.startsWith("#") || URL.canParse(target)) {
1818
- continue;
1819
- }
1820
- const rebased = relative(toDir, resolve(fromDir, target));
1821
- const url = rebased.startsWith(".") ? rebased : `./${rebased}`;
1822
- const offset = (match.index ?? 0) + targetOffsetIn(match[0], target, match.groups?.title);
1823
- out += line.slice(cursor, offset) + url;
1824
- cursor = offset + target.length;
1825
- }
1826
- return out + line.slice(cursor);
1827
- };
1828
- var rebaseImages = (lines, fromDir, toDir) => {
1829
- if (fromDir === toDir) {
1830
- return lines;
1831
- }
1832
- let fence = null;
1833
- return lines.map((line) => {
1834
- const next = nextFenceState(line, fence);
1835
- const inFence = fence !== null || next !== null;
1836
- fence = next;
1837
- return inFence ? line : rebaseImageLine(line, fromDir, toDir);
1838
- });
1839
- };
1840
- var codeBlockLines = (content, path, attributes) => {
1841
- const body = content.replace(/\n$/u, "");
1842
- let longestRun = 0;
1843
- for (const run of body.match(/`+/gu) ?? []) {
1844
- longestRun = Math.max(longestRun, run.length);
1845
- }
1846
- const fence = "`".repeat(Math.max(3, longestRun + 1));
1847
- const ext = extname3(path);
1848
- const lang = attributes.lang ?? (ext ? ext.slice(1) : "text");
1849
- const meta = attributes.meta ? ` ${attributes.meta}` : "";
1850
- return [`${fence}${lang}${meta}`, ...body.split(`
1851
- `), fence];
1852
- };
1853
- var expandStatement = async (statement, filePath, line, ctx, stack) => {
1854
- const resolved = resolveIncludePath(statement.target, filePath, ctx, line);
1855
- if ("error" in resolved) {
1856
- return resolved;
1857
- }
1858
- const { path } = resolved;
1859
- if (stack.includes(path)) {
1860
- return {
1861
- error: includeError("BLUME_INCLUDE_CYCLE", `Circular include: ${[...stack, path].map((entry) => relative(ctx.contentRoot ?? dirname(path), entry)).join(" -> ")}.`, filePath, line, "Remove the include statement that closes the loop.")
1862
- };
1863
- }
1864
- let content;
1865
- try {
1866
- content = await readFile(path, "utf-8");
1867
- } catch (error) {
1868
- return {
1869
- error: includeError("BLUME_INCLUDE_NOT_FOUND", `Include target ${statement.target} was not found (looked at ${path}): ${error instanceof Error ? error.message : String(error)}.`, filePath, line, DOCS_SUGGESTION)
1870
- };
1871
- }
1872
- ctx.includes.add(path);
1873
- const asContent = !statement.attributes.lang && CONTENT_EXTENSIONS.has(extname3(path).toLowerCase());
1874
- if (!asContent) {
1875
- const lines = codeBlockLines(content, path, statement.attributes);
1876
- return {
1877
- lines,
1878
- origins: lines.map((_, i) => ({ file: path, line: Math.max(1, i) }))
1879
- };
1880
- }
1881
- const parsed = frontmatter_default(content);
1882
- const body = parsed.content;
1883
- const strippedOffset = Math.max(0, content.split(`
1884
- `).length - body.split(`
1885
- `).length);
1886
- const expanded = await expandLines(body, path, strippedOffset, ctx, [
1887
- ...stack,
1888
- path
1889
- ]);
1890
- const lines = [...expanded.lines];
1891
- const origins = [...expanded.origins];
1892
- while (lines.at(-1)?.trim() === "") {
1893
- lines.pop();
1894
- origins.pop();
1895
- }
1896
- while (lines[0]?.trim() === "") {
1897
- lines.shift();
1898
- origins.shift();
1899
- }
1900
- return {
1901
- lines: rebaseImages(lines, dirname(path), dirname(filePath)),
1902
- origins
1903
- };
1904
- };
1905
- var expandLines = async (body, filePath, lineOffset, ctx, stack) => {
1906
- const sourceLines = body.split(`
1907
- `);
1908
- const isMdx = extname3(filePath).toLowerCase() === ".mdx";
1909
- const delimiters = COMMENT_DELIMITERS[isMdx ? "mdx" : "md"];
1910
- let fence = null;
1911
- let inComment = false;
1912
- const statements = sourceLines.map((line, index) => {
1913
- if (inComment) {
1914
- inComment = advanceCommentState(line, delimiters, true);
1915
- return null;
1916
- }
1917
- const next = nextFenceState(line, fence);
1918
- const inFence = fence !== null || next !== null;
1919
- fence = next;
1920
- if (inFence) {
1921
- return null;
1922
- }
1923
- inComment = advanceCommentState(line, delimiters, false);
1924
- if (inComment || !hasIncludeStatements(line)) {
1925
- return null;
1926
- }
1927
- const statement = isMdx ? parseIncludeStatement(line.trim()) : parseIncludeLine(line);
1928
- if (statement && !isMdx && SETEXT_UNDERLINE2.test(sourceLines[index + 1] ?? "")) {
1929
- return null;
1930
- }
1931
- return statement;
1932
- });
1933
- const expansions = await Promise.all(statements.map((statement, index) => statement ? expandStatement(statement, filePath, lineOffset + index + 1, ctx, stack) : null));
1934
- const lines = [];
1935
- const origins = [];
1936
- for (const [index, line] of sourceLines.entries()) {
1937
- const rawLine = lineOffset + index + 1;
1938
- const expanded = expansions[index];
1939
- if (!expanded || "error" in expanded) {
1940
- if (expanded) {
1941
- ctx.errors.push(expanded.error);
1942
- }
1943
- lines.push(line);
1944
- origins.push({ file: filePath, line: rawLine });
1945
- continue;
1946
- }
1947
- const pad = () => {
1948
- lines.push("");
1949
- origins.push({ file: filePath, line: rawLine });
1950
- };
1951
- if (lines.at(-1)?.trim()) {
1952
- pad();
1953
- }
1954
- lines.push(...expanded.lines);
1955
- origins.push(...expanded.origins);
1956
- if (sourceLines[index + 1]?.trim()) {
1957
- pad();
1958
- }
1959
- }
1960
- return { lines, origins };
1961
- };
1962
- var expandIncludes = async (body, options) => {
1963
- const ctx = {
1964
- contentRoot: options.contentRoot,
1965
- errors: [],
1966
- includes: new Set
1967
- };
1968
- const { lines, origins } = await expandLines(body, options.sourcePath, options.lineOffset ?? 0, ctx, [options.sourcePath]);
1969
- return {
1970
- errors: ctx.errors,
1971
- includes: [...ctx.includes],
1972
- origins,
1973
- text: lines.join(`
1974
- `)
1975
- };
1976
- };
1977
- var buildIncludeGraph = (pages) => {
1978
- const graph = {};
1979
- for (const page of pages) {
1980
- const { sourcePath } = page;
1981
- if (!sourcePath) {
1982
- continue;
1983
- }
1984
- for (const partial of page.includes ?? []) {
1985
- const includers = graph[partial] ?? [];
1986
- graph[partial] = includers;
1987
- if (!includers.includes(sourcePath)) {
1988
- includers.push(sourcePath);
1989
- }
1990
- }
1991
- }
1992
- return graph;
1993
- };
1994
-
1995
- // src/core/last-modified.ts
1996
- import { execFileSync } from "node:child_process";
1997
- import { relative as relative2 } from "pathe";
1998
- var GIT_LOCATION_VARS = new Set([
1999
- "GIT_COMMON_DIR",
2000
- "GIT_DIR",
2001
- "GIT_INDEX_FILE",
2002
- "GIT_OBJECT_DIRECTORY",
2003
- "GIT_PREFIX",
2004
- "GIT_WORK_TREE"
2005
- ]);
2006
- var gitEnv = () => Object.fromEntries(Object.entries(process.env).filter(([key]) => !GIT_LOCATION_VARS.has(key)));
2007
- var resolveLastModifiedConfig = (value) => {
2008
- if (value === false) {
2009
- return { enabled: false, source: "git" };
2010
- }
2011
- if (value === true) {
2012
- return { enabled: true, source: "git" };
2013
- }
2014
- return { enabled: true, source: value.type };
2015
- };
2016
- var parseGitLog = (output) => {
2017
- const times = new Map;
2018
- let current = null;
2019
- for (const line of output.split(`
2020
- `)) {
2021
- if (line.startsWith("\x00")) {
2022
- current = line.slice(1);
2023
- } else if (line && current && !times.has(line)) {
2024
- times.set(line, current);
2025
- }
2026
- }
2027
- return times;
2028
- };
2029
- var gitRepositoryRoot = (root) => {
2030
- try {
2031
- return execFileSync("git", ["-C", root, "rev-parse", "--show-toplevel"], { encoding: "utf-8", env: gitEnv(), stdio: ["ignore", "pipe", "ignore"] }).trim();
2032
- } catch {
2033
- return null;
2034
- }
2035
- };
2036
- var gitLastModifiedTimes = (root, contentRoots, sourcePaths, repositoryRoot) => {
2037
- if (sourcePaths.length === 0 || contentRoots.length === 0) {
2038
- return new Map;
2039
- }
2040
- const gitRoot = repositoryRoot === undefined ? gitRepositoryRoot(root) : repositoryRoot;
2041
- if (gitRoot === null) {
2042
- return new Map;
2043
- }
2044
- try {
2045
- const output = execFileSync("git", [
2046
- "-C",
2047
- root,
2048
- "-c",
2049
- "core.quotePath=false",
2050
- "log",
2051
- "--format=%x00%cI",
2052
- "--name-only",
2053
- "--",
2054
- ...contentRoots
2055
- ], { encoding: "utf-8", env: gitEnv(), maxBuffer: 256 * 1024 * 1024 });
2056
- const byRepoPath = parseGitLog(output);
2057
- const result = new Map;
2058
- for (const sourcePath of sourcePaths) {
2059
- const iso = byRepoPath.get(relative2(gitRoot, sourcePath));
2060
- if (iso) {
2061
- result.set(sourcePath, iso);
2062
- }
2063
- }
2064
- return result;
2065
- } catch {
2066
- return new Map;
2067
- }
2068
- };
2069
- var isShallowGitRepository = (root) => {
2070
- try {
2071
- return execFileSync("git", ["-C", root, "rev-parse", "--is-shallow-repository"], {
2072
- encoding: "utf-8",
2073
- env: gitEnv(),
2074
- stdio: ["ignore", "pipe", "ignore"]
2075
- }).trim() === "true";
2076
- } catch {
2077
- return false;
2078
- }
2079
- };
2080
- var lastModifiedShallowWarning = (root, undatedCount) => {
2081
- if (undatedCount === 0 || !isShallowGitRepository(root)) {
2082
- return [];
2083
- }
2084
- return [
2085
- {
2086
- code: "BLUME_SHALLOW_GIT_HISTORY",
2087
- message: `lastModified is on, but this build runs in a shallow git clone, so ${undatedCount} page(s) have no git-derived date — their sitemap <lastmod> and "Last updated" stamps are omitted.`,
2088
- severity: "warning",
2089
- suggestion: "Fetch full history in CI: set the VERCEL_DEEP_CLONE=true environment variable on Vercel, or fetch-depth: 0 for actions/checkout."
2090
- }
2091
- ];
2092
- };
2093
-
2094
- // src/core/meta.ts
2095
- import { basename, dirname as dirname2, relative as relative3 } from "pathe";
2096
- import { glob } from "tinyglobby";
2097
- var META_FILES = [
2098
- "**/meta.ts",
2099
- "**/meta.js",
2100
- "**/meta.mjs",
2101
- "**/meta.$.ts",
2102
- "**/meta.$.js",
2103
- "**/meta.$.mjs"
2104
- ];
2105
- var isMetaFactory = (mod) => typeof mod === "function";
2106
- var resolveMeta = async (mod) => isMetaFactory(mod) ? await mod() : mod;
2107
- var metaKeyFor = (prefix, dir) => {
2108
- const clean = prefix ? trimChar(prefix, "/") : "";
2109
- if (!clean) {
2110
- return dir;
2111
- }
2112
- return dir ? `${clean}/${dir}` : clean;
1243
+ return dir ? `${clean}/${dir}` : clean;
2113
1244
  };
2114
1245
  var discoverFolderMeta = async (sources, options = {}) => {
2115
1246
  const list = Array.isArray(sources) ? sources : [{ root: sources }];
@@ -2141,7 +1272,7 @@ var discoverFolderMeta = async (sources, options = {}) => {
2141
1272
  }));
2142
1273
  for (const { loaded, source } of perSource) {
2143
1274
  for (const entry of loaded) {
2144
- const dir = relative3(source.root, dirname2(entry.file));
1275
+ const dir = relative2(source.root, dirname(entry.file));
2145
1276
  const [head, ...tail] = dir.split("/");
2146
1277
  const version = head && versionDirs.has(head) ? head : "";
2147
1278
  const afterVersion = version ? tail : [head ?? "", ...tail];
@@ -2174,1287 +1305,345 @@ var discoverFolderMeta = async (sources, options = {}) => {
2174
1305
  };
2175
1306
 
2176
1307
  // src/core/sources/resolve.ts
2177
- import { isAbsolute as isAbsolute4, join as join9, resolve as resolve4 } from "pathe";
1308
+ import { join as join7 } from "pathe";
2178
1309
 
2179
- // src/core/sources/cache.ts
2180
- import { createHash } from "node:crypto";
2181
- import { mkdir, readFile as readFile2, writeFile } from "node:fs/promises";
2182
- import { join as join2 } from "pathe";
2183
- var hashText = (text) => createHash("sha256").update(text).digest("hex").slice(0, 16);
2184
- var entriesDigest = (entries) => hashText(entries.map((entry) => `${entry.ref}:${entry.hash ?? hashText(entry.body.text)}`).join("|"));
2185
- var pollingWatch = (load, intervalSeconds, seed) => (onChange) => {
2186
- let last = "";
2187
- if (seed) {
2188
- const seedBaseline = async () => {
2189
- try {
2190
- const { entries } = await seed();
2191
- last ||= entriesDigest(entries);
2192
- } catch {}
2193
- };
2194
- seedBaseline();
2195
- }
2196
- const tick = async () => {
2197
- try {
2198
- const { entries } = await load();
2199
- const next = entriesDigest(entries);
2200
- if (last && next !== last) {
2201
- onChange();
2202
- }
2203
- last = next;
2204
- } catch {}
2205
- };
2206
- const timer = setInterval(() => {
2207
- tick();
2208
- }, intervalSeconds * 1000);
2209
- return () => clearInterval(timer);
2210
- };
2211
- var snapshotCache = (cacheDir) => {
2212
- const file = join2(cacheDir, "entries.json");
2213
- return {
2214
- read: async () => {
2215
- try {
2216
- return JSON.parse(await readFile2(file, "utf-8"));
2217
- } catch {
2218
- return [];
2219
- }
2220
- },
2221
- write: async (entries) => {
2222
- try {
2223
- await mkdir(cacheDir, { recursive: true });
2224
- await writeFile(file, `${JSON.stringify(entries)}
2225
- `, "utf-8");
2226
- } catch {}
2227
- }
2228
- };
2229
- };
2230
- var loadWithCache = async (name, cache, fetchEntries, refresh = true) => {
2231
- if (!refresh) {
2232
- const cached = await cache.read();
2233
- if (cached.length > 0) {
2234
- return { diagnostics: [], entries: cached };
2235
- }
2236
- }
2237
- try {
2238
- const entries = await fetchEntries();
2239
- await cache.write(entries);
2240
- return { diagnostics: [], entries };
2241
- } catch (error) {
2242
- const fallback = await cache.read();
2243
- if (fallback.length > 0) {
2244
- const diagnostic = {
2245
- code: "BLUME_SOURCE_OFFLINE",
2246
- message: `Source "${name}" could not be fetched (${error.message}); served ${fallback.length} cached entries.`,
2247
- severity: "warning"
2248
- };
2249
- return { diagnostics: [diagnostic], entries: fallback };
1310
+ // src/core/sources/json.ts
1311
+ var isJsonObject = (value) => typeof value === "object" && value !== null;
1312
+ var getPath = (doc, path) => {
1313
+ let current = doc;
1314
+ for (const key of path.split(".")) {
1315
+ if (isJsonObject(current)) {
1316
+ current = current[key];
1317
+ } else {
1318
+ return;
2250
1319
  }
2251
- throw new BlumeError({
2252
- code: "BLUME_SOURCE_FETCH_FAILED",
2253
- message: `Source "${name}" failed to load and no cache is available: ${error.message}`,
2254
- severity: "error"
2255
- });
2256
1320
  }
1321
+ return current;
2257
1322
  };
1323
+ var isStringValue2 = (value) => typeof value === "string";
1324
+ var asString = (value) => isStringValue2(value) ? value : undefined;
1325
+ var asArray = (value) => Array.isArray(value) ? value : [];
1326
+ var asObject = (value) => isJsonObject(value) && !Array.isArray(value) ? value : undefined;
1327
+ var isNumberValue = (value) => typeof value === "number";
1328
+ var asNumber = (value) => isNumberValue(value) ? value : undefined;
1329
+ var objectsIn = (value) => asArray(value).flatMap((item) => {
1330
+ const object = asObject(item);
1331
+ return object ? [object] : [];
1332
+ });
2258
1333
 
2259
- // src/openapi/model.ts
2260
- var HTTP_METHODS = [
2261
- "get",
2262
- "put",
2263
- "post",
2264
- "delete",
2265
- "options",
2266
- "head",
2267
- "patch",
2268
- "trace"
2269
- ];
2270
- var UNTAGGED = "Operations";
2271
- var CASE_BOUNDARY = /(?<before>[\p{Ll}\p{N}])(?<capital>\p{Lu})/gu;
2272
- var ACRONYM_BOUNDARY = /(?<acronym>\p{Lu})(?<word>\p{Lu}\p{Ll})/gu;
2273
- var splitIdentifier = (identifier) => identifier.replace(CASE_BOUNDARY, "$<before>-$<capital>").replace(ACRONYM_BOUNDARY, "$<acronym>-$<word>");
2274
- var operationKey = (method, path, operationId) => {
2275
- const fromId = operationId ? slugify(splitIdentifier(operationId)) : "";
2276
- return fromId || slugify(`${method}-${path}`);
2277
- };
2278
- var specOf = (specs, source) => Object.hasOwn(specs, source) ? specs[source] : undefined;
2279
- var operationOf = (spec, id) => Object.hasOwn(spec.operations, id) ? spec.operations[id] : undefined;
2280
- var specAddresses = (spec) => {
2281
- const addresses = [];
2282
- if (spec.kind === "graphql") {
2283
- if (spec.endpoint) {
2284
- addresses.push(spec.endpoint);
2285
- }
2286
- return { addresses, label: "Endpoint" };
2287
- }
2288
- if (spec.kind === "asyncapi") {
2289
- const servers = spec.document.servers ?? {};
2290
- for (const server of Object.values(servers)) {
2291
- if (server?.host) {
2292
- addresses.push(`${server.protocol ? `${server.protocol}://` : ""}${server.host}${server.pathname ?? ""}`);
2293
- }
2294
- }
2295
- return { addresses, label: "Servers" };
2296
- }
2297
- const declared = spec.document.servers;
2298
- for (const server of Array.isArray(declared) ? declared : []) {
2299
- if (server.url) {
2300
- addresses.push(server.url);
2301
- }
1334
+ // src/core/sources/contentful-rich-text.ts
1335
+ var HEADING = /^heading-(?<level>[1-6])$/u;
1336
+ var CELL_PIPE = /\|/gu;
1337
+ var CELL_BREAK = /\n+/gu;
1338
+ var assetFromEntry = (asset) => {
1339
+ const url = asString(getPath(asset, "fields.file.url"));
1340
+ if (!url) {
1341
+ return null;
2302
1342
  }
2303
- return { addresses, label: "Base URL" };
2304
- };
2305
- var isOperation = (value) => typeof value === "object" && value !== null;
2306
- var hasTagName = (tag) => typeof tag.name === "string";
2307
- var tagSlugger = () => {
2308
- const assigned = new Map;
2309
- const taken = new Set;
2310
- return (name) => {
2311
- const existing = assigned.get(name);
2312
- if (existing) {
2313
- return existing;
2314
- }
2315
- const base = slugify(name) || "operations";
2316
- let slug = base;
2317
- for (let suffix = 2;taken.has(slug); suffix += 1) {
2318
- slug = `${base}-${suffix}`;
2319
- }
2320
- taken.add(slug);
2321
- assigned.set(name, slug);
2322
- return slug;
2323
- };
2324
- };
2325
- var operationCollector = (baseRoute, tagMeta) => {
2326
- const operations = [];
2327
- const tagOrder = [];
2328
- const tagsSeen = new Set;
2329
- const seen = new Set;
2330
- const slugForTag = tagSlugger();
2331
- const add = (entry) => {
2332
- const tagSlug = slugForTag(entry.tag);
2333
- if (!tagsSeen.has(entry.tag)) {
2334
- tagsSeen.add(entry.tag);
2335
- tagOrder.push(entry.tag);
2336
- }
2337
- let { key } = entry;
2338
- while (seen.has(key)) {
2339
- key = `${key}-${entry.method}`;
2340
- }
2341
- seen.add(key);
2342
- operations.push({
2343
- ...entry,
2344
- key,
2345
- route: `${baseRoute === "/" ? "" : baseRoute}/${tagSlug}/${key}`,
2346
- tagSlug
2347
- });
2348
- };
2349
- const finish = () => ({
2350
- operations,
2351
- tags: tagOrder.map((name) => ({
2352
- description: tagMeta.get(name) ?? "",
2353
- name,
2354
- slug: slugForTag(name)
2355
- }))
2356
- });
2357
- return { add, finish };
2358
- };
2359
- var extractOperations = (document, baseRoute) => {
2360
- const warnings = [];
2361
- const tagMeta = new Map((document.tags ?? []).filter(hasTagName).map((tag) => [tag.name, tag.description ?? ""]));
2362
- const collector = operationCollector(baseRoute, tagMeta);
2363
- for (const [path, item] of Object.entries(document.paths ?? {})) {
2364
- if (!item) {
2365
- continue;
2366
- }
2367
- if ("$ref" in item) {
2368
- warnings.push(`Path "${path}" is a $ref to a shared path item; referenced path items are not resolved, so its operations are missing from the reference. Inline the path item under "paths" to render it.`);
2369
- continue;
2370
- }
2371
- for (const method of HTTP_METHODS) {
2372
- const operation = item[method];
2373
- if (!isOperation(operation)) {
2374
- continue;
1343
+ return {
1344
+ description: asString(getPath(asset, "fields.description")),
1345
+ title: asString(getPath(asset, "fields.title")),
1346
+ url: absoluteUrl(url)
1347
+ };
1348
+ };
1349
+ var children = (node) => objectsIn(node.content);
1350
+ var marksOf = (node) => {
1351
+ const marks = {};
1352
+ for (const mark of objectsIn(node.marks)) {
1353
+ switch (asString(mark.type)) {
1354
+ case "bold": {
1355
+ marks.bold = true;
1356
+ break;
2375
1357
  }
2376
- collector.add({
2377
- deprecated: operation.deprecated ?? false,
2378
- description: operation.description ?? "",
2379
- key: operationKey(method, path, operation.operationId),
2380
- method,
2381
- operationId: operation.operationId,
2382
- path,
2383
- summary: operation.summary ?? "",
2384
- tag: operation.tags?.[0] ?? UNTAGGED
2385
- });
2386
- }
2387
- }
2388
- return { ...collector.finish(), warnings };
2389
- };
2390
-
2391
- // src/openapi/asyncapi.ts
2392
- var ASYNCAPI_ACTIONS = ["send", "receive"];
2393
- var unescapePointer = (token) => token.replaceAll("~1", "/").replaceAll("~0", "~");
2394
- var CHANNEL_REF = /^#\/channels\/(?<id>.+)$/u;
2395
- var isString = (value) => typeof value === "string";
2396
- var channelIdOf = (channel) => {
2397
- const id = CHANNEL_REF.exec(channel?.$ref ?? "")?.groups?.id;
2398
- return id === undefined ? undefined : unescapePointer(id);
2399
- };
2400
- var channelAddress = (channelId, channel) => {
2401
- const address = channel?.address;
2402
- return isString(address) && address !== "" ? address : channelId;
2403
- };
2404
- var isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2405
- var COMPONENT_SECTION_REF = /^#\/components\/(?<section>channels|operations)\/(?<name>[^/]+)$/u;
2406
- var inlineComponentRefs = (document) => {
2407
- const maps = [
2408
- [document.channels, "channels"],
2409
- [document.operations, "operations"]
2410
- ];
2411
- for (const [map, section] of maps) {
2412
- if (!isObject(map)) {
2413
- continue;
2414
- }
2415
- const table = document.components?.[section];
2416
- for (const [id, entry] of Object.entries(map)) {
2417
- if (!isObject(entry) || !isString(entry.$ref)) {
2418
- continue;
1358
+ case "italic": {
1359
+ marks.italic = true;
1360
+ break;
2419
1361
  }
2420
- const groups = COMPONENT_SECTION_REF.exec(entry.$ref)?.groups;
2421
- if (groups?.section !== section) {
2422
- continue;
1362
+ case "code": {
1363
+ marks.code = true;
1364
+ break;
2423
1365
  }
2424
- const resolved = isObject(table) ? table[unescapePointer(groups.name ?? "")] : undefined;
2425
- if (isObject(resolved) && !isString(resolved.$ref)) {
2426
- map[id] = { ...resolved };
1366
+ case "strikethrough": {
1367
+ marks.strike = true;
1368
+ break;
2427
1369
  }
1370
+ default:
2428
1371
  }
2429
1372
  }
1373
+ return marks;
2430
1374
  };
2431
- var TRAIT_REF = /^#\/components\/(?<section>operationTraits|messageTraits)\/(?<name>[^/]+)$/u;
2432
- var applyAsyncApiTraits = (document) => {
2433
- const resolveTrait = (trait) => {
2434
- if (!isObject(trait)) {
2435
- return;
2436
- }
2437
- if (!isString(trait.$ref)) {
2438
- return trait;
2439
- }
2440
- const groups = TRAIT_REF.exec(trait.$ref)?.groups;
2441
- const section = groups?.section ? document.components?.[groups.section] : undefined;
2442
- const resolved = section?.[groups?.name ?? ""];
2443
- return isObject(resolved) ? resolved : undefined;
2444
- };
2445
- const mergeTraits = (node) => {
2446
- const { traits } = node;
2447
- if (!Array.isArray(traits)) {
2448
- return;
2449
- }
2450
- delete node.traits;
2451
- const merged = {};
2452
- for (const trait of traits) {
2453
- Object.assign(merged, resolveTrait(trait));
2454
- }
2455
- for (const [key, value] of Object.entries(merged)) {
2456
- if (!(key in node)) {
2457
- node[key] = value;
2458
- }
2459
- }
2460
- };
2461
- for (const operation of Object.values(document.operations ?? {})) {
2462
- if (isObject(operation)) {
2463
- mergeTraits(operation);
2464
- }
2465
- }
2466
- const channelMaps = [
2467
- ...Object.values(document.channels ?? {}),
2468
- ...Object.values(document.components?.channels ?? {})
2469
- ];
2470
- const messageMaps = [
2471
- ...channelMaps.map((channel) => isObject(channel) ? channel.messages : undefined),
2472
- document.components?.messages
2473
- ];
2474
- for (const messages of messageMaps) {
2475
- if (!isObject(messages)) {
2476
- continue;
2477
- }
2478
- for (const message of Object.values(messages)) {
2479
- if (isObject(message) && !isString(message.$ref)) {
2480
- mergeTraits(message);
2481
- }
2482
- }
1375
+ var linkedTarget = (node, resolve) => {
1376
+ const target = getPath(node, "data.target");
1377
+ if (getPath(node, "data.target.fields") !== undefined) {
1378
+ return target;
2483
1379
  }
2484
- return document;
1380
+ const id = asString(getPath(node, "data.target.sys.id"));
1381
+ return id ? resolve?.(id) ?? null : null;
2485
1382
  };
2486
- var normalizeAsyncApiDocument = (document) => {
2487
- inlineComponentRefs(document);
2488
- return applyAsyncApiTraits(document);
2489
- };
2490
- var operationSite = (id, operation, channels) => {
2491
- const action = ASYNCAPI_ACTIONS.find((a) => a === operation.action);
2492
- if (!action) {
2493
- return `Operation "${id}" declares no send/receive action; it is missing from the reference.`;
2494
- }
2495
- const channelId = channelIdOf(operation.channel);
2496
- const channel = channelId === undefined ? undefined : channels[channelId];
2497
- if (channelId === undefined || !isObject(channel) || isString(channel.$ref)) {
2498
- return `Operation "${id}" references a channel that isn't declared under "channels"; it is missing from the reference.`;
1383
+ var linkedAsset = (node, options) => {
1384
+ if (getPath(node, "data.target.fields") !== undefined) {
1385
+ const inline = linkedTarget(node);
1386
+ return inline ? assetFromEntry(inline) : null;
2499
1387
  }
2500
- return { action, address: channelAddress(channelId, channel), channelId };
1388
+ const id = asString(getPath(node, "data.target.sys.id"));
1389
+ return id ? options.resolveAsset?.(id) ?? null : null;
2501
1390
  };
2502
- var extractAsyncApiOperations = (document, baseRoute) => {
2503
- const warnings = [];
2504
- const tagMeta = new Map((document.info?.tags ?? []).filter((tag) => typeof tag?.name === "string").map((tag) => [tag.name, tag.description ?? ""]));
2505
- const collector = operationCollector(baseRoute, tagMeta);
2506
- const channels = document.channels ?? {};
2507
- for (const [id, operation] of Object.entries(document.operations ?? {})) {
2508
- if (!isObject(operation)) {
2509
- continue;
2510
- }
2511
- if (isString(operation.$ref)) {
2512
- warnings.push(`Operation "${id}" is a reference that doesn't resolve to a components operation; it is missing from the reference.`);
2513
- continue;
2514
- }
2515
- const site = operationSite(id, operation, channels);
2516
- if (isString(site)) {
2517
- warnings.push(site);
2518
- continue;
2519
- }
2520
- const { action, address, channelId } = site;
2521
- const tag = operation.tags?.find((candidate) => typeof candidate?.name === "string")?.name;
2522
- collector.add({
2523
- channelId,
2524
- deprecated: operation.deprecated === true,
2525
- description: operation.description ?? "",
2526
- key: operationKey(action, address, id),
2527
- method: action,
2528
- operationId: id,
2529
- path: address,
2530
- summary: operation.title ?? operation.summary ?? "",
2531
- tag: tag ?? address
2532
- });
1391
+ var embeddedEntry = (node, options) => {
1392
+ const entry = linkedTarget(node, options.resolveEntry);
1393
+ const contentType = entry ? asString(getPath(entry, "sys.contentType.sys.id")) : undefined;
1394
+ const serializer = contentType ? options.serializers?.[contentType] : undefined;
1395
+ if (entry && serializer) {
1396
+ return serializer(entry);
2533
1397
  }
2534
- return { ...collector.finish(), warnings };
1398
+ return unsupported(`Contentful embedded entry${contentType ? `: ${contentType}` : ""}`, writesMdx(options.serializers));
2535
1399
  };
2536
-
2537
- // src/openapi/graphql.ts
2538
- var GRAPHQL_OPERATION_KINDS = [
2539
- "query",
2540
- "mutation",
2541
- "subscription"
2542
- ];
2543
- var GRAPHQL_TYPE_KINDS = [
2544
- "object",
2545
- "input",
2546
- "enum",
2547
- "interface",
2548
- "union",
2549
- "scalar"
2550
- ];
2551
- var GRAPHQL_GROUP_LABELS = {
2552
- enum: "Enums",
2553
- input: "Input Objects",
2554
- interface: "Interfaces",
2555
- mutation: "Mutations",
2556
- object: "Objects",
2557
- query: "Queries",
2558
- scalar: "Scalars",
2559
- subscription: "Subscriptions",
2560
- union: "Unions"
2561
- };
2562
- var isGraphqlOperationKind = (method) => GRAPHQL_OPERATION_KINDS.some((kind) => kind === method);
2563
- var GRAPHQL_TYPE_KEYWORDS = {
2564
- enum: "enum",
2565
- input: "input",
2566
- interface: "interface",
2567
- object: "type",
2568
- scalar: "scalar",
2569
- union: "union"
2570
- };
2571
- var graphqlSignature = (operation) => {
2572
- const member = operation.method;
2573
- const keyword = isGraphqlOperationKind(member) ? member : GRAPHQL_TYPE_KEYWORDS[member];
2574
- return `${keyword} ${operation.path}`;
2575
- };
2576
- var byName = (a, b) => a.name < b.name ? -1 : 1;
2577
- var extractGraphqlOperations = (document, baseRoute) => {
2578
- const collector = operationCollector(baseRoute, new Map);
2579
- const rootNames = new Set(Object.values(document.roots));
2580
- for (const kind of GRAPHQL_OPERATION_KINDS) {
2581
- const rootName = document.roots[kind];
2582
- const root = rootName === undefined ? undefined : document.types[rootName];
2583
- for (const field of root?.fields ?? []) {
2584
- collector.add({
2585
- deprecated: field.deprecationReason !== undefined,
2586
- description: field.description,
2587
- key: operationKey(kind, field.name, field.name),
2588
- method: kind,
2589
- operationId: field.name,
2590
- path: field.name,
2591
- summary: "",
2592
- tag: GRAPHQL_GROUP_LABELS[kind]
2593
- });
1400
+ var renderInlines = (nodes, options) => nodes.map((node) => renderInlineNode(node, options)).join("");
1401
+ var renderInlineNode = (node, options) => {
1402
+ switch (asString(node.nodeType)) {
1403
+ case "text": {
1404
+ return renderInline(asString(node.value) ?? "", marksOf(node));
2594
1405
  }
2595
- }
2596
- for (const kind of GRAPHQL_TYPE_KINDS) {
2597
- const members = Object.values(document.types).filter((type) => type.kind === kind && !(type.builtIn || rootNames.has(type.name))).toSorted(byName);
2598
- for (const type of members) {
2599
- collector.add({
2600
- deprecated: false,
2601
- description: type.description,
2602
- key: operationKey(kind, type.name, type.name),
2603
- method: kind,
2604
- operationId: type.name,
2605
- path: type.name,
2606
- summary: "",
2607
- tag: GRAPHQL_GROUP_LABELS[kind]
2608
- });
1406
+ case "hyperlink": {
1407
+ return renderLink(renderInlines(children(node), options), asString(getPath(node, "data.uri")));
2609
1408
  }
2610
- }
2611
- return { ...collector.finish(), warnings: [] };
2612
- };
2613
-
2614
- // src/openapi/parse.ts
2615
- import { mkdir as mkdir2, readFile as readFile3, writeFile as writeFile2 } from "node:fs/promises";
2616
- import { setTimeout as sleep } from "node:timers/promises";
2617
- import { convert } from "@asyncapi/converter";
2618
- import { normalize as normalize2, upgrade } from "@scalar/openapi-parser";
2619
- import pRetry, { AbortError } from "p-retry";
2620
- import { isAbsolute, join as join3 } from "pathe";
2621
-
2622
- // src/openapi/graphql-build.ts
2623
- import {
2624
- buildClientSchema,
2625
- buildSchema,
2626
- getNamedType,
2627
- isEnumType,
2628
- isInputObjectType,
2629
- isInterfaceType,
2630
- isObjectType,
2631
- isSpecifiedScalarType,
2632
- isUnionType,
2633
- print
2634
- } from "graphql";
2635
- var typeRef = (type) => ({
2636
- display: String(type),
2637
- name: getNamedType(type).name
2638
- });
2639
- var defaultLiteral = (input) => {
2640
- const literal = input.default?.literal;
2641
- return literal ? print(literal) : undefined;
2642
- };
2643
- var inputValue = (input) => {
2644
- const value = {
2645
- description: input.description ?? "",
2646
- name: input.name,
2647
- type: typeRef(input.type)
2648
- };
2649
- const printed = defaultLiteral(input);
2650
- if (printed !== undefined) {
2651
- value.default = printed;
2652
- }
2653
- if (input.deprecationReason !== null && input.deprecationReason !== undefined) {
2654
- value.deprecationReason = input.deprecationReason;
2655
- }
2656
- return value;
2657
- };
2658
- var outputField = (field) => {
2659
- const value = {
2660
- args: field.args.map(inputValue),
2661
- description: field.description ?? "",
2662
- name: field.name,
2663
- type: typeRef(field.type)
2664
- };
2665
- if (field.deprecationReason !== null && field.deprecationReason !== undefined) {
2666
- value.deprecationReason = field.deprecationReason;
2667
- }
2668
- return value;
2669
- };
2670
- var enumValues = (type) => type.getValues().map((value) => {
2671
- const member = {
2672
- description: value.description ?? "",
2673
- name: value.name
2674
- };
2675
- if (value.deprecationReason !== null && value.deprecationReason !== undefined) {
2676
- member.deprecationReason = value.deprecationReason;
2677
- }
2678
- return member;
2679
- });
2680
- var objectDef = (type, kind) => {
2681
- const interfaces = type.getInterfaces().map((iface) => iface.name);
2682
- const def = {
2683
- description: type.description ?? "",
2684
- fields: Object.values(type.getFields()).map(outputField),
2685
- kind,
2686
- name: type.name
2687
- };
2688
- if (interfaces.length > 0) {
2689
- def.interfaces = interfaces;
2690
- }
2691
- return def;
2692
- };
2693
- var inputDef = (type) => ({
2694
- description: type.description ?? "",
2695
- inputFields: Object.values(type.getFields()).map(inputValue),
2696
- kind: "input",
2697
- name: type.name
2698
- });
2699
- var unionDef = (type) => ({
2700
- description: type.description ?? "",
2701
- kind: "union",
2702
- name: type.name,
2703
- possibleTypes: type.getTypes().map((member) => member.name)
2704
- });
2705
- var scalarDef = (type) => {
2706
- const def = {
2707
- description: type.description ?? "",
2708
- kind: "scalar",
2709
- name: type.name
2710
- };
2711
- if (type.specifiedByURL !== null && type.specifiedByURL !== undefined) {
2712
- def.specifiedByUrl = type.specifiedByURL;
2713
- }
2714
- if (isSpecifiedScalarType(type)) {
2715
- def.builtIn = true;
2716
- }
2717
- return def;
2718
- };
2719
- var typeDef = (type, schema) => {
2720
- if (isObjectType(type)) {
2721
- return objectDef(type, "object");
2722
- }
2723
- if (isInterfaceType(type)) {
2724
- const def = objectDef(type, "interface");
2725
- const implementers = schema.getImplementations(type).objects.map((object) => object.name);
2726
- if (implementers.length > 0) {
2727
- def.possibleTypes = implementers;
1409
+ case "asset-hyperlink": {
1410
+ return renderLink(renderInlines(children(node), options), linkedAsset(node, options)?.url);
2728
1411
  }
2729
- return def;
2730
- }
2731
- if (isInputObjectType(type)) {
2732
- return inputDef(type);
2733
- }
2734
- if (isEnumType(type)) {
2735
- return {
2736
- description: type.description ?? "",
2737
- enumValues: enumValues(type),
2738
- kind: "enum",
2739
- name: type.name
2740
- };
2741
- }
2742
- if (isUnionType(type)) {
2743
- return unionDef(type);
2744
- }
2745
- return scalarDef(type);
2746
- };
2747
- var documentOf = (schema) => {
2748
- const types = {};
2749
- for (const [name, type] of Object.entries(schema.getTypeMap())) {
2750
- if (name.startsWith("__")) {
2751
- continue;
2752
- }
2753
- types[name] = typeDef(type, schema);
2754
- }
2755
- const roots = {};
2756
- const query = schema.getQueryType();
2757
- const mutation = schema.getMutationType();
2758
- const subscription = schema.getSubscriptionType();
2759
- if (query) {
2760
- roots.query = query.name;
2761
- }
2762
- if (mutation) {
2763
- roots.mutation = mutation.name;
2764
- }
2765
- if (subscription) {
2766
- roots.subscription = subscription.name;
2767
- }
2768
- return {
2769
- info: { description: schema.description ?? "" },
2770
- roots,
2771
- types
2772
- };
2773
- };
2774
- var hasSchemaKey = (value) => typeof value === "object" && value !== null && ("__schema" in value);
2775
- var hasDataEnvelope = (value) => typeof value === "object" && value !== null && ("data" in value);
2776
- var introspectionOf = (parsed) => {
2777
- if (hasSchemaKey(parsed)) {
2778
- return parsed;
2779
- }
2780
- return hasDataEnvelope(parsed) && hasSchemaKey(parsed.data) ? parsed.data : undefined;
2781
- };
2782
- var buildGraphqlDocument = (text) => {
2783
- let parsed;
2784
- let isJson = true;
2785
- try {
2786
- parsed = JSON.parse(text);
2787
- } catch {
2788
- isJson = false;
2789
- }
2790
- if (isJson) {
2791
- const introspection = introspectionOf(parsed);
2792
- if (!introspection) {
2793
- throw new Error("the JSON document is not a GraphQL introspection result (no `__schema` key).");
1412
+ case "embedded-entry-inline": {
1413
+ return embeddedEntry(node, options);
2794
1414
  }
2795
- return documentOf(buildClientSchema(introspection));
2796
- }
2797
- return documentOf(buildSchema(text));
2798
- };
2799
-
2800
- // src/openapi/parse.ts
2801
- var URL_SPEC = /^https?:\/\//u;
2802
- var FETCH_TIMEOUT_MS = 15000;
2803
- var MAX_ATTEMPTS = 3;
2804
- var BASE_BACKOFF_MS = 500;
2805
- var MAX_RETRY_WAIT_MS = 1e4;
2806
- var SECOND_MS = 1000;
2807
- var RETRYABLE_STATUS = new Set([408, 425, 429, 500, 502, 503, 504]);
2808
- var USER_AGENT = "blume (+https://github.com/haydenbleasel/blume)";
2809
- var PROXY_ENV_VARS = [
2810
- "HTTP_PROXY",
2811
- "http_proxy",
2812
- "HTTPS_PROXY",
2813
- "https_proxy",
2814
- "ALL_PROXY",
2815
- "all_proxy"
2816
- ];
2817
-
2818
- class InvalidSpecError extends Error {
2819
- constructor(message) {
2820
- super(message);
2821
- this.name = "InvalidSpecError";
2822
- }
2823
- }
2824
- var proxyInstalled = false;
2825
- var ensureProxyDispatcher = async () => {
2826
- if (proxyInstalled || !PROXY_ENV_VARS.some((name) => process.env[name])) {
2827
- return;
2828
- }
2829
- try {
2830
- const { EnvHttpProxyAgent, setGlobalDispatcher } = await import("undici");
2831
- setGlobalDispatcher(new EnvHttpProxyAgent);
2832
- proxyInstalled = true;
2833
- } catch {}
2834
- };
2835
- var retryAfterMs = (response) => {
2836
- const header = response.headers.get("retry-after");
2837
- if (!header) {
2838
- return;
2839
- }
2840
- const seconds = Number(header);
2841
- if (Number.isFinite(seconds)) {
2842
- return seconds > 0 ? seconds * SECOND_MS : undefined;
2843
- }
2844
- const delta = Date.parse(header) - Date.now();
2845
- return Number.isFinite(delta) && delta > 0 ? delta : undefined;
2846
- };
2847
- var attemptFetch = async (spec) => {
2848
- try {
2849
- const response = await fetch(spec, {
2850
- headers: {
2851
- accept: "application/json, application/yaml, text/yaml, */*",
2852
- "user-agent": USER_AGENT
2853
- },
2854
- redirect: "follow",
2855
- signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
2856
- });
2857
- if (response.ok) {
2858
- return { text: await response.text() };
1415
+ default: {
1416
+ return renderInlines(children(node), options);
2859
1417
  }
2860
- return {
2861
- error: new Error(`${spec} -> ${response.status} ${response.statusText}`),
2862
- retryAfter: retryAfterMs(response),
2863
- retryable: RETRYABLE_STATUS.has(response.status)
2864
- };
2865
- } catch (error) {
2866
- return {
2867
- error: error instanceof Error ? error : new Error(String(error)),
2868
- retryable: true
2869
- };
2870
1418
  }
2871
1419
  };
2872
- var retryableFetchError = (error, retryAfter) => {
2873
- const wrapper = new Error(error.message, {
2874
- cause: error
2875
- });
2876
- wrapper.name = "RetryableFetchError";
2877
- wrapper.retryAfter = retryAfter;
2878
- return wrapper;
2879
- };
2880
- var fetchSpecText = async (spec) => {
2881
- await ensureProxyDispatcher();
2882
- return await pRetry(async () => {
2883
- const attempt = await attemptFetch(spec);
2884
- if ("text" in attempt) {
2885
- return attempt.text;
2886
- }
2887
- if (!attempt.retryable) {
2888
- throw new AbortError(attempt.error);
2889
- }
2890
- throw retryableFetchError(attempt.error, attempt.retryAfter);
2891
- }, {
2892
- factor: 2,
2893
- maxTimeout: MAX_RETRY_WAIT_MS,
2894
- minTimeout: BASE_BACKOFF_MS,
2895
- onFailedAttempt: async (context) => {
2896
- const { retryAfter } = context.error;
2897
- if (retryAfter !== undefined && context.retriesLeft > 0) {
2898
- await sleep(Math.max(0, Math.min(retryAfter, MAX_RETRY_WAIT_MS) - context.retryDelay));
2899
- }
2900
- },
2901
- retries: MAX_ATTEMPTS - 1
2902
- });
2903
- };
2904
- var cacheFileFor = (cacheDir, spec) => join3(cacheDir, `spec-${hashText(spec)}.cache`);
2905
- var readCache = async (file) => {
2906
- try {
2907
- return await readFile3(file, "utf-8");
2908
- } catch {
2909
- return;
1420
+ var renderList = (items, marker, options) => items.map((item, index) => listItem(marker(index), children(item).map((child) => renderBlock(child, options)).filter((block) => block !== "").join(`
1421
+ `))).join(`
1422
+ `);
1423
+ var cellText = (cell, options) => children(cell).map((paragraph) => renderInlines(children(paragraph), options)).join(" ").replaceAll(CELL_BREAK, " ").replaceAll(CELL_PIPE, String.raw`\|`);
1424
+ var tableRow = (cells) => `| ${cells.join(" | ")} |`;
1425
+ var renderTable = (node, options) => {
1426
+ const rows = children(node).map((row) => children(row).map((cell) => cellText(cell, options)));
1427
+ const [head, ...body] = rows;
1428
+ if (!head) {
1429
+ return "";
2910
1430
  }
1431
+ return [
1432
+ tableRow(head),
1433
+ tableRow(head.map(() => "---")),
1434
+ ...body.map((cells) => tableRow(cells))
1435
+ ].join(`
1436
+ `);
2911
1437
  };
2912
- var writeCache = async (dir, file, text) => {
2913
- try {
2914
- await mkdir2(dir, { recursive: true });
2915
- await writeFile2(file, text, "utf-8");
2916
- } catch {}
2917
- };
2918
- var readSpecText = async (spec, root, options) => {
2919
- if (!URL_SPEC.test(spec)) {
2920
- const absolute = isAbsolute(spec) ? spec : join3(root, spec);
2921
- return { text: await readFile3(absolute, "utf-8"), warnings: [] };
1438
+ var renderBlock = (node, options) => {
1439
+ const type = asString(node.nodeType) ?? "";
1440
+ const heading = HEADING.exec(type)?.groups?.level;
1441
+ if (heading) {
1442
+ return `${headingPrefix(Number(heading))}${renderInlines(children(node), options)}`;
2922
1443
  }
2923
- const cacheFile = options.cacheDir ? cacheFileFor(options.cacheDir, spec) : undefined;
2924
- if (cacheFile && options.refresh === false) {
2925
- const cached = await readCache(cacheFile);
2926
- if (cached !== undefined) {
2927
- return { text: cached, warnings: [] };
2928
- }
2929
- }
2930
- try {
2931
- const text = await fetchSpecText(spec);
2932
- if (options.cacheDir && cacheFile) {
2933
- await writeCache(options.cacheDir, cacheFile, text);
1444
+ switch (type) {
1445
+ case "paragraph": {
1446
+ return renderInlines(children(node), options);
2934
1447
  }
2935
- return { text, warnings: [] };
2936
- } catch (error) {
2937
- if (cacheFile) {
2938
- const cached = await readCache(cacheFile);
2939
- if (cached !== undefined) {
2940
- return {
2941
- text: cached,
2942
- warnings: [
2943
- `Could not fetch ${spec} (${error.message}); using the last cached copy.`
2944
- ]
2945
- };
2946
- }
1448
+ case "blockquote": {
1449
+ return blockquote(joinBlocks(children(node).map((child) => renderBlock(child, options))));
2947
1450
  }
2948
- throw error;
2949
- }
2950
- };
2951
- var isApiDocument = (value) => typeof value === "object" && value !== null;
2952
- var parseSpec = async (spec, root, options = {}) => {
2953
- const { text, warnings } = await readSpecText(spec, root, options);
2954
- const normalized = normalize2(text);
2955
- const { specification } = upgrade(normalized);
2956
- if (!isApiDocument(specification)) {
2957
- throw new InvalidSpecError(`${spec} is not a valid OpenAPI document (expected a YAML or JSON object).`);
2958
- }
2959
- return { document: specification, warnings };
2960
- };
2961
- var isAsyncApiDocument = (value) => typeof value === "object" && value !== null && ("asyncapi" in value) && typeof value.asyncapi === "string" && value.asyncapi !== "";
2962
- var parseAsyncApiSpec = async (spec, root, options = {}) => {
2963
- const { text, warnings } = await readSpecText(spec, root, options);
2964
- const normalized = normalize2(text);
2965
- if (!isAsyncApiDocument(normalized)) {
2966
- throw new InvalidSpecError(`${spec} is not a valid AsyncAPI document (expected a YAML or JSON object with an \`asyncapi\` version field).`);
2967
- }
2968
- const version = normalized.asyncapi;
2969
- let document = normalized;
2970
- if (!version.startsWith("3.")) {
2971
- const captured = [];
2972
- const originalWarn = console.warn;
2973
- console.warn = (...args) => {
2974
- captured.push(args.map(String).join(" "));
2975
- };
2976
- try {
2977
- document = convert(document, "3.0.0");
2978
- } catch (error) {
2979
- throw new InvalidSpecError(`${spec} could not be converted to AsyncAPI 3.0 (${error.message}).`);
2980
- } finally {
2981
- console.warn = originalWarn;
1451
+ case "unordered-list": {
1452
+ return renderList(children(node), () => "-", options);
2982
1453
  }
2983
- warnings.push(...captured.map((message) => `Converting ${spec} to AsyncAPI 3.0: ${message}`));
2984
- }
2985
- return { document: normalizeAsyncApiDocument(document), warnings };
2986
- };
2987
- var parseGraphqlSpec = async (spec, root, options = {}) => {
2988
- const { text, warnings } = await readSpecText(spec, root, options);
2989
- try {
2990
- return { document: buildGraphqlDocument(text), warnings };
2991
- } catch (error) {
2992
- throw new InvalidSpecError(`${spec} is not a valid GraphQL schema (${error.message.trim()})`);
2993
- }
2994
- };
2995
-
2996
- // src/openapi/render-mdx.ts
2997
- import { fromMarkdown } from "mdast-util-from-markdown";
2998
- import { toString as mdastToString } from "mdast-util-to-string";
2999
- import stringWidth2 from "string-width";
3000
-
3001
- // src/core/api-name.ts
3002
- var NAMES_AN_API = /\bAPIs?\b/iu;
3003
- var apiNamePhrase = (name) => {
3004
- const trimmed = name.trim();
3005
- return NAMES_AN_API.test(trimmed) ? trimmed : `${trimmed} API`;
3006
- };
3007
-
3008
- // src/core/text-width.ts
3009
- import stringWidth from "string-width";
3010
- var columnsPrefix = (text, max) => {
3011
- let end = 0;
3012
- let used = 0;
3013
- const graphemes = new Intl.Segmenter(undefined, { granularity: "grapheme" });
3014
- for (const { index, segment } of graphemes.segment(text)) {
3015
- const width = stringWidth(segment);
3016
- if (used + width > max) {
3017
- break;
1454
+ case "ordered-list": {
1455
+ return renderList(children(node), (index) => `${index + 1}.`, options);
3018
1456
  }
3019
- used += width;
3020
- end = index + segment.length;
3021
- }
3022
- return text.slice(0, end);
3023
- };
3024
-
3025
- // src/openapi/signature.ts
3026
- var operationSignature = (spec, operation) => spec.kind === "graphql" ? graphqlSignature(operation) : `${operation.method.toUpperCase()} ${operation.path}`;
3027
-
3028
- // src/openapi/render-mdx.ts
3029
- var MDX_UNSAFE = /[<{}]/gu;
3030
- var ENTITIES = new Map([
3031
- ["<", "&lt;"],
3032
- ["{", "&#123;"],
3033
- ["}", "&#125;"]
3034
- ]);
3035
- var MDX_ESM_KEYWORD = /^(?<keyword>import|export)\b/gmu;
3036
- var escapeProse = (text) => text.replace(MDX_UNSAFE, (char) => ENTITIES.get(char) ?? char).replace(MDX_ESM_KEYWORD, (keyword) => `&#${keyword.codePointAt(0)};${keyword.slice(1)}`);
3037
- var codeSpans = (text) => {
3038
- const spans = [];
3039
- const collect = (node) => {
3040
- if (node.type === "inlineCode" || node.type === "code") {
3041
- const start = node.position?.start.offset ?? -1;
3042
- const end = node.position?.end.offset ?? -1;
3043
- const head = text.slice(Math.max(start, 0), Math.max(end, 0)).trimStart();
3044
- if (start >= 0 && (node.type === "inlineCode" || head.startsWith("`") || head.startsWith("~"))) {
3045
- spans.push([start, end]);
3046
- }
3047
- return;
1457
+ case "hr": {
1458
+ return "---";
3048
1459
  }
3049
- if ("children" in node) {
3050
- for (const child of node.children) {
3051
- collect(child);
3052
- }
1460
+ case "embedded-asset-block": {
1461
+ const asset = linkedAsset(node, options);
1462
+ return asset ? image(asset.description ?? asset.title ?? "", asset.url) : "";
3053
1463
  }
3054
- };
3055
- collect(fromMarkdown(text));
3056
- return spans;
3057
- };
3058
- var mdxSafe = (text) => {
3059
- let out = "";
3060
- let cursor = 0;
3061
- for (const [start, end] of codeSpans(text)) {
3062
- out += escapeProse(text.slice(cursor, start));
3063
- out += text.slice(start, end);
3064
- cursor = end;
3065
- }
3066
- return out + escapeProse(text.slice(cursor));
3067
- };
3068
- var META_DESCRIPTION_MAX = 160;
3069
- var WHITESPACE = /\s+/gu;
3070
- var TRAILING_WORD = /\s+\S*$/u;
3071
- var plainProse = (markdown) => {
3072
- const tree = fromMarkdown(markdown);
3073
- const first = tree.children.find((node) => node.type === "paragraph") ?? tree.children[0];
3074
- return first ? mdastToString(first).replace(WHITESPACE, " ").trim() : "";
3075
- };
3076
- var clip = (text, max) => {
3077
- if (max <= 0) {
3078
- return "";
3079
- }
3080
- if (stringWidth2(text) <= max) {
3081
- return text;
3082
- }
3083
- const head = columnsPrefix(text, max - 1);
3084
- const onWordBoundary = head.replace(TRAILING_WORD, "");
3085
- return `${stringWidth2(onWordBoundary) >= max / 2 ? onWordBoundary : head}…`;
3086
- };
3087
- var apiName = (spec) => spec.title || spec.label;
3088
- var apiPhrase = (spec) => apiNamePhrase(apiName(spec));
3089
- var GRAPHQL_MEMBER_PHRASES = {
3090
- enum: "enum type",
3091
- input: "input object type",
3092
- interface: "interface type",
3093
- mutation: "mutation",
3094
- object: "object type",
3095
- query: "query",
3096
- scalar: "scalar type",
3097
- subscription: "subscription",
3098
- union: "union type"
3099
- };
3100
- var operationDescription = (spec, operation, options) => {
3101
- if (!options.suffix) {
3102
- return clip(plainProse(operation.description || operation.summary) || options.title, META_DESCRIPTION_MAX);
3103
- }
3104
- let subject;
3105
- if (spec.kind === "asyncapi") {
3106
- subject = `${operation.method} operation on ${operation.path}`;
3107
- } else if (spec.kind === "graphql") {
3108
- subject = `${operation.path} ${GRAPHQL_MEMBER_PHRASES[operation.method]}`;
3109
- } else {
3110
- subject = `${operation.method.toUpperCase()} ${operation.path} endpoint`;
3111
- }
3112
- const suffix = `Reference for the ${subject} in the ${apiPhrase(spec)}.`;
3113
- const prose = clip(plainProse(operation.description || operation.summary), META_DESCRIPTION_MAX - stringWidth2(suffix) - 1);
3114
- return clip([prose, suffix].filter(Boolean).join(" "), META_DESCRIPTION_MAX);
3115
- };
3116
- var withDescription = (description, component) => description.trim() ? `${mdxSafe(description.trim())}
3117
-
3118
- ${component}` : component;
3119
- var operationMdx = (spec, operation, reference) => {
3120
- const method = operation.method.toUpperCase();
3121
- const graphql = spec.kind === "graphql";
3122
- const fallbackTitle = graphql ? operation.path : operationSignature(spec, operation);
3123
- const title = operation.summary || fallbackTitle;
3124
- const description = operation.description.trim() === operation.summary.trim() ? "" : operation.description;
3125
- const flags = {};
3126
- if (reference?.includeInLlms === false) {
3127
- flags.ai = { exclude: true };
3128
- }
3129
- if (operation.deprecated) {
3130
- flags.deprecated = true;
3131
- }
3132
- const searchFlags = {};
3133
- if (reference?.includeInSearch === false) {
3134
- searchFlags.exclude = true;
3135
- }
3136
- const seo = {
3137
- description: operationDescription(spec, operation, {
3138
- suffix: reference?.seoDescriptionSuffix !== false,
3139
- title
3140
- })
3141
- };
3142
- if (reference?.noindex) {
3143
- seo.noindex = true;
3144
- }
3145
- const sidebar = {
3146
- label: operation.summary || operation.path
3147
- };
3148
- if (!graphql || isGraphqlOperationKind(operation.method)) {
3149
- sidebar.badge = method;
3150
- }
3151
- const tags = graphql ? [operation.tag] : [operation.tag, method];
3152
- return {
3153
- body: withDescription(description, `<Operation source="${spec.slug}" id="${operation.key}" />`),
3154
- data: {
3155
- ...flags,
3156
- search: { ...searchFlags, tags },
3157
- seo,
3158
- sidebar,
3159
- title,
3160
- type: "openapi-operation"
3161
- }
3162
- };
3163
- };
3164
- var overviewMdx = (spec, reference) => {
3165
- const operations = Object.values(spec.operations);
3166
- const sections = [];
3167
- const known = new Set;
3168
- for (const tag of spec.tags) {
3169
- if (!known.has(tag.slug)) {
3170
- known.add(tag.slug);
3171
- sections.push(tag);
3172
- }
3173
- }
3174
- for (const operation of operations) {
3175
- if (!known.has(operation.tagSlug)) {
3176
- known.add(operation.tagSlug);
3177
- sections.push({
3178
- description: "",
3179
- name: operation.tag,
3180
- slug: operation.tagSlug
3181
- });
1464
+ case "embedded-entry-block": {
1465
+ return embeddedEntry(node, options);
3182
1466
  }
3183
- }
3184
- const tagSections = [];
3185
- for (const tag of sections) {
3186
- if (!operations.some((operation) => operation.tagSlug === tag.slug)) {
3187
- continue;
1467
+ case "table": {
1468
+ return renderTable(node, options);
1469
+ }
1470
+ default: {
1471
+ return unsupported(`Contentful node: ${type}`, writesMdx(options.serializers));
3188
1472
  }
3189
- const description = tag.description.trim() ? [mdxSafe(tag.description.trim())] : [];
3190
- tagSections.push([
3191
- `## ${mdxSafe(tag.name)}`,
3192
- ...description,
3193
- `<ApiTagOperations source="${spec.slug}" tag="${tag.slug}" />`
3194
- ].join(`
3195
-
3196
- `));
3197
- }
3198
- const flags = {};
3199
- if (reference?.includeInLlms === false) {
3200
- flags.ai = { exclude: true };
3201
- }
3202
- if (reference?.includeInSearch === false) {
3203
- flags.search = { exclude: true };
3204
- }
3205
- const seo = {
3206
- description: clip(plainProse(spec.description), META_DESCRIPTION_MAX) || `${apiPhrase(spec)} reference.`
3207
- };
3208
- if (reference?.noindex) {
3209
- seo.noindex = true;
3210
1473
  }
3211
- return {
3212
- body: [
3213
- withDescription(spec.description, `<ApiOverview source="${spec.slug}" />`),
3214
- ...tagSections
3215
- ].join(`
1474
+ };
1475
+ var contentfulRichTextToMarkdown = (doc, options = {}) => markdownDocument(children(doc).map((node) => renderBlock(node, options)));
3216
1476
 
3217
- `),
3218
- data: {
3219
- ...flags,
3220
- seo,
3221
- sidebar: { label: "Overview" },
3222
- title: apiName(spec)
1477
+ // src/core/sources/remote.ts
1478
+ import { join } from "pathe";
1479
+ var remoteSource = (options, ctx) => {
1480
+ const cache = snapshotCache(ctx?.cacheDir ?? join(".blume", "cache", options.name));
1481
+ let snapshot = new Map;
1482
+ const load = async (refresh = ctx?.refresh ?? true) => {
1483
+ const result = await loadWithCache(options.name, cache, options.fetchEntries, refresh);
1484
+ snapshot = new Map(result.entries.map((entry) => [entry.ref, entry]));
1485
+ return result;
1486
+ };
1487
+ const read = async (ref) => {
1488
+ const cached = snapshot.get(ref);
1489
+ if (cached) {
1490
+ return cached.raw ?? cached.body.text;
3223
1491
  }
1492
+ const all = await cache.read();
1493
+ return all.find((entry) => entry.ref === ref)?.raw ?? "";
1494
+ };
1495
+ return {
1496
+ load,
1497
+ name: options.name,
1498
+ prefix: options.prefix,
1499
+ read,
1500
+ staged: true,
1501
+ watch: options.pollInterval ? pollingWatch(() => load(true), options.pollInterval, () => load()) : undefined
3224
1502
  };
3225
1503
  };
3226
-
3227
- // src/openapi/source.ts
3228
- var isOpenApiSource = (source) => ("kind" in source) && source.kind === "openapi-source";
3229
- var routeToRef = (route) => route.replace(/^\/+/u, "");
3230
- var KIND_LABELS = {
3231
- asyncapi: "AsyncAPI",
3232
- graphql: "GraphQL",
3233
- openapi: "OpenAPI"
3234
- };
3235
- var CODE_PREFIXES = {
3236
- asyncapi: "BLUME_ASYNCAPI",
3237
- graphql: "BLUME_GRAPHQL",
3238
- openapi: "BLUME_OPENAPI"
3239
- };
3240
- var SPEC_WARNING_CODES = {
3241
- asyncapi: "BLUME_ASYNCAPI_SPEC_WARNING",
3242
- graphql: "BLUME_GRAPHQL_SPEC_WARNING",
3243
- openapi: "BLUME_OPENAPI_STALE"
3244
- };
3245
- var SKIPPED_CODES = {
3246
- asyncapi: "BLUME_ASYNCAPI_SKIPPED_OPERATION",
3247
- graphql: "BLUME_GRAPHQL_SKIPPED",
3248
- openapi: "BLUME_OPENAPI_REF_PATH_ITEM"
3249
- };
3250
- var EMPTY_SUGGESTIONS = {
3251
- asyncapi: "Check the spec points at an AsyncAPI document with `channels` and `operations`.",
3252
- graphql: "Check the spec points at a GraphQL schema (SDL or introspection JSON) whose root types declare fields.",
3253
- openapi: "Check the spec points at an OpenAPI document with operations under `paths`."
3254
- };
3255
- var INVALID_SUGGESTIONS = {
3256
- asyncapi: "Point the spec at an AsyncAPI document (a YAML or JSON file with an object at the top level).",
3257
- graphql: "Point the spec at a GraphQL schema — SDL text or an introspection JSON result.",
3258
- openapi: "Point the spec at an OpenAPI document (a YAML or JSON file with an object at the top level)."
3259
- };
3260
- var toEntry = (rendered, ref) => {
3261
- const raw = frontmatter_default.stringify(`${rendered.body}
3262
- `, rendered.data);
1504
+ var stagedEntry = (slug, data, markdown, lastModified, format = "md") => {
1505
+ const raw = frontmatter_default.stringify(markdown, data);
3263
1506
  return {
3264
- body: { format: "mdx", text: rendered.body },
3265
- data: { ...rendered.data },
1507
+ body: { format, text: markdown },
1508
+ data: { ...data },
3266
1509
  hash: hashText(raw),
1510
+ lastModified,
3267
1511
  raw,
3268
- ref
1512
+ ref: `${slug}.${format}`
3269
1513
  };
3270
1514
  };
3271
- var specEntries = (spec, operations, reference) => {
3272
- const entries = operations.map((operation) => toEntry(operationMdx(spec, operation, reference), `${routeToRef(operation.route)}.mdx`));
3273
- const base = routeToRef(spec.route);
3274
- entries.push(toEntry(overviewMdx(spec, reference), base ? `${base}/index.mdx` : "index.mdx"));
3275
- return entries;
3276
- };
3277
- var tagFolderMeta = (spec, tags) => {
3278
- const base = routeToRef(spec.route);
3279
- return Object.fromEntries(tags.map((tag) => [
3280
- base ? `${base}/${tag.slug}` : tag.slug,
3281
- { title: tag.name }
3282
- ]));
1515
+ var REMOTE_TIMEOUT_MS = 30000;
1516
+ var fetchJson = async (url, client) => {
1517
+ const doFetch = client.fetchImpl ?? globalThis.fetch;
1518
+ const res = await doFetch(url, {
1519
+ headers: { accept: "application/json", ...client.headers },
1520
+ signal: AbortSignal.timeout(REMOTE_TIMEOUT_MS)
1521
+ });
1522
+ if (!res.ok) {
1523
+ throw new Error(`${url} responded ${res.status} ${res.statusText}`.trim());
1524
+ }
1525
+ return await res.json();
3283
1526
  };
3284
- var parseReference = async (reference, ctx) => {
3285
- const options = { cacheDir: ctx.cacheDir, refresh: ctx.refresh };
3286
- if (reference.kind === "asyncapi") {
3287
- const { document, warnings } = await parseAsyncApiSpec(reference.spec, ctx.projectRoot, options);
3288
- const extracted = extractAsyncApiOperations(document, reference.route);
3289
- return {
3290
- document,
3291
- extractWarnings: extracted.warnings,
3292
- operations: extracted.operations,
3293
- tags: extracted.tags,
3294
- warnings
3295
- };
1527
+ var queryString = (params) => {
1528
+ const search = new URLSearchParams;
1529
+ for (const [key, value] of Object.entries(params)) {
1530
+ if (value !== undefined) {
1531
+ search.set(key, value);
1532
+ }
3296
1533
  }
3297
- if (reference.kind === "graphql") {
3298
- const { document, warnings } = await parseGraphqlSpec(reference.spec, ctx.projectRoot, options);
3299
- const extracted = extractGraphqlOperations(document, reference.route);
3300
- return {
3301
- document,
3302
- extractWarnings: extracted.warnings,
3303
- operations: extracted.operations,
3304
- tags: extracted.tags,
3305
- warnings
3306
- };
1534
+ return search.toString();
1535
+ };
1536
+ var documentEntry = (doc, fields, id, lower, draft = false, lowersToMdx = false) => {
1537
+ const slugValue = asString(getPath(doc, fields.slug)) ?? id;
1538
+ const slug = slugifyPath(slugValue) || slugify(id) || "untitled";
1539
+ const data = {};
1540
+ const title = asString(getPath(doc, fields.title));
1541
+ const description = asString(getPath(doc, fields.description));
1542
+ if (title) {
1543
+ data.title = title;
3307
1544
  }
3308
- const { document, warnings } = await parseSpec(reference.spec, ctx.projectRoot, options);
3309
- const extracted = extractOperations(document, reference.route);
3310
- return {
3311
- document,
3312
- extractWarnings: extracted.warnings,
3313
- operations: extracted.operations,
3314
- tags: extracted.tags,
3315
- warnings
3316
- };
1545
+ if (description) {
1546
+ data.description = description;
1547
+ }
1548
+ if (draft) {
1549
+ data.draft = true;
1550
+ }
1551
+ const body = getPath(doc, fields.body);
1552
+ let markdown = "";
1553
+ let format = "md";
1554
+ if (isStringValue2(body)) {
1555
+ markdown = `${body.trimEnd()}
1556
+ `;
1557
+ } else if (body !== undefined) {
1558
+ markdown = lower(body);
1559
+ format = lowersToMdx ? "mdx" : "md";
1560
+ }
1561
+ return stagedEntry(slug, data, markdown, asString(getPath(doc, fields.lastModified)), format);
3317
1562
  };
3318
- var openApiSource = (references, ctx) => {
3319
- let parsed = {};
3320
- const loadReference = async (reference) => {
3321
- const kindLabel = KIND_LABELS[reference.kind];
3322
- const codePrefix = CODE_PREFIXES[reference.kind];
3323
- try {
3324
- const { document, warnings, operations, tags, extractWarnings } = await parseReference(reference, ctx);
3325
- const info = document.info ?? { title: reference.label, version: "" };
3326
- const { proxy: configuredProxy } = reference.display.playground;
3327
- let proxy = false;
3328
- if (configuredProxy === true) {
3329
- proxy = withBasePath(reference.basePath, "/_api-proxy");
3330
- } else if (configuredProxy !== false && configuredProxy !== "") {
3331
- proxy = configuredProxy;
3332
- }
3333
- const spec = {
3334
- codeSamples: reference.display.codeSamples,
3335
- description: info.description ?? "",
3336
- document,
3337
- expandSchemas: reference.display.expandSchemas,
3338
- kind: reference.kind,
3339
- label: reference.label,
3340
- operations: Object.fromEntries(operations.map((operation) => [
3341
- operation.key,
3342
- {
3343
- ...operation,
3344
- route: withBasePath(reference.basePath, operation.route)
3345
- }
3346
- ])),
3347
- playground: { enabled: reference.display.playground.enabled, proxy },
3348
- route: reference.route,
3349
- slug: reference.slug,
3350
- tags,
3351
- title: info.title ?? reference.label,
3352
- version: info.version ?? ""
3353
- };
3354
- if (reference.endpoint !== undefined) {
3355
- spec.endpoint = reference.endpoint;
3356
- }
3357
- return {
3358
- diagnostics: [
3359
- ...warnings.map((message) => ({
3360
- code: SPEC_WARNING_CODES[reference.kind],
3361
- message,
3362
- severity: "warning"
3363
- })),
3364
- ...extractWarnings.map((message) => ({
3365
- code: SKIPPED_CODES[reference.kind],
3366
- message: `In ${kindLabel} spec "${reference.spec}": ${message}`,
3367
- severity: "warning"
3368
- })),
3369
- ...operations.length === 0 ? [
3370
- {
3371
- code: `${codePrefix}_EMPTY`,
3372
- message: `${kindLabel} spec "${reference.spec}" for ${reference.route} declares no operations; its API reference is empty.`,
3373
- severity: "warning",
3374
- suggestion: EMPTY_SUGGESTIONS[reference.kind]
3375
- }
3376
- ] : []
3377
- ],
3378
- entries: specEntries(spec, operations, reference),
3379
- folderMeta: tagFolderMeta(spec, tags),
3380
- slug: reference.slug,
3381
- spec
3382
- };
3383
- } catch (error) {
3384
- return {
3385
- code: `${codePrefix}_UNAVAILABLE`,
3386
- message: `Could not load ${kindLabel} spec "${reference.spec}" for ${reference.route} (${error.message}); its reference pages were skipped.`,
3387
- severity: ctx.mode === "build" ? "error" : "warning",
3388
- suggestion: error instanceof InvalidSpecError ? INVALID_SUGGESTIONS[reference.kind] : "Check the spec URL/path is reachable from the build environment; behind a proxy, set HTTP(S)_PROXY."
3389
- };
3390
- }
1563
+
1564
+ // src/core/sources/contentful.ts
1565
+ var DELIVERY_HOST = "https://cdn.contentful.com";
1566
+ var PREVIEW_HOST = "https://preview.contentful.com";
1567
+ var PAGE_SIZE = 100;
1568
+ var DEFAULT_FIELDS = {
1569
+ body: "body",
1570
+ description: "description",
1571
+ lastModified: "sys.updatedAt",
1572
+ slug: "slug",
1573
+ title: "title"
1574
+ };
1575
+ var byId = (objects) => new Map(objects.map((object) => [asString(getPath(object, "sys.id")) ?? "", object]));
1576
+ var contentfulSource = (options, ctx) => {
1577
+ const fields = { ...DEFAULT_FIELDS, ...options.fields };
1578
+ const toEntry = (item, assets, linked) => {
1579
+ const sys = asObject(item.sys) ?? {};
1580
+ const id = asString(sys.id) ?? "";
1581
+ const view = { ...asObject(item.fields), sys };
1582
+ return documentEntry(view, fields, id, (body) => isJsonObject(body) ? contentfulRichTextToMarkdown(body, {
1583
+ resolveAsset: (assetId) => {
1584
+ const asset = assets.get(assetId);
1585
+ return asset ? assetFromEntry(asset) : null;
1586
+ },
1587
+ resolveEntry: (entryId) => linked.get(entryId) ?? null,
1588
+ serializers: options.serializers
1589
+ }) : "", false, writesMdx(options.serializers));
3391
1590
  };
3392
- const load = async () => {
3393
- const results = await Promise.all(references.map(loadReference));
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.");
1597
+ }
1598
+ const token = preview ? previewToken : deliveryToken;
1599
+ const client = {
1600
+ fetchImpl: options.fetchImpl,
1601
+ headers: token ? { authorization: `Bearer ${token}` } : {}
1602
+ };
1603
+ const host = preview ? PREVIEW_HOST : DELIVERY_HOST;
1604
+ const base = `${host}/spaces/${options.space}/environments/${options.environment ?? "master"}/entries`;
3394
1605
  const entries = [];
3395
- const diagnostics = references.flatMap((reference) => (reference.collisions ?? []).map((message) => ({
3396
- code: "BLUME_OPENAPI_ROUTE_COLLISION",
3397
- message,
3398
- severity: "warning"
3399
- })));
3400
- const data = {};
3401
- const folderMeta = {};
3402
- for (const result of results) {
3403
- if ("severity" in result) {
3404
- diagnostics.push(result);
3405
- continue;
1606
+ let skip = 0;
1607
+ let more = true;
1608
+ while (more) {
1609
+ const query = queryString({
1610
+ ...options.params,
1611
+ content_type: options.contentType,
1612
+ include: "2",
1613
+ limit: String(PAGE_SIZE),
1614
+ locale: options.locale,
1615
+ skip: String(skip)
1616
+ });
1617
+ const page = asObject(await fetchJson(`${base}?${query}`, client));
1618
+ if (!page) {
1619
+ throw new Error("Contentful returned a non-object response");
3406
1620
  }
3407
- data[result.slug] = result.spec;
3408
- entries.push(...result.entries);
3409
- Object.assign(folderMeta, result.folderMeta);
3410
- diagnostics.push(...result.diagnostics);
1621
+ const assets = byId(objectsIn(getPath(page, "includes.Asset")));
1622
+ const linked = byId(objectsIn(getPath(page, "includes.Entry")));
1623
+ const items = objectsIn(page.items);
1624
+ for (const item of items) {
1625
+ entries.push(toEntry(item, assets, linked));
1626
+ }
1627
+ skip += items.length;
1628
+ more = items.length > 0 && skip < (asNumber(page.total) ?? 0);
3411
1629
  }
3412
- parsed = data;
3413
- return { diagnostics, entries, folderMeta };
3414
- };
3415
- return {
3416
- kind: "openapi-source",
3417
- load,
3418
- name: "openapi",
3419
- openApiData: () => parsed,
3420
- staged: true
1630
+ return entries;
3421
1631
  };
1632
+ return remoteSource({
1633
+ fetchEntries,
1634
+ name: options.name,
1635
+ pollInterval: options.pollInterval,
1636
+ prefix: options.prefix
1637
+ }, ctx);
3422
1638
  };
3423
1639
 
3424
1640
  // src/core/sources/filesystem.ts
3425
- import { existsSync as existsSync2, watch as fsWatch } from "node:fs";
3426
- import { readFile as readFile4 } from "node:fs/promises";
3427
- import { extname as extname4, isAbsolute as isAbsolute2, join as join4, relative as relative4, resolve as resolve2 } from "pathe";
1641
+ import { existsSync, watch as fsWatch } from "node:fs";
1642
+ import { readFile } from "node:fs/promises";
1643
+ import { extname as extname2, isAbsolute, join as join2, relative as relative3, resolve } from "pathe";
3428
1644
  import { glob as glob2 } from "tinyglobby";
3429
-
3430
- // src/core/sources/watch.ts
3431
- var BLUME_IGNORE_DIRS = [
3432
- ".blume",
3433
- ".blume-verify",
3434
- ".cache",
3435
- ".git",
3436
- ".next",
3437
- ".turbo",
3438
- ".vercel",
3439
- "dist",
3440
- "node_modules"
3441
- ];
3442
- var baselineScanIgnore = () => BLUME_IGNORE_DIRS.map((dir) => `**/${dir}/**`);
3443
- var BLUME_WATCH_IGNORE_DIRS = BLUME_IGNORE_DIRS;
3444
- var excludeDirSegments = (patterns) => patterns.map((pattern) => /^(?<dir>[^*/]+)\/\*\*$/u.exec(pattern)?.groups?.dir).filter((dir) => dir !== undefined);
3445
- var ignoringWatchListener = (onChange, ignoreDirs = BLUME_WATCH_IGNORE_DIRS, ignoreSegment = () => false) => {
3446
- const ignore = new Set(ignoreDirs);
3447
- return (_event, filename) => {
3448
- if (filename !== null && filename.split(/[/\\]/u).some((segment) => ignore.has(segment) || ignoreSegment(segment))) {
3449
- return;
3450
- }
3451
- onChange();
3452
- };
3453
- };
3454
-
3455
- // src/core/sources/filesystem.ts
3456
1645
  var filesystemSource = (options) => {
3457
- const contentRoot = isAbsolute2(options.root) ? options.root : join4(resolve2(options.projectRoot), options.root);
1646
+ const contentRoot = isAbsolute(options.root) ? options.root : join2(resolve(options.projectRoot), options.root);
3458
1647
  const load = async () => {
3459
1648
  const files = await glob2(options.include, {
3460
1649
  absolute: true,
@@ -3464,22 +1653,22 @@ var filesystemSource = (options) => {
3464
1653
  });
3465
1654
  files.sort();
3466
1655
  const entries = await Promise.all(files.map(async (file) => {
3467
- const source = await readFile4(file, "utf-8");
3468
- const ext = extname4(file).toLowerCase();
1656
+ const source = await readFile(file, "utf-8");
1657
+ const ext = extname2(file).toLowerCase();
3469
1658
  const format = ext === ".mdx" ? "mdx" : "md";
3470
1659
  const parsed = frontmatter_default(source);
3471
1660
  return {
3472
1661
  body: { format, text: parsed.content },
3473
1662
  data: parsed.data,
3474
1663
  raw: source,
3475
- ref: relative4(contentRoot, file),
1664
+ ref: relative3(contentRoot, file),
3476
1665
  sourcePath: file
3477
1666
  };
3478
1667
  }));
3479
1668
  return { diagnostics: [], entries };
3480
1669
  };
3481
1670
  const validate = () => {
3482
- if (!existsSync2(contentRoot)) {
1671
+ if (!existsSync(contentRoot)) {
3483
1672
  throw new BlumeError({
3484
1673
  code: options.missingCode ?? "BLUME_CONTENT_ROOT_MISSING",
3485
1674
  file: contentRoot,
@@ -3494,7 +1683,7 @@ var filesystemSource = (options) => {
3494
1683
  ...excludeDirSegments(options.exclude)
3495
1684
  ]);
3496
1685
  const watch = (onChange) => {
3497
- if (!existsSync2(contentRoot)) {
1686
+ if (!existsSync(contentRoot)) {
3498
1687
  return () => {};
3499
1688
  }
3500
1689
  const watcher = fsWatch(contentRoot, { recursive: true }, ignoringWatchListener(onChange, watchIgnoreDirs));
@@ -3505,7 +1694,7 @@ var filesystemSource = (options) => {
3505
1694
  load,
3506
1695
  name: options.name,
3507
1696
  prefix: options.prefix,
3508
- read: (ref) => readFile4(join4(contentRoot, ref), "utf-8"),
1697
+ read: (ref) => readFile(join2(contentRoot, ref), "utf-8"),
3509
1698
  staged: false,
3510
1699
  validate,
3511
1700
  watch
@@ -3513,11 +1702,11 @@ var filesystemSource = (options) => {
3513
1702
  };
3514
1703
 
3515
1704
  // src/core/sources/github-releases.ts
3516
- import { fromMarkdown as fromMarkdown2 } from "mdast-util-from-markdown";
1705
+ import { fromMarkdown } from "mdast-util-from-markdown";
3517
1706
  import { gfmFromMarkdown } from "mdast-util-gfm";
3518
- import { toString as mdastToString2 } from "mdast-util-to-string";
1707
+ import { toString as mdastToString } from "mdast-util-to-string";
3519
1708
  import { gfm } from "micromark-extension-gfm";
3520
- import stringWidth3 from "string-width";
1709
+ import stringWidth from "string-width";
3521
1710
  var DEFAULT_LIMIT = 100;
3522
1711
  var PER_PAGE = 100;
3523
1712
  var LEADING_V = /^v/iu;
@@ -3526,26 +1715,54 @@ var EDGE_DASHES = /^-+|-+$/gu;
3526
1715
  var DESCRIPTION_MAX = 160;
3527
1716
  var DESCRIPTION_MIN = 110;
3528
1717
  var CHANGESET_HASH = /^(?<mark>\s*(?:[-*+]|\d+[.)])\s+)[0-9a-f]{7,40}:\s+/gmu;
3529
- var WHITESPACE2 = /\s+/gu;
1718
+ var WHITESPACE = /\s+/gu;
3530
1719
  var TRAILING_FRAGMENT = /[\s,;:.—–-]+$/u;
3531
1720
  var NON_PROSE = new Set(["code", "heading", "html", "thematicBreak"]);
3532
1721
  var releaseDescription = (body) => {
3533
- const tree = fromMarkdown2(body.replaceAll(CHANGESET_HASH, "$<mark>"), {
1722
+ const tree = fromMarkdown(body.replaceAll(CHANGESET_HASH, "$<mark>"), {
3534
1723
  extensions: [gfm()],
3535
1724
  mdastExtensions: [gfmFromMarkdown()]
3536
1725
  });
3537
- const text = tree.children.filter((node) => !NON_PROSE.has(node.type)).map((node) => mdastToString2(node, { includeHtml: false, includeImageAlt: false })).join(" ").replaceAll(WHITESPACE2, " ").trim();
1726
+ const text = tree.children.filter((node) => !NON_PROSE.has(node.type)).map((node) => mdastToString(node, { includeHtml: false, includeImageAlt: false })).join(" ").replaceAll(WHITESPACE, " ").trim();
3538
1727
  if (!text) {
3539
1728
  return;
3540
1729
  }
3541
- if (stringWidth3(text) <= DESCRIPTION_MAX) {
1730
+ if (stringWidth(text) <= DESCRIPTION_MAX) {
3542
1731
  return text;
3543
1732
  }
3544
1733
  const slice = columnsPrefix(text, DESCRIPTION_MAX - 1);
3545
1734
  const boundary = slice.lastIndexOf(" ");
3546
- const head = (boundary !== -1 && stringWidth3(slice.slice(0, boundary)) >= DESCRIPTION_MIN ? slice.slice(0, boundary) : slice).replace(TRAILING_FRAGMENT, "");
1735
+ const head = (boundary !== -1 && stringWidth(slice.slice(0, boundary)) >= DESCRIPTION_MIN ? slice.slice(0, boundary) : slice).replace(TRAILING_FRAGMENT, "");
3547
1736
  return `${head}…`;
3548
1737
  };
1738
+ var headingsIn = (node) => {
1739
+ if (node.type === "heading") {
1740
+ return [node];
1741
+ }
1742
+ const found = [];
1743
+ if ("children" in node) {
1744
+ for (const child of node.children) {
1745
+ found.push(...headingsIn(child));
1746
+ }
1747
+ }
1748
+ return found;
1749
+ };
1750
+ var liftHeadings = (body) => {
1751
+ const headings = headingsIn(fromMarkdown(body, {
1752
+ extensions: [gfm()],
1753
+ mdastExtensions: [gfmFromMarkdown()]
1754
+ }));
1755
+ const lift = Math.min(...headings.map((heading) => heading.depth)) - 2;
1756
+ if (headings.length === 0 || lift <= 0) {
1757
+ return body;
1758
+ }
1759
+ let lifted = body;
1760
+ for (const heading of headings.toReversed()) {
1761
+ const marker = lifted.indexOf("#", heading.position?.start.offset);
1762
+ lifted = lifted.slice(0, marker) + lifted.slice(marker + lift);
1763
+ }
1764
+ return lifted;
1765
+ };
3549
1766
  var slugifyTag = (tag) => tag.toLowerCase().replaceAll(NON_SLUG, "-").replaceAll(EDGE_DASHES, "");
3550
1767
  var githubHeaders = () => {
3551
1768
  const headers = new Headers({ Accept: "application/vnd.github+json" });
@@ -3560,9 +1777,9 @@ var releaseToEntry = (release) => {
3560
1777
  const title = release.name?.trim() || release.tag_name;
3561
1778
  const date = release.published_at ?? release.created_at;
3562
1779
  const category = release.prerelease ? "Prerelease" : "Release";
3563
- const body = (release.body ?? "").replaceAll(`\r
1780
+ const body = liftHeadings(neutralizeUnsafeLinks((release.body ?? "").replaceAll(`\r
3564
1781
  `, `
3565
- `).trim();
1782
+ `)).trim());
3566
1783
  const description = releaseDescription(body);
3567
1784
  const data = {
3568
1785
  changelog: { category, version },
@@ -3648,6 +1865,7 @@ var githubReleasesSource = (options, ctx) => {
3648
1865
  };
3649
1866
  return {
3650
1867
  load,
1868
+ monolingual: true,
3651
1869
  name: options.name,
3652
1870
  prefix: options.prefix,
3653
1871
  read,
@@ -3800,36 +2018,22 @@ var mdxRemoteSource = (options, ctx) => {
3800
2018
  };
3801
2019
 
3802
2020
  // src/core/sources/notion.ts
3803
- import { setTimeout as sleep2 } from "node:timers/promises";
2021
+ import { setTimeout as sleep } from "node:timers/promises";
3804
2022
  import pLimit from "p-limit";
3805
- import { join as join6 } from "pathe";
3806
-
3807
- // src/components/content/youtube.ts
3808
- var BARE_ID = /^[\w-]{11}$/u;
3809
- var URL_ID = /(?:youtu\.be\/|\/embed\/|\/shorts\/|\/live\/|[?&]v=)(?<id>[\w-]{11})/u;
3810
- var parseYouTubeId = (input) => {
3811
- const value = input.trim();
3812
- if (!value) {
3813
- return null;
3814
- }
3815
- if (BARE_ID.test(value)) {
3816
- return value;
3817
- }
3818
- return URL_ID.exec(value)?.groups?.id ?? null;
3819
- };
2023
+ import { join as join4 } from "pathe";
3820
2024
 
3821
2025
  // src/core/sources/assets.ts
3822
2026
  import { randomUUID } from "node:crypto";
3823
2027
  import {
3824
2028
  access,
3825
- mkdir as mkdir3,
2029
+ mkdir,
3826
2030
  readdir,
3827
2031
  rename,
3828
2032
  rm,
3829
- writeFile as writeFile3
2033
+ writeFile
3830
2034
  } from "node:fs/promises";
3831
- import { extname as extname5, join as join5 } from "pathe";
3832
- var MD_IMAGE2 = /!\[(?<alt>[^\]]*)\]\((?<url>[^)\s]+)\)/gu;
2035
+ import { extname as extname3, join as join3 } from "pathe";
2036
+ var MD_IMAGE = /!\[(?<alt>[^\]]*)\]\((?<url>[^)\s]+)\)/gu;
3833
2037
  var HTML_VIDEO_SRC = /(?<open><video\b[^>]*?\ssrc=")(?<url>[^"]+)(?<close>")/gu;
3834
2038
  var REMOTE = /^https?:\/\//u;
3835
2039
  var SAFE_EXT = /^\.[a-z0-9]+$/iu;
@@ -3853,7 +2057,7 @@ var PART_SUFFIX = ".part";
3853
2057
  var DEFAULT_TIMEOUT_MS = 120000;
3854
2058
  var extFromUrl = (url) => {
3855
2059
  const clean = url.split("?")[0] ?? url;
3856
- const ext = extname5(clean);
2060
+ const ext = extname3(clean);
3857
2061
  return SAFE_EXT.test(ext) ? ext.toLowerCase() : null;
3858
2062
  };
3859
2063
  var mediaType = (res) => res.headers.get("content-type")?.split(";")[0]?.trim().toLowerCase() ?? "";
@@ -3878,6 +2082,7 @@ var completedFor = async (dir, stem) => {
3878
2082
  var materializeAssets = async (markdown, ctx) => {
3879
2083
  const doFetch = ctx.fetchImpl ?? globalThis.fetch;
3880
2084
  const limit = ctx.limit ?? ((task) => task());
2085
+ const inFlight = ctx.inFlight ?? new Map;
3881
2086
  const timeoutMs = ctx.timeoutMs ?? DEFAULT_TIMEOUT_MS;
3882
2087
  const diagnostics = [];
3883
2088
  const fences = [];
@@ -3886,7 +2091,7 @@ var materializeAssets = async (markdown, ctx) => {
3886
2091
  return `\x00blume-fence-${fences.length - 1}\x00`;
3887
2092
  });
3888
2093
  const urls = new Set;
3889
- for (const pattern of [MD_IMAGE2, HTML_VIDEO_SRC]) {
2094
+ for (const pattern of [MD_IMAGE, HTML_VIDEO_SRC]) {
3890
2095
  for (const match of masked.matchAll(pattern)) {
3891
2096
  const url = match.groups?.url;
3892
2097
  if (url && REMOTE.test(url)) {
@@ -3894,10 +2099,9 @@ var materializeAssets = async (markdown, ctx) => {
3894
2099
  }
3895
2100
  }
3896
2101
  }
3897
- const download = async (url) => {
3898
- const stem = hashText(url.split("?")[0] ?? url);
2102
+ const download = async (url, stem) => {
3899
2103
  const urlExt = extFromUrl(url);
3900
- if (urlExt && await exists(join5(ctx.assetsDir, `${stem}${urlExt}`))) {
2104
+ if (urlExt && await exists(join3(ctx.assetsDir, `${stem}${urlExt}`))) {
3901
2105
  return `${stem}${urlExt}`;
3902
2106
  }
3903
2107
  const completed = urlExt ? null : await completedFor(ctx.assetsDir, stem);
@@ -3916,11 +2120,11 @@ var materializeAssets = async (markdown, ctx) => {
3916
2120
  throw new Error("empty response body");
3917
2121
  }
3918
2122
  const file = `${stem}${urlExt ?? EXT_BY_MIME.get(type) ?? UNKNOWN_EXT}`;
3919
- const target = join5(ctx.assetsDir, file);
3920
- await mkdir3(ctx.assetsDir, { recursive: true });
2123
+ const target = join3(ctx.assetsDir, file);
2124
+ await mkdir(ctx.assetsDir, { recursive: true });
3921
2125
  const part = `${target}.${randomUUID()}${PART_SUFFIX}`;
3922
2126
  try {
3923
- await writeFile3(part, res.body);
2127
+ await writeFile(part, res.body);
3924
2128
  } catch (error) {
3925
2129
  await rm(part, { force: true });
3926
2130
  throw error;
@@ -3928,10 +2132,30 @@ var materializeAssets = async (markdown, ctx) => {
3928
2132
  await rename(part, target);
3929
2133
  return file;
3930
2134
  };
2135
+ const publish = async (url) => {
2136
+ const stem = hashText(url.split("?")[0] ?? url);
2137
+ const pending = inFlight.get(stem);
2138
+ if (pending) {
2139
+ try {
2140
+ return await pending;
2141
+ } catch {
2142
+ return await publish(url);
2143
+ }
2144
+ }
2145
+ const own = (async () => {
2146
+ try {
2147
+ return await limit(() => download(url, stem));
2148
+ } finally {
2149
+ inFlight.delete(stem);
2150
+ }
2151
+ })();
2152
+ inFlight.set(stem, own);
2153
+ return await own;
2154
+ };
3931
2155
  const rewrites = new Map;
3932
2156
  await Promise.all([...urls].map(async (url) => {
3933
2157
  try {
3934
- const file = await limit(() => download(url));
2158
+ const file = await publish(url);
3935
2159
  rewrites.set(url, `${ctx.assetsBaseUrl}/${file}`);
3936
2160
  } catch (error) {
3937
2161
  diagnostics.push({
@@ -3941,7 +2165,7 @@ var materializeAssets = async (markdown, ctx) => {
3941
2165
  });
3942
2166
  }
3943
2167
  }));
3944
- const rewritten = masked.replaceAll(MD_IMAGE2, (match, alt, url) => {
2168
+ const rewritten = masked.replaceAll(MD_IMAGE, (match, alt, url) => {
3945
2169
  const local = rewrites.get(url);
3946
2170
  return local ? `![${alt}](${local})` : match;
3947
2171
  }).replaceAll(HTML_VIDEO_SRC, (match, open, url, close) => {
@@ -3952,22 +2176,12 @@ var materializeAssets = async (markdown, ctx) => {
3952
2176
  };
3953
2177
 
3954
2178
  // src/core/sources/notion.ts
3955
- var richToMarkdown = (rich = []) => rich.map((node) => {
3956
- let text = node.plain_text;
3957
- if (node.annotations?.code) {
3958
- text = `\`${text}\``;
3959
- }
3960
- if (node.annotations?.bold) {
3961
- text = `**${text}**`;
3962
- }
3963
- if (node.annotations?.italic) {
3964
- text = `*${text}*`;
3965
- }
3966
- if (node.annotations?.strikethrough) {
3967
- text = `~~${text}~~`;
3968
- }
3969
- return node.href ? `[${text}](${node.href})` : text;
3970
- }).join("");
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("");
3971
2185
  var isBlockPayload = (value) => typeof value === "object";
3972
2186
  var payloadOf = (block) => {
3973
2187
  const value = block[block.type];
@@ -3977,7 +2191,7 @@ var blockField = (block) => payloadOf(block)?.rich_text ?? [];
3977
2191
  var RATE_LIMITED = 429;
3978
2192
  var MAX_RETRIES = 4;
3979
2193
  var BASE_DELAY_MS = 500;
3980
- var SECOND_MS2 = 1000;
2194
+ var SECOND_MS = 1000;
3981
2195
  var DEFAULT_CONCURRENCY = 3;
3982
2196
  var ASSET_DOWNLOAD_CONCURRENCY = 4;
3983
2197
  var withNotionRetry = async (call, attempt = 0) => {
@@ -3989,8 +2203,8 @@ var withNotionRetry = async (call, attempt = 0) => {
3989
2203
  throw error;
3990
2204
  }
3991
2205
  const retryAfter = Number(error.headers?.["retry-after"]);
3992
- const wait = retryAfter > 0 ? retryAfter * SECOND_MS2 : BASE_DELAY_MS * 2 ** attempt * (1 + Math.random());
3993
- await sleep2(wait);
2206
+ const wait = retryAfter > 0 ? retryAfter * SECOND_MS : BASE_DELAY_MS * 2 ** attempt * (1 + Math.random());
2207
+ await sleep(wait);
3994
2208
  return withNotionRetry(call, attempt + 1);
3995
2209
  }
3996
2210
  };
@@ -4056,13 +2270,11 @@ var renderLeaf = (block) => {
4056
2270
  return "---";
4057
2271
  }
4058
2272
  case "code": {
4059
- return `\`\`\`${data.language ?? ""}
4060
- ${text}
4061
- \`\`\``;
2273
+ return codeFence(richToPlain(blockField(block)), data.language ?? "");
4062
2274
  }
4063
2275
  case "image": {
4064
2276
  const url = data.external?.url ?? data.file?.url;
4065
- return url ? `![${richToMarkdown(data.caption)}](${url})` : "";
2277
+ return url ? image(richToPlain(data.caption), url) : "";
4066
2278
  }
4067
2279
  case "video": {
4068
2280
  return renderVideo(data);
@@ -4076,18 +2288,19 @@ var notionSource = (options, ctx) => {
4076
2288
  const props = options.properties ?? {};
4077
2289
  const limit = pLimit(Math.max(1, options.concurrency ?? DEFAULT_CONCURRENCY));
4078
2290
  const downloads = pLimit(ASSET_DOWNLOAD_CONCURRENCY);
2291
+ const inFlight = new Map;
4079
2292
  const notionCall = (call) => withNotionRetry(() => limit(call));
4080
- const cache = snapshotCache(ctx?.cacheDir ?? join6(".blume", "cache", options.name));
4081
- const assetsDir = ctx?.assetsDir ?? join6(".blume", "public", "blume-assets", options.name);
2293
+ const cache = snapshotCache(ctx?.cacheDir ?? join4(".blume", "cache", options.name));
2294
+ const assetsDir = ctx?.assetsDir ?? join4(".blume", "public", "blume-assets", options.name);
4082
2295
  const assetsBaseUrl = ctx?.assetsBaseUrl ?? `/blume-assets/${options.name}`;
4083
2296
  let snapshot = new Map;
4084
- const resolveClient = async () => {
2297
+ const resolveClient = () => {
4085
2298
  if (options.client) {
4086
2299
  return options.client;
4087
2300
  }
4088
2301
  let Client;
4089
2302
  try {
4090
- const sdk = await import("@notionhq/client");
2303
+ const sdk = nodeRequire("@notionhq/client");
4091
2304
  ({ Client } = sdk);
4092
2305
  } catch {
4093
2306
  throw new BlumeError({
@@ -4115,7 +2328,7 @@ ${body}
4115
2328
  </Callout>`;
4116
2329
  }
4117
2330
  if (block.type === "toggle") {
4118
- const title = jsxString(richToMarkdown(blockField(block)));
2331
+ const title = jsxString(richToPlain(blockField(block)));
4119
2332
  return `<Accordion>
4120
2333
  <AccordionItem title=${title}>
4121
2334
  ${await children(block)}
@@ -4191,11 +2404,11 @@ ${nested}`;
4191
2404
  };
4192
2405
  const frontmatter = (page) => {
4193
2406
  const data = {};
4194
- const title = richToMarkdown(titleProperty(page)?.title);
2407
+ const title = richToPlain(titleProperty(page)?.title);
4195
2408
  if (title) {
4196
2409
  data.title = title;
4197
2410
  }
4198
- const description = richToMarkdown(page.properties[props.description ?? "Description"]?.rich_text);
2411
+ const description = richToPlain(page.properties[props.description ?? "Description"]?.rich_text);
4199
2412
  if (description) {
4200
2413
  data.description = description;
4201
2414
  }
@@ -4206,7 +2419,7 @@ ${nested}`;
4206
2419
  if (order !== undefined) {
4207
2420
  data.sidebar = { order };
4208
2421
  }
4209
- const slugProp = richToMarkdown(page.properties[props.slug ?? "Slug"]?.rich_text);
2422
+ const slugProp = richToPlain(page.properties[props.slug ?? "Slug"]?.rich_text);
4210
2423
  const slug = slugifyPath(slugProp || title) || page.id;
4211
2424
  return { data, slug };
4212
2425
  };
@@ -4217,6 +2430,7 @@ ${nested}`;
4217
2430
  assetsBaseUrl,
4218
2431
  assetsDir,
4219
2432
  fetchImpl: options.fetchImpl,
2433
+ inFlight,
4220
2434
  limit: downloads
4221
2435
  });
4222
2436
  const raw = frontmatter_default.stringify(assets.markdown, data);
@@ -4251,7 +2465,7 @@ ${nested}`;
4251
2465
  const load = async (refresh = ctx?.refresh ?? true) => {
4252
2466
  const assetDiagnostics = [];
4253
2467
  const result = await loadWithCache(options.name, cache, async () => {
4254
- const client = await resolveClient();
2468
+ const client = resolveClient();
4255
2469
  const dataSourceId = await resolveDataSource(client);
4256
2470
  const pages = await collectAll((cursor) => queryDataSource(client, dataSourceId, cursor));
4257
2471
  const built = await Promise.all(pages.map((page) => toEntry(client, page)));
@@ -4285,9 +2499,9 @@ ${nested}`;
4285
2499
  };
4286
2500
 
4287
2501
  // src/core/sources/obsidian.ts
4288
- import { existsSync as existsSync3, watch as fsWatch2, statSync } from "node:fs";
4289
- import { readdir as readdir2, readFile as readFile5, stat } from "node:fs/promises";
4290
- import { basename as basename2, isAbsolute as isAbsolute3, join as join7, relative as relative5, resolve as resolve3 } from "pathe";
2502
+ import { existsSync as existsSync2, watch as fsWatch2, statSync } from "node:fs";
2503
+ import { readdir as readdir2, readFile as readFile2, stat } from "node:fs/promises";
2504
+ import { basename as basename2, isAbsolute as isAbsolute2, join as join5, relative as relative4, resolve as resolve2 } from "pathe";
4291
2505
  var MARKDOWN_FILE = /\.md$/iu;
4292
2506
  var OBSIDIAN_COMMENT = /%%.*?%%/gu;
4293
2507
  var WIKILINK = /(?<embed>!)?\[\[(?<target>(?:[^\][|#\n][^\]|#\n]*?)?)(?:#(?<heading>[^\]|\n]+?))?(?:\\?\|(?<alias>[^\]\n]+))?\]\]/gu;
@@ -4435,7 +2649,7 @@ var transformChunk = (chunk, index, self, unresolved) => chunk.replaceAll(OBSIDI
4435
2649
  return `[${label}](${href}#${anchor})`;
4436
2650
  });
4437
2651
  var transformProse = (text, index, self, unresolved) => splitLiterals(text).map((chunk) => chunk.literal ? chunk.text : transformChunk(chunk.text, index, self, unresolved)).join("");
4438
- var INDENTED_CODE2 = /^(?: {4}|\t)/u;
2652
+ var INDENTED_CODE = /^(?: {4}|\t)/u;
4439
2653
  var LIST_ITEM = /^ {0,3}(?:[-+*]|\d{1,9}[.)])(?: |$)/u;
4440
2654
  var indentOf = (line) => line.length - line.trimStart().length;
4441
2655
  var listAfter = (line, inList) => {
@@ -4448,7 +2662,7 @@ var listAfter = (line, inList) => {
4448
2662
  return line.startsWith(" ") || line.startsWith("\t") ? inList : false;
4449
2663
  };
4450
2664
  var fenceAfter = (line, fence, fenceIndent, inList) => {
4451
- const canDelimit = fence === null ? inList || !INDENTED_CODE2.test(line) : indentOf(line) <= fenceIndent + 3;
2665
+ const canDelimit = fence === null ? inList || !INDENTED_CODE.test(line) : indentOf(line) <= fenceIndent + 3;
4452
2666
  return canDelimit ? nextFenceState(line, fence) : fence;
4453
2667
  };
4454
2668
  var transformBody = (body, index, self, unresolved) => {
@@ -4470,7 +2684,7 @@ var transformBody = (body, index, self, unresolved) => {
4470
2684
  `)) {
4471
2685
  const blank = line.trim() === "";
4472
2686
  if (fence === null && indentedCode) {
4473
- if (blank || INDENTED_CODE2.test(line)) {
2687
+ if (blank || INDENTED_CODE.test(line)) {
4474
2688
  out.push(line);
4475
2689
  afterBlank = blank;
4476
2690
  continue;
@@ -4478,7 +2692,7 @@ var transformBody = (body, index, self, unresolved) => {
4478
2692
  indentedCode = false;
4479
2693
  }
4480
2694
  inList = listAfter(line, inList);
4481
- if (fence === null && afterBlank && !(blank || inList) && INDENTED_CODE2.test(line)) {
2695
+ if (fence === null && afterBlank && !(blank || inList) && INDENTED_CODE.test(line)) {
4482
2696
  flush();
4483
2697
  indentedCode = true;
4484
2698
  out.push(line);
@@ -4602,9 +2816,9 @@ var noteToEntry = (pair, index, options, unresolved) => {
4602
2816
  };
4603
2817
  };
4604
2818
  var readNote = async (vaultDir, rel, options) => {
4605
- const absPath = join7(vaultDir, rel);
2819
+ const absPath = join5(vaultDir, rel);
4606
2820
  try {
4607
- const { content, data } = frontmatter_default(await readFile5(absPath, "utf-8"));
2821
+ const { content, data } = frontmatter_default(await readFile2(absPath, "utf-8"));
4608
2822
  return {
4609
2823
  absPath,
4610
2824
  content,
@@ -4629,7 +2843,7 @@ var classify = async (dir, entry) => {
4629
2843
  };
4630
2844
  }
4631
2845
  try {
4632
- const target = await stat(join7(dir, entry.name));
2846
+ const target = await stat(join5(dir, entry.name));
4633
2847
  return {
4634
2848
  directory: target.isDirectory(),
4635
2849
  file: target.isFile(),
@@ -4649,11 +2863,11 @@ var walkVault = async (dir, root, exclude) => {
4649
2863
  const listed = await readdir2(dir, { withFileTypes: true });
4650
2864
  const entries = await Promise.all(listed.filter((entry) => !(isHidden(entry.name) || exclude.has(entry.name))).map((entry) => classify(dir, entry)));
4651
2865
  for (const entry of entries.toSorted(byVaultOrder)) {
4652
- const full = join7(dir, entry.name);
2866
+ const full = join5(dir, entry.name);
4653
2867
  if (entry.directory) {
4654
2868
  found.push(...await walkVault(full, root, exclude));
4655
2869
  } else if (entry.file && MARKDOWN_FILE.test(entry.name)) {
4656
- found.push(relative5(root, full));
2870
+ found.push(relative4(root, full));
4657
2871
  }
4658
2872
  }
4659
2873
  return found;
@@ -4689,7 +2903,7 @@ var unresolvedDiagnostics = (name, unresolved) => {
4689
2903
  return diagnostics;
4690
2904
  };
4691
2905
  var obsidianSource = (options, ctx) => {
4692
- const vaultDir = resolve3(ctx.projectRoot, options.vault);
2906
+ const vaultDir = resolve2(ctx.projectRoot, options.vault);
4693
2907
  const exclude = new Set([...BLUME_IGNORE_DIRS, ...options.exclude ?? []]);
4694
2908
  const load = async () => {
4695
2909
  const files = await walkVault(vaultDir, vaultDir, exclude);
@@ -4704,9 +2918,9 @@ var obsidianSource = (options, ctx) => {
4704
2918
  };
4705
2919
  };
4706
2920
  const read = async (ref) => {
4707
- const absPath = resolve3(vaultDir, ref);
4708
- const rel = relative5(vaultDir, absPath);
4709
- if (rel.startsWith("..") || isAbsolute3(rel)) {
2921
+ const absPath = resolve2(vaultDir, ref);
2922
+ const rel = relative4(vaultDir, absPath);
2923
+ if (rel.startsWith("..") || isAbsolute2(rel)) {
4710
2924
  throw new BlumeError({
4711
2925
  code: "BLUME_SOURCE_MISCONFIGURED",
4712
2926
  file: absPath,
@@ -4715,7 +2929,7 @@ var obsidianSource = (options, ctx) => {
4715
2929
  suggestion: "Reference notes by their vault-relative path."
4716
2930
  });
4717
2931
  }
4718
- return await readFile5(absPath, "utf-8");
2932
+ return await readFile2(absPath, "utf-8");
4719
2933
  };
4720
2934
  const validate = () => {
4721
2935
  const stats = statSync(vaultDir, { throwIfNoEntry: false });
@@ -4732,7 +2946,7 @@ var obsidianSource = (options, ctx) => {
4732
2946
  });
4733
2947
  };
4734
2948
  const watch = (onChange) => {
4735
- if (!existsSync3(vaultDir)) {
2949
+ if (!existsSync2(vaultDir)) {
4736
2950
  return () => {};
4737
2951
  }
4738
2952
  const watcher = fsWatch2(vaultDir, { recursive: true }, ignoringWatchListener(onChange, exclude, isHidden));
@@ -4750,8 +2964,193 @@ var obsidianSource = (options, ctx) => {
4750
2964
  };
4751
2965
  };
4752
2966
 
2967
+ // src/core/sources/lexical.ts
2968
+ var IS_BOLD = 1;
2969
+ var IS_ITALIC = 2;
2970
+ var IS_STRIKETHROUGH = 4;
2971
+ var IS_CODE = 16;
2972
+ var hasFlag = (format, flag) => Math.floor(format / flag) % 2 === 1;
2973
+ var marksOf2 = (node) => {
2974
+ const format = asNumber(node.format) ?? 0;
2975
+ return {
2976
+ bold: hasFlag(format, IS_BOLD),
2977
+ code: hasFlag(format, IS_CODE),
2978
+ italic: hasFlag(format, IS_ITALIC),
2979
+ strike: hasFlag(format, IS_STRIKETHROUGH)
2980
+ };
2981
+ };
2982
+ var children2 = (node) => objectsIn(node.children);
2983
+ var typeOf = (node) => asString(node.type) ?? "";
2984
+ var customBlock = (node, options) => {
2985
+ const fields = asObject(node.fields);
2986
+ const blockType = fields ? asString(fields.blockType) : undefined;
2987
+ const serializer = blockType ? options.serializers?.[blockType] : undefined;
2988
+ if (fields && serializer) {
2989
+ return serializer(fields);
2990
+ }
2991
+ return unsupported(`Lexical block${blockType ? `: ${blockType}` : ""}`, writesMdx(options.serializers));
2992
+ };
2993
+ var renderInlines2 = (nodes, options) => nodes.map((node) => renderInlineNode2(node, options)).join("");
2994
+ var renderInlineNode2 = (node, options) => {
2995
+ switch (typeOf(node)) {
2996
+ case "text": {
2997
+ return renderInline(asString(node.text) ?? "", marksOf2(node));
2998
+ }
2999
+ case "linebreak": {
3000
+ return `
3001
+ `;
3002
+ }
3003
+ case "tab": {
3004
+ return "\t";
3005
+ }
3006
+ case "link":
3007
+ case "autolink": {
3008
+ return renderLink(renderInlines2(children2(node), options), asString(getPath(node, "fields.url")) ?? asString(node.url));
3009
+ }
3010
+ case "inlineBlock": {
3011
+ return customBlock(node, options);
3012
+ }
3013
+ default: {
3014
+ return renderInlines2(children2(node), options);
3015
+ }
3016
+ }
3017
+ };
3018
+ var renderUpload = (node, options) => {
3019
+ const value = asObject(node.value);
3020
+ const url = value ? asString(value.url) : undefined;
3021
+ if (!(value && url)) {
3022
+ return unsupported("Lexical upload (fetch with depth 1 to populate it)", writesMdx(options.serializers));
3023
+ }
3024
+ const alt = asString(value.alt) ?? asString(value.filename) ?? "";
3025
+ return image(alt, absoluteUrl(url, options.baseUrl));
3026
+ };
3027
+ var renderList2 = (node, options) => {
3028
+ const listType = asString(node.listType) ?? "bullet";
3029
+ const start = asNumber(node.start) ?? 1;
3030
+ const items = [];
3031
+ let lastMarker = "-";
3032
+ let index = 0;
3033
+ for (const item of children2(node)) {
3034
+ const kids = children2(item);
3035
+ const nested = kids.filter((kid) => typeOf(kid) === "list");
3036
+ const inline = kids.filter((kid) => typeOf(kid) !== "list");
3037
+ const sub = nested.map((list) => renderList2(list, options)).join(`
3038
+ `);
3039
+ if (inline.length === 0 && nested.length > 0) {
3040
+ items.push(items.length > 0 ? indent(sub, lastMarker.length + 1) : sub);
3041
+ continue;
3042
+ }
3043
+ lastMarker = listType === "number" ? `${start + index}.` : "-";
3044
+ let check = "";
3045
+ if (listType === "check") {
3046
+ check = item.checked === true ? "[x] " : "[ ] ";
3047
+ }
3048
+ const line = `${check}${renderInlines2(inline, options)}`;
3049
+ items.push(listItem(lastMarker, sub ? `${line}
3050
+ ${sub}` : line));
3051
+ index += 1;
3052
+ }
3053
+ return items.join(`
3054
+ `);
3055
+ };
3056
+ var renderBlock2 = (node, options) => {
3057
+ switch (typeOf(node)) {
3058
+ case "paragraph": {
3059
+ return renderInlines2(children2(node), options);
3060
+ }
3061
+ case "heading": {
3062
+ const level = Number((asString(node.tag) ?? "h1").slice(1));
3063
+ return `${headingPrefix(level)}${renderInlines2(children2(node), options)}`;
3064
+ }
3065
+ case "quote": {
3066
+ return blockquote(renderInlines2(children2(node), options));
3067
+ }
3068
+ case "list": {
3069
+ return renderList2(node, options);
3070
+ }
3071
+ case "horizontalrule": {
3072
+ return "---";
3073
+ }
3074
+ case "upload": {
3075
+ return renderUpload(node, options);
3076
+ }
3077
+ case "block": {
3078
+ return customBlock(node, options);
3079
+ }
3080
+ default: {
3081
+ return unsupported(`Lexical node: ${typeOf(node)}`, writesMdx(options.serializers));
3082
+ }
3083
+ }
3084
+ };
3085
+ var lexicalToMarkdown = (state, options = {}) => {
3086
+ const root = asObject(state.root) ?? state;
3087
+ return markdownDocument(children2(root).map((node) => renderBlock2(node, options)));
3088
+ };
3089
+
3090
+ // src/core/sources/payload.ts
3091
+ var PAGE_SIZE2 = 100;
3092
+ var DEFAULT_FIELDS2 = {
3093
+ body: "content",
3094
+ description: "description",
3095
+ lastModified: "updatedAt",
3096
+ slug: "slug",
3097
+ title: "title"
3098
+ };
3099
+ var payloadSource = (options, ctx) => {
3100
+ const fields = { ...DEFAULT_FIELDS2, ...options.fields };
3101
+ const origin = options.url.replace(/\/+$/u, "");
3102
+ const lower = (body) => lexicalToMarkdown(body, {
3103
+ baseUrl: origin,
3104
+ serializers: options.serializers
3105
+ });
3106
+ const fetchEntries = async () => {
3107
+ const preview = ctx?.preview ?? false;
3108
+ const token = options.token ?? process.env.PAYLOAD_API_KEY;
3109
+ const client = {
3110
+ fetchImpl: options.fetchImpl,
3111
+ headers: token ? {
3112
+ authorization: `${options.authCollection ?? "users"} API-Key ${token}`
3113
+ } : {}
3114
+ };
3115
+ const base = `${origin}/api/${options.collection}`;
3116
+ const entries = [];
3117
+ let page = 1;
3118
+ let more = true;
3119
+ while (more) {
3120
+ const query = queryString({
3121
+ ...options.params,
3122
+ depth: String(options.depth ?? 1),
3123
+ draft: preview ? "true" : undefined,
3124
+ limit: String(PAGE_SIZE2),
3125
+ page: String(page)
3126
+ });
3127
+ const result = asObject(await fetchJson(`${base}?${query}`, client));
3128
+ if (!result) {
3129
+ throw new Error("Payload returned a non-object response");
3130
+ }
3131
+ for (const doc of objectsIn(result.docs)) {
3132
+ const draft = asString(doc._status) === "draft";
3133
+ if (draft && !preview) {
3134
+ continue;
3135
+ }
3136
+ const id = asString(doc.id) ?? String(doc.id ?? "");
3137
+ entries.push(documentEntry(doc, fields, id, (body) => isJsonObject(body) ? lower(body) : "", draft, writesMdx(options.serializers)));
3138
+ }
3139
+ more = result.hasNextPage === true;
3140
+ page += 1;
3141
+ }
3142
+ return entries;
3143
+ };
3144
+ return remoteSource({
3145
+ fetchEntries,
3146
+ name: options.name,
3147
+ pollInterval: options.pollInterval,
3148
+ prefix: options.prefix
3149
+ }, ctx);
3150
+ };
3151
+
4753
3152
  // src/core/sources/sanity.ts
4754
- import { join as join8 } from "pathe";
3153
+ import { join as join6 } from "pathe";
4755
3154
 
4756
3155
  // src/core/sources/portable-text.ts
4757
3156
  var HEADING_STYLES = new Map([
@@ -4762,11 +3161,9 @@ var HEADING_STYLES = new Map([
4762
3161
  ["h5", "##### "],
4763
3162
  ["h6", "###### "]
4764
3163
  ]);
4765
- var MARKDOWN_SPECIALS = /[\\`*_[\]~<]/gu;
4766
- var escapeText = (text) => text.replaceAll(MARKDOWN_SPECIALS, String.raw`\$&`);
4767
3164
  var renderSpan = (span, defs) => {
4768
3165
  const isCode = span.marks?.includes("code") ?? false;
4769
- let text = isCode ? span.text ?? "" : escapeText(span.text ?? "");
3166
+ let text = isCode ? span.text ?? "" : escapeMarkdownText(span.text ?? "");
4770
3167
  if (!span.marks || span.marks.length === 0) {
4771
3168
  return text;
4772
3169
  }
@@ -4804,7 +3201,7 @@ var renderChildren = (block) => {
4804
3201
  return (block.children ?? []).map((span) => renderSpan(span, defs)).join("");
4805
3202
  };
4806
3203
  var isAltText = (value) => typeof value === "string";
4807
- var renderBlock = (block, options) => {
3204
+ var renderBlock3 = (block, options) => {
4808
3205
  const custom = options.serializers?.[block._type];
4809
3206
  if (custom) {
4810
3207
  return custom(block);
@@ -4812,10 +3209,10 @@ var renderBlock = (block, options) => {
4812
3209
  if (block._type === "image") {
4813
3210
  const url = options.imageUrl?.(block);
4814
3211
  const alt = isAltText(block.alt) ? block.alt : "";
4815
- return url ? `![${alt}](${url})` : "";
3212
+ return url ? image(alt, url) : "";
4816
3213
  }
4817
3214
  if (block._type !== "block") {
4818
- return `<!-- unsupported Portable Text block: ${block._type} -->`;
3215
+ return unsupported(`Portable Text block: ${block._type}`, writesMdx(options.serializers));
4819
3216
  }
4820
3217
  const inline = renderChildren(block);
4821
3218
  if (block.listItem) {
@@ -4829,7 +3226,7 @@ var renderBlock = (block, options) => {
4829
3226
  return `${HEADING_STYLES.get(block.style ?? "normal") ?? ""}${inline}`;
4830
3227
  };
4831
3228
  var portableTextToMarkdown = (blocks, options = {}) => {
4832
- const lines = blocks.map((block) => renderBlock(block, options));
3229
+ const lines = blocks.map((block) => renderBlock3(block, options));
4833
3230
  const out = [];
4834
3231
  for (let i = 0;i < lines.length; i += 1) {
4835
3232
  const line = lines[i] ?? "";
@@ -4849,20 +3246,6 @@ var portableTextToMarkdown = (blocks, options = {}) => {
4849
3246
 
4850
3247
  // src/core/sources/sanity.ts
4851
3248
  var IMAGE_REF = /^image-(?<id>[a-f0-9]+)-(?<dims>\d+x\d+)-(?<ext>\w+)$/u;
4852
- var isTraversable = (value) => typeof value === "object" && value !== null;
4853
- var getPath = (doc, path) => {
4854
- let current = doc;
4855
- for (const key of path.split(".")) {
4856
- if (isTraversable(current)) {
4857
- current = current[key];
4858
- } else {
4859
- return;
4860
- }
4861
- }
4862
- return current;
4863
- };
4864
- var isStringValue2 = (value) => typeof value === "string";
4865
- var asString = (value) => isStringValue2(value) ? value : undefined;
4866
3249
  var imageUrlFromRef = (ref, projectId, dataset) => {
4867
3250
  const match = ref.match(IMAGE_REF);
4868
3251
  if (!match?.groups) {
@@ -4871,13 +3254,13 @@ var imageUrlFromRef = (ref, projectId, dataset) => {
4871
3254
  const { id, dims, ext } = match.groups;
4872
3255
  return `https://cdn.sanity.io/images/${projectId}/${dataset}/${id}-${dims}.${ext}`;
4873
3256
  };
4874
- var resolveClient = async (options, preview) => {
3257
+ var resolveClient = (options, preview) => {
4875
3258
  if (options.client) {
4876
3259
  return options.client;
4877
3260
  }
4878
3261
  let createClient;
4879
3262
  try {
4880
- ({ createClient } = await import("@sanity/client"));
3263
+ ({ createClient } = nodeRequire("@sanity/client"));
4881
3264
  } catch {
4882
3265
  throw new BlumeError({
4883
3266
  code: "BLUME_SOURCE_SDK_MISSING",
@@ -4896,11 +3279,11 @@ var resolveClient = async (options, preview) => {
4896
3279
  };
4897
3280
  var sanitySource = (options, ctx) => {
4898
3281
  const fields = options.fields ?? {};
4899
- const cache = snapshotCache(ctx?.cacheDir ?? join8(".blume", "cache", options.name));
3282
+ const cache = snapshotCache(ctx?.cacheDir ?? join6(".blume", "cache", options.name));
4900
3283
  let snapshot = new Map;
4901
3284
  const toEntry = (doc) => {
4902
3285
  const slugValue = asString(getPath(doc, fields.slug ?? "slug.current")) ?? asString(doc._id) ?? "untitled";
4903
- const slug = slugifyPath(slugValue) || slugify2(asString(doc._id) ?? "") || "untitled";
3286
+ const slug = slugifyPath(slugValue) || slugify(asString(doc._id) ?? "") || "untitled";
4904
3287
  const data = {};
4905
3288
  const title = asString(getPath(doc, fields.title ?? "title"));
4906
3289
  const description = asString(getPath(doc, fields.description ?? "description"));
@@ -4918,19 +3301,20 @@ var sanitySource = (options, ctx) => {
4918
3301
  },
4919
3302
  serializers: options.serializers
4920
3303
  }) : "";
3304
+ const format = writesMdx(options.serializers) ? "mdx" : "md";
4921
3305
  const raw = frontmatter_default.stringify(markdown, data);
4922
3306
  return {
4923
- body: { format: "md", text: markdown },
3307
+ body: { format, text: markdown },
4924
3308
  data: { ...data },
4925
3309
  hash: hashText(raw),
4926
3310
  lastModified: asString(getPath(doc, fields.lastModified ?? "_updatedAt")),
4927
3311
  raw,
4928
- ref: `${slug}.md`
3312
+ ref: `${slug}.${format}`
4929
3313
  };
4930
3314
  };
4931
3315
  const load = async (refresh = ctx?.refresh ?? true) => {
4932
3316
  const result = await loadWithCache(options.name, cache, async () => {
4933
- const client = await resolveClient(options, ctx?.preview ?? false);
3317
+ const client = resolveClient(options, ctx?.preview ?? false);
4934
3318
  const docs = await client.fetch(options.query);
4935
3319
  return docs.map(toEntry);
4936
3320
  }, refresh);
@@ -4955,6 +3339,162 @@ var sanitySource = (options, ctx) => {
4955
3339
  };
4956
3340
  };
4957
3341
 
3342
+ // src/core/sources/strapi-blocks.ts
3343
+ var children3 = (node) => objectsIn(node.children);
3344
+ var typeOf2 = (node) => asString(node.type) ?? "";
3345
+ var renderInlines3 = (nodes) => nodes.map((node) => renderInlineNode3(node)).join("");
3346
+ var renderInlineNode3 = (node) => {
3347
+ switch (typeOf2(node)) {
3348
+ 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
+ });
3355
+ }
3356
+ case "link": {
3357
+ return renderLink(renderInlines3(children3(node)), asString(node.url));
3358
+ }
3359
+ default: {
3360
+ return renderInlines3(children3(node));
3361
+ }
3362
+ }
3363
+ };
3364
+ var renderList3 = (node) => {
3365
+ const ordered = asString(node.format) === "ordered";
3366
+ return children3(node).map((item, index) => {
3367
+ const kids = children3(item);
3368
+ const line = renderInlines3(kids.filter((kid) => typeOf2(kid) !== "list"));
3369
+ const sub = kids.filter((kid) => typeOf2(kid) === "list").map((list) => renderList3(list)).join(`
3370
+ `);
3371
+ const body = sub ? `${line}
3372
+ ${sub}` : line;
3373
+ return listItem(ordered ? `${index + 1}.` : "-", body);
3374
+ }).join(`
3375
+ `);
3376
+ };
3377
+ var renderImage = (node, options) => {
3378
+ const file = asObject(node.image);
3379
+ const url = file ? asString(file.url) : undefined;
3380
+ if (!(file && url)) {
3381
+ return "";
3382
+ }
3383
+ return image(asString(file.alternativeText) ?? asString(file.name) ?? "", absoluteUrl(url, options.baseUrl));
3384
+ };
3385
+ var renderBlock4 = (node, options) => {
3386
+ switch (typeOf2(node)) {
3387
+ case "paragraph": {
3388
+ return renderInlines3(children3(node));
3389
+ }
3390
+ case "heading": {
3391
+ return `${headingPrefix(asNumber(node.level) ?? 1)}${renderInlines3(children3(node))}`;
3392
+ }
3393
+ case "quote": {
3394
+ return blockquote(renderInlines3(children3(node)));
3395
+ }
3396
+ case "list": {
3397
+ return renderList3(node);
3398
+ }
3399
+ case "code": {
3400
+ const code = children3(node).map((child) => asString(child.text) ?? "").join("");
3401
+ return codeFence(code, asString(node.language) ?? "");
3402
+ }
3403
+ case "image": {
3404
+ return renderImage(node, options);
3405
+ }
3406
+ default: {
3407
+ return unsupported(`Strapi block: ${typeOf2(node)}`);
3408
+ }
3409
+ }
3410
+ };
3411
+ var strapiBlocksToMarkdown = (blocks, options = {}) => markdownDocument(objectsIn(blocks).map((node) => renderBlock4(node, options)));
3412
+
3413
+ // src/core/sources/strapi.ts
3414
+ var PAGE_SIZE3 = 100;
3415
+ var DEFAULT_FIELDS3 = {
3416
+ body: "content",
3417
+ description: "description",
3418
+ lastModified: "updatedAt",
3419
+ slug: "slug",
3420
+ title: "title"
3421
+ };
3422
+ var flatten = (item) => {
3423
+ const attributes = asObject(item.attributes);
3424
+ return attributes ? { ...attributes, id: item.id ?? null } : item;
3425
+ };
3426
+ var idOf = (doc) => {
3427
+ const raw = doc.documentId ?? doc.id;
3428
+ return asString(raw) ?? String(asNumber(raw) ?? "");
3429
+ };
3430
+ var unpublished = (doc) => doc.publishedAt === null;
3431
+ var strapiSource = (options, ctx) => {
3432
+ const fields = { ...DEFAULT_FIELDS3, ...options.fields };
3433
+ const origin = options.url.replace(/\/+$/u, "");
3434
+ const fetchEntries = async () => {
3435
+ const token = options.token ?? process.env.STRAPI_API_TOKEN;
3436
+ const client = {
3437
+ fetchImpl: options.fetchImpl,
3438
+ headers: token ? { authorization: `Bearer ${token}` } : {}
3439
+ };
3440
+ const base = `${origin}/api/${options.contentType}`;
3441
+ const list = async (publication) => {
3442
+ const listing = { docs: [], v4: false };
3443
+ let page = 1;
3444
+ let more = true;
3445
+ while (more) {
3446
+ const query = queryString({
3447
+ ...options.params,
3448
+ locale: options.locale,
3449
+ "pagination[pageSize]": String(PAGE_SIZE3),
3450
+ "pagination[page]": String(page),
3451
+ populate: options.populate ?? "*",
3452
+ ...publication
3453
+ });
3454
+ const result = asObject(await fetchJson(`${base}?${query}`, client));
3455
+ if (!result) {
3456
+ throw new Error("Strapi returned a non-object response");
3457
+ }
3458
+ const items = objectsIn(result.data);
3459
+ for (const item of items) {
3460
+ listing.v4 ||= asObject(item.attributes) !== undefined;
3461
+ listing.docs.push(flatten(item));
3462
+ }
3463
+ const pageCount = asNumber(getPath(result, "meta.pagination.pageCount")) ?? 1;
3464
+ more = items.length > 0 && page < pageCount;
3465
+ page += 1;
3466
+ }
3467
+ return listing;
3468
+ };
3469
+ const toEntries = (docs, isDraft) => docs.map((doc) => documentEntry(doc, fields, idOf(doc), (body) => strapiBlocksToMarkdown(body, { baseUrl: origin }), isDraft(doc)));
3470
+ const published = await list({});
3471
+ if (!ctx?.preview) {
3472
+ return toEntries(published.docs, () => false);
3473
+ }
3474
+ if (published.v4) {
3475
+ const previewed = await list({ publicationState: "preview" });
3476
+ return toEntries(previewed.docs, unpublished);
3477
+ }
3478
+ const drafts = await list({ status: "draft" });
3479
+ if (drafts.docs.length === 0 && published.docs.length === 0) {
3480
+ const previewed = await list({ publicationState: "preview" }).catch(() => null);
3481
+ return previewed ? toEntries(previewed.docs, unpublished) : [];
3482
+ }
3483
+ const publishedIds = new Set(published.docs.map(idOf));
3484
+ const draftIds = new Set(drafts.docs.map(idOf));
3485
+ return [
3486
+ ...toEntries(drafts.docs, (doc) => !publishedIds.has(idOf(doc))),
3487
+ ...toEntries(published.docs.filter((doc) => !draftIds.has(idOf(doc))), () => false)
3488
+ ];
3489
+ };
3490
+ return remoteSource({
3491
+ fetchEntries,
3492
+ name: options.name,
3493
+ pollInterval: options.pollInterval,
3494
+ prefix: options.prefix
3495
+ }, ctx);
3496
+ };
3497
+
4958
3498
  // src/core/sources/resolve.ts
4959
3499
  var uniqueNamer = () => {
4960
3500
  const used = new Set;
@@ -4971,8 +3511,8 @@ var uniqueNamer = () => {
4971
3511
  };
4972
3512
  var sourceContext = (context, name, runtime) => ({
4973
3513
  assetsBaseUrl: `/blume-assets/${name}`,
4974
- assetsDir: join9(context.outDir, "public", "blume-assets", name),
4975
- cacheDir: join9(context.outDir, "cache", name),
3514
+ assetsDir: join7(context.outDir, "public", "blume-assets", name),
3515
+ cacheDir: join7(context.outDir, "cache", name),
4976
3516
  mode: runtime.mode,
4977
3517
  preview: runtime.preview,
4978
3518
  projectRoot: context.root,
@@ -4982,119 +3522,61 @@ var typeFrontmatterKeys = (config) => Object.fromEntries(Object.entries(config.c
4982
3522
  type,
4983
3523
  Object.keys(def.frontmatter)
4984
3524
  ]));
4985
- var buildSource = (def, name, config, context, runtime) => {
4986
- if (def.type === "filesystem") {
4987
- return filesystemSource({
4988
- exclude: def.exclude,
4989
- include: def.include,
4990
- name,
4991
- prefix: def.prefix,
4992
- projectRoot: context.root,
4993
- root: def.root
4994
- });
4995
- }
4996
- if (def.type === "custom") {
4997
- return def.source.name === name ? def.source : { ...def.source, name };
4998
- }
4999
- if (def.type === "sanity") {
5000
- return sanitySource({
5001
- apiVersion: def.apiVersion,
5002
- dataset: def.dataset,
5003
- fields: def.fields,
5004
- name,
5005
- pollInterval: def.pollInterval,
5006
- prefix: def.prefix,
5007
- projectId: def.projectId,
5008
- query: def.query
5009
- }, sourceContext(context, name, runtime));
5010
- }
5011
- if (def.type === "notion") {
5012
- return notionSource({
5013
- concurrency: def.concurrency,
5014
- database: def.database,
5015
- name,
5016
- pollInterval: def.pollInterval,
5017
- prefix: def.prefix,
5018
- properties: def.properties,
5019
- publishedValue: def.publishedValue
5020
- }, sourceContext(context, name, runtime));
5021
- }
5022
- if (def.type === "obsidian") {
5023
- return obsidianSource({
5024
- defaultType: config.content.defaultType,
5025
- exclude: def.exclude,
5026
- frontmatterKeys: Object.keys(config.frontmatter.extend),
5027
- i18n: config.i18n,
5028
- name,
5029
- prefix: def.prefix,
5030
- typeFrontmatterKeys: typeFrontmatterKeys(config),
5031
- vault: def.vault,
5032
- versions: config.versions
5033
- }, sourceContext(context, name, runtime));
5034
- }
5035
- if (def.type === "github-releases") {
5036
- return githubReleasesSource({
5037
- drafts: def.drafts,
5038
- limit: def.limit,
5039
- name,
5040
- owner: def.owner,
5041
- pollInterval: def.pollInterval,
5042
- prefix: def.prefix,
5043
- prereleases: def.prereleases,
5044
- repo: def.repo
5045
- }, sourceContext(context, name, runtime));
5046
- }
5047
- return mdxRemoteSource({
5048
- files: def.files,
5049
- github: def.github,
5050
- include: def.include,
5051
- name,
5052
- pollInterval: def.pollInterval,
5053
- prefix: def.prefix,
5054
- url: def.url
5055
- }, sourceContext(context, name, runtime));
5056
- };
5057
- var resolveRoot = (projectRoot, root) => isAbsolute4(root) ? root : join9(resolve4(projectRoot), root);
5058
- var resolveDocsCollection = (config, context) => {
5059
- const filesystem = (config.content.sources ?? []).filter((def) => def.type === "filesystem");
5060
- const only = filesystem.length === 1 ? filesystem[0] : undefined;
5061
- if (only) {
5062
- return {
5063
- base: resolveRoot(context.root, only.root),
5064
- exclude: only.exclude,
5065
- include: only.include
5066
- };
5067
- }
5068
- return {
5069
- base: context.contentRoot,
5070
- exclude: config.content.exclude,
5071
- include: config.content.include
5072
- };
5073
- };
5074
- var baseName = (def) => {
5075
- if (def.type === "custom") {
5076
- return def.source.name;
3525
+ var buildSource = (adapter, name, config, context, runtime) => {
3526
+ switch (adapter.kind) {
3527
+ case "filesystem": {
3528
+ const { pollInterval: _ignored, ...options } = adapter.options;
3529
+ return filesystemSource({ ...options, name, projectRoot: context.root });
3530
+ }
3531
+ case "custom": {
3532
+ const source = adapter.options;
3533
+ return source.name === name ? source : { ...source, name };
3534
+ }
3535
+ case "sanity": {
3536
+ return sanitySource({ ...adapter.options, name }, sourceContext(context, name, runtime));
3537
+ }
3538
+ case "notion": {
3539
+ return notionSource({ ...adapter.options, name }, sourceContext(context, name, runtime));
3540
+ }
3541
+ case "contentful": {
3542
+ return contentfulSource({ ...adapter.options, name }, sourceContext(context, name, runtime));
3543
+ }
3544
+ case "payload": {
3545
+ return payloadSource({ ...adapter.options, name }, sourceContext(context, name, runtime));
3546
+ }
3547
+ case "strapi": {
3548
+ return strapiSource({ ...adapter.options, name }, sourceContext(context, name, runtime));
3549
+ }
3550
+ case "obsidian": {
3551
+ const { pollInterval: _ignored, ...options } = adapter.options;
3552
+ return obsidianSource({
3553
+ ...options,
3554
+ defaultType: config.content.defaultType,
3555
+ frontmatterKeys: Object.keys(config.frontmatter.extend),
3556
+ i18n: config.i18n,
3557
+ name,
3558
+ typeFrontmatterKeys: typeFrontmatterKeys(config),
3559
+ versions: config.versions
3560
+ }, sourceContext(context, name, runtime));
3561
+ }
3562
+ case "github-releases": {
3563
+ return githubReleasesSource({ ...adapter.options, name }, sourceContext(context, name, runtime));
3564
+ }
3565
+ default: {
3566
+ break;
3567
+ }
5077
3568
  }
5078
- return def.prefix ?? def.type;
3569
+ return mdxRemoteSource({ ...adapter.options, name }, sourceContext(context, name, runtime));
5079
3570
  };
5080
- var contentSources = (config, context, runtime) => {
5081
- const defs = config.content.sources;
5082
- if (!defs || defs.length === 0) {
5083
- return [
5084
- filesystemSource({
5085
- exclude: config.content.exclude,
5086
- include: config.content.include,
5087
- name: "filesystem",
5088
- projectRoot: context.root,
5089
- root: config.content.root
5090
- })
5091
- ];
3571
+ var baseName = (adapter) => {
3572
+ if (adapter.kind === "custom") {
3573
+ return adapter.options.name;
5092
3574
  }
5093
- const nameFor = uniqueNamer();
5094
- return defs.map((def) => buildSource(def, nameFor(baseName(def)), config, context, runtime));
3575
+ return adapter.options.prefix ?? adapter.kind;
5095
3576
  };
5096
3577
  var resolveSources = (config, context, runtime) => {
5097
- const sources = contentSources(config, context, runtime);
3578
+ const nameFor = uniqueNamer();
3579
+ const sources = config.content.sources.map((adapter) => buildSource(adapter, nameFor(baseName(adapter)), config, context, runtime));
5098
3580
  const references = blumeReferences(config);
5099
3581
  if (references.length > 0) {
5100
3582
  sources.push(openApiSource(references, sourceContext(context, "openapi", runtime)));
@@ -5104,25 +3586,18 @@ var resolveSources = (config, context, runtime) => {
5104
3586
 
5105
3587
  // src/core/project-graph.ts
5106
3588
  var isWithin = (dir, path) => {
5107
- const rel = relative6(dir, path);
5108
- return !(rel.startsWith("..") || isAbsolute5(rel));
3589
+ const rel = relative5(dir, path);
3590
+ return !(rel.startsWith("..") || isAbsolute3(rel));
5109
3591
  };
5110
3592
  var applyConfigOverrides = (config, overrides) => {
5111
3593
  if (!overrides) {
5112
3594
  return config;
5113
3595
  }
3596
+ const { contentRoot } = overrides;
3597
+ const sources = contentRoot ? config.content.sources.map((source) => source.kind === "filesystem" ? { ...source, options: { ...source.options, root: contentRoot } } : source) : config.content.sources;
5114
3598
  return {
5115
3599
  ...config,
5116
- content: {
5117
- ...config.content,
5118
- root: overrides.contentRoot ?? config.content.root
5119
- },
5120
- deployment: {
5121
- ...config.deployment,
5122
- adapter: overrides.adapter ?? config.deployment.adapter,
5123
- base: overrides.base ?? config.deployment.base,
5124
- output: overrides.output ?? config.deployment.output
5125
- }
3600
+ content: { ...config.content, sources }
5126
3601
  };
5127
3602
  };
5128
3603
  var entryIdDiagnostics = (pages, collectionBase) => {
@@ -5133,7 +3608,7 @@ var entryIdDiagnostics = (pages, collectionBase) => {
5133
3608
  continue;
5134
3609
  }
5135
3610
  seen.add(page.sourcePath);
5136
- const expected = relative6(collectionBase, page.sourcePath).split("\\").join("/");
3611
+ const expected = relative5(collectionBase, page.sourcePath).split("\\").join("/");
5137
3612
  const entryId = page.entryId ?? page.source.ref;
5138
3613
  if (expected !== entryId) {
5139
3614
  diagnostics.push({
@@ -5141,7 +3616,7 @@ var entryIdDiagnostics = (pages, collectionBase) => {
5141
3616
  file: page.sourcePath,
5142
3617
  message: `Content source "${page.source.name}" is rooted outside the docs collection base, so ${page.route} resolves entry id "${entryId}" but the collection would generate "${expected}" — the page would 404 at runtime.`,
5143
3618
  severity: "error",
5144
- suggestion: "Use a single filesystem source (the docs collection roots at it), or root every filesystem source at content.root and partition them with include globs — a root at a subdirectory of content.root still mismatches."
3619
+ suggestion: "Use a single filesystem() source (the docs collection roots at it), or give every filesystem() source the same root and partition them with include globs — a root at a subdirectory of the first source's root still mismatches."
5145
3620
  });
5146
3621
  }
5147
3622
  }
@@ -5163,6 +3638,7 @@ var normalizeLoadedEntries = (loaded, config) => {
5163
3638
  frontmatterExtend,
5164
3639
  i18n: config.i18n,
5165
3640
  source: {
3641
+ monolingual: source.monolingual,
5166
3642
  name: source.name,
5167
3643
  prefix: source.prefix,
5168
3644
  staged: source.staged
@@ -5179,13 +3655,15 @@ var normalizeLoadedEntries = (loaded, config) => {
5179
3655
  }
5180
3656
  return { diagnostics: allDiagnostics, droppedPages, pages };
5181
3657
  };
3658
+ var isLogoShorthand = (logo) => typeof logo === "string";
3659
+ var logoHref = (logo) => logo === undefined || isLogoShorthand(logo) ? "/" : logo.href ?? "/";
5182
3660
  var scanProject = async (root, options = {}) => {
5183
3661
  const mode = options.mode ?? "dev";
5184
3662
  const preview = options.preview ?? false;
5185
3663
  const configResult = await loadConfig(root, {
5186
3664
  devServerUrl: options.devServerUrl
5187
3665
  });
5188
- const config = applyDeploymentEnv(applyConfigOverrides(configResult.config, options.overrides));
3666
+ const config = applyConfigOverrides(configResult.config, options.overrides);
5189
3667
  const context = resolveProjectContext(root, config, {
5190
3668
  runtimeDir: options.runtimeDir
5191
3669
  });
@@ -5249,8 +3727,14 @@ var scanProject = async (root, options = {}) => {
5249
3727
  const undated = pages.filter((page) => page.sourcePath && !page.lastModified && contentRoots.some((dir) => isWithin(dir, page.sourcePath ?? ""))).length;
5250
3728
  lastModifiedWarnings.push(...lastModifiedShallowWarning(context.root, undated));
5251
3729
  }
3730
+ const customPages = context.pagesRoot ? await discoverPages(context.pagesRoot) : [];
5252
3731
  const graph = buildContentGraph(pages, {
5253
3732
  basePath: config.basePath,
3733
+ brandHref: logoHref(config.logo),
3734
+ extraRoutes: new Set([
3735
+ ...customStaticRoutes(customPages),
3736
+ ...hasChangelogIndex(pages, config) ? [CHANGELOG_INDEX_ROUTE] : []
3737
+ ]),
5254
3738
  folderMeta: folderMeta.meta,
5255
3739
  i18n: config.i18n,
5256
3740
  navigation: config.navigation,
@@ -5267,7 +3751,7 @@ var scanProject = async (root, options = {}) => {
5267
3751
  ...contentDiagnostics,
5268
3752
  ...includeDiagnostics,
5269
3753
  ...folderMeta.diagnostics,
5270
- ...entryIdDiagnostics(pages, resolveDocsCollection(config, context).base),
3754
+ ...entryIdDiagnostics(pages, resolveDocsCollection(config, context.root).base),
5271
3755
  ...graph.diagnostics,
5272
3756
  ...i18nWarnings,
5273
3757
  ...versionWarnings,
@@ -5282,7 +3766,7 @@ var scanProject = async (root, options = {}) => {
5282
3766
  };
5283
3767
  };
5284
3768
 
5285
- export { YAML_SCHEMA, frontmatter_default, TOC_HIDDEN_KEY, parseHeadingMarkers, BLUME_IGNORE_DIRS, i18nEnabled, localeCodes, resolveFallbackLocale, localePrefix, localizeRoute, localeTargetPath, validateSearchPopularIcons, validateNavTargets, VERSION_LIKE, versionizeRoute, nextFenceState, HTML_COMMENT, INLINE_CODE, scanBody, MD_IMAGE, targetOffsetIn, hasIncludeStatements, expandIncludes, buildIncludeGraph, contentIndexable, buildManifest, hashText, specOf, operationOf, specAddresses, apiNamePhrase, operationSignature, isOpenApiSource, parseYouTubeId, resolveDocsCollection, isWithin, scanProject };
3769
+ export { validateSearchPopularIcons, validateNavTargets, contentIndexable, buildManifest, isWithin, scanProject };
5286
3770
 
5287
- //# debugId=9C98B15F280A03E064756E2164756E21
5288
- //# sourceMappingURL=chunk-1jefwnfs.js.map
3771
+ //# debugId=EBFFBF92D39DE54464756E2164756E21
3772
+ //# sourceMappingURL=chunk-fs23ddbb.js.map