blume 2.1.0 → 2.1.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 (397) hide show
  1. package/CHANGELOG.md +94 -0
  2. package/dist/cli/{chunk-5cg2rzgf.js → chunk-073rdvgb.js} +19 -48
  3. package/dist/cli/{chunk-5cg2rzgf.js.map → chunk-073rdvgb.js.map} +4 -5
  4. package/dist/cli/{chunk-ga0pf4aj.js → chunk-0n9xkp3d.js} +12 -3
  5. package/dist/cli/{chunk-ga0pf4aj.js.map → chunk-0n9xkp3d.js.map} +3 -3
  6. package/dist/cli/chunk-150jk7km.js +52 -0
  7. package/dist/cli/chunk-150jk7km.js.map +10 -0
  8. package/dist/cli/{chunk-rekqz4qg.js → chunk-313vnn40.js} +16 -15
  9. package/dist/cli/chunk-313vnn40.js.map +10 -0
  10. package/dist/cli/{chunk-gp84za0h.js → chunk-3v3ffbrj.js} +2 -2
  11. package/dist/cli/{chunk-9xr4dgee.js → chunk-3x6x3pph.js} +29 -16
  12. package/dist/cli/chunk-3x6x3pph.js.map +10 -0
  13. package/dist/cli/{chunk-0gz36akr.js → chunk-42t4jxdx.js} +21 -5
  14. package/dist/cli/chunk-42t4jxdx.js.map +12 -0
  15. package/dist/cli/{chunk-ta27feev.js → chunk-54ztw1nj.js} +21 -2
  16. package/dist/cli/chunk-54ztw1nj.js.map +10 -0
  17. package/dist/cli/{chunk-jnwj5kjj.js → chunk-5vjtgs6e.js} +20 -7
  18. package/dist/cli/{chunk-jnwj5kjj.js.map → chunk-5vjtgs6e.js.map} +3 -3
  19. package/dist/cli/chunk-66w9j7p6.js +322 -0
  20. package/dist/cli/chunk-66w9j7p6.js.map +14 -0
  21. package/dist/cli/{chunk-nd1821sk.js → chunk-7ppf1rva.js} +32 -18
  22. package/dist/cli/chunk-7ppf1rva.js.map +10 -0
  23. package/dist/cli/{chunk-p804f3pe.js → chunk-8y2kmjv8.js} +6 -6
  24. package/dist/cli/{chunk-2f7n4qzt.js → chunk-c1y538kd.js} +1 -1
  25. package/dist/cli/{chunk-1n228xqb.js → chunk-c887fj0s.js} +2 -2
  26. package/dist/cli/{chunk-y5h4r2y0.js → chunk-cfyg47zg.js} +15 -5
  27. package/dist/cli/chunk-cfyg47zg.js.map +10 -0
  28. package/dist/cli/{chunk-cmtcnh9f.js → chunk-dzwehpy7.js} +2 -2
  29. package/dist/cli/{chunk-h2a48yhd.js → chunk-e6atmn4h.js} +119 -15
  30. package/dist/cli/{chunk-h2a48yhd.js.map → chunk-e6atmn4h.js.map} +10 -10
  31. package/dist/cli/{chunk-5ffywsmb.js → chunk-e8xw9qkh.js} +8 -6
  32. package/dist/cli/{chunk-5ffywsmb.js.map → chunk-e8xw9qkh.js.map} +3 -3
  33. package/dist/cli/{chunk-ntv4qrt7.js → chunk-e9q7a2ak.js} +14 -14
  34. package/dist/cli/{chunk-xkz5xbtv.js → chunk-ehweg4fr.js} +2366 -1114
  35. package/dist/cli/chunk-ehweg4fr.js.map +218 -0
  36. package/dist/cli/{chunk-zf748avv.js → chunk-ep6nd0rn.js} +176 -281
  37. package/dist/cli/{chunk-zf748avv.js.map → chunk-ep6nd0rn.js.map} +11 -12
  38. package/dist/cli/{chunk-yxmk4pec.js → chunk-fdrkhpjy.js} +16 -15
  39. package/dist/cli/{chunk-yxmk4pec.js.map → chunk-fdrkhpjy.js.map} +3 -3
  40. package/dist/cli/{chunk-5wj2qwn2.js → chunk-ft9vfys6.js} +4 -4
  41. package/dist/cli/{chunk-4h8z9vv5.js → chunk-gb420vvz.js} +89 -46
  42. package/dist/cli/chunk-gb420vvz.js.map +17 -0
  43. package/dist/cli/{chunk-713mngdq.js → chunk-hebn0tpx.js} +76 -10
  44. package/dist/cli/chunk-hebn0tpx.js.map +14 -0
  45. package/dist/cli/{chunk-2n4ps1b3.js → chunk-htrwgabr.js} +40 -22
  46. package/dist/cli/chunk-htrwgabr.js.map +15 -0
  47. package/dist/cli/{chunk-zhvd6adm.js → chunk-hx3xqnk9.js} +13 -13
  48. package/dist/cli/chunk-j134vrp2.js +65 -0
  49. package/dist/cli/chunk-j134vrp2.js.map +10 -0
  50. package/dist/cli/{chunk-y4ykbark.js → chunk-kma9f6sf.js} +2 -2
  51. package/dist/cli/{chunk-py62fewh.js → chunk-mrcccgnh.js} +3 -3
  52. package/dist/cli/{chunk-2f3ndkpw.js → chunk-n0v7m8ry.js} +3 -3
  53. package/dist/cli/{chunk-43zcf4gz.js → chunk-ncj7w596.js} +522 -353
  54. package/dist/cli/chunk-ncj7w596.js.map +35 -0
  55. package/dist/cli/{chunk-9geymqns.js → chunk-nq1d20w2.js} +14 -14
  56. package/dist/cli/{chunk-g2z9x01v.js → chunk-nrns10mj.js} +3 -3
  57. package/dist/cli/{chunk-e7vvk5ya.js → chunk-sds923fa.js} +3 -3
  58. package/dist/cli/{chunk-cy5bm0j0.js → chunk-t2m5j4pr.js} +2 -2
  59. package/dist/cli/{chunk-cy5bm0j0.js.map → chunk-t2m5j4pr.js.map} +1 -1
  60. package/dist/cli/{chunk-yj17vs39.js → chunk-tdqhyxfz.js} +5 -5
  61. package/dist/cli/{chunk-q3wxxq39.js → chunk-tphac74f.js} +5 -5
  62. package/dist/cli/{chunk-6bm0xpgd.js → chunk-w1r4vase.js} +2 -2
  63. package/dist/cli/index.js +24 -19
  64. package/dist/cli/index.js.map +3 -3
  65. package/dist/types/ai/ask-context.d.ts +6 -2
  66. package/dist/types/ai/component-markdown.d.ts +2 -0
  67. package/dist/types/ai/openapi-components.d.ts +4 -1
  68. package/dist/types/astro/integration.d.ts +5 -4
  69. package/dist/types/astro/markdown-negotiation.d.ts +3 -0
  70. package/dist/types/astro/pages.d.ts +10 -0
  71. package/dist/types/cli/init/scaffold.d.ts +1 -1
  72. package/dist/types/components/content/api-field.d.ts +4 -1
  73. package/dist/types/components/content/api-page.d.ts +6 -1
  74. package/dist/types/components/openapi/code-samples.d.ts +29 -0
  75. package/dist/types/components/openapi/helpers.d.ts +22 -1
  76. package/dist/types/components/openapi/security.d.ts +10 -0
  77. package/dist/types/components/openapi/snippets.d.ts +60 -0
  78. package/dist/types/core/changelog-index.d.ts +8 -0
  79. package/dist/types/core/config-input.d.ts +30 -3
  80. package/dist/types/core/data.d.ts +4 -0
  81. package/dist/types/core/i18n-ui.d.ts +2 -0
  82. package/dist/types/core/links.d.ts +8 -0
  83. package/dist/types/core/probe.d.ts +2 -0
  84. package/dist/types/core/project-graph.d.ts +6 -0
  85. package/dist/types/core/redirect-patterns.d.ts +11 -3
  86. package/dist/types/core/schema.d.ts +119 -0
  87. package/dist/types/core/sources/cache.d.ts +3 -0
  88. package/dist/types/core/sources/contentful-rich-text.d.ts +11 -2
  89. package/dist/types/core/sources/contentful.d.ts +17 -1
  90. package/dist/types/core/sources/lexical.d.ts +5 -0
  91. package/dist/types/core/sources/normalize.d.ts +20 -1
  92. package/dist/types/core/sources/remote.d.ts +37 -10
  93. package/dist/types/core/sources/sanity.d.ts +1 -1
  94. package/dist/types/core/types.d.ts +17 -0
  95. package/dist/types/deploy/adapter-output.d.ts +2 -1
  96. package/dist/types/deploy/adapters/cloudflare.d.ts +7 -5
  97. package/dist/types/deploy/adapters/node.d.ts +58 -23
  98. package/dist/types/deploy/adapters/registry.d.ts +10 -0
  99. package/dist/types/deploy/adapters/types.d.ts +3 -2
  100. package/dist/types/deploy/platforms/cloudflare.d.ts +10 -7
  101. package/dist/types/deploy/platforms/node.d.ts +3 -1
  102. package/dist/types/deploy/platforms/types.d.ts +18 -7
  103. package/dist/types/deploy/platforms/vercel.d.ts +20 -1
  104. package/dist/types/deploy/redirects.d.ts +17 -1
  105. package/dist/types/deploy/sitemap.d.ts +2 -1
  106. package/dist/types/deploy/vercel-negotiation.d.ts +42 -21
  107. package/dist/types/openapi/checks.d.ts +27 -0
  108. package/dist/types/openapi/graphql.d.ts +6 -0
  109. package/dist/types/openapi/model.d.ts +15 -4
  110. package/dist/types/openapi/parse.d.ts +3 -0
  111. package/dist/types/openapi/references.d.ts +16 -0
  112. package/dist/types/ratelimit/index.d.ts +1 -1
  113. package/dist/types/ratelimit/schema.d.ts +8 -0
  114. package/dist/types/ratelimit/upstash.d.ts +39 -14
  115. package/dist/types/reference/graphql.d.ts +76 -1
  116. package/dist/types/reference/index.d.ts +1 -1
  117. package/dist/types/reference/openapi.d.ts +2 -2
  118. package/dist/types/reference/options.d.ts +37 -1
  119. package/dist/types/reference/schema.d.ts +84 -0
  120. package/dist/types/search/adapters/registry.d.ts +3 -0
  121. package/dist/types/search/adapters/typesense.d.ts +14 -4
  122. package/dist/types/search/build.d.ts +3 -1
  123. package/dist/types/search/orama-index.d.ts +9 -9
  124. package/dist/types/search/sync/algolia.d.ts +5 -2
  125. package/dist/types/search/sync/index.d.ts +3 -1
  126. package/dist/types/search/sync/typesense.d.ts +11 -7
  127. package/dist/types/sources/contentful.d.ts +16 -0
  128. package/dist/types/sources/github-releases.d.ts +8 -1
  129. package/dist/types/sources/payload.d.ts +2 -0
  130. package/dist/types/sources/registry.d.ts +54 -0
  131. package/dist/types/sources/shared.d.ts +3 -0
  132. package/dist/types/sources/strapi.d.ts +2 -0
  133. package/docs/01-quickstart.mdx +4 -2
  134. package/docs/02-deployment.mdx +31 -13
  135. package/docs/advanced/changelog.mdx +11 -0
  136. package/docs/advanced/skills.mdx +1 -1
  137. package/docs/cli/audit.mdx +30 -7
  138. package/docs/cli/doctor.mdx +5 -1
  139. package/docs/cli/evals.mdx +4 -4
  140. package/docs/cli/translate.mdx +1 -0
  141. package/docs/cli/validate.mdx +4 -3
  142. package/docs/configuration/analytics.mdx +1 -1
  143. package/docs/configuration/assistant.mdx +6 -2
  144. package/docs/configuration/customization.mdx +4 -2
  145. package/docs/configuration/export.mdx +5 -3
  146. package/docs/configuration/index.mdx +3 -2
  147. package/docs/configuration/rate-limiting.mdx +23 -2
  148. package/docs/configuration/search.mdx +37 -14
  149. package/docs/content/components.mdx +2 -2
  150. package/docs/content/frontmatter.mdx +5 -3
  151. package/docs/content/i18n.mdx +2 -2
  152. package/docs/content/includes.mdx +1 -1
  153. package/docs/content/navigation.mdx +4 -4
  154. package/docs/content/sources/contentful.mdx +7 -4
  155. package/docs/content/sources/custom.mdx +7 -3
  156. package/docs/content/sources/github-releases.mdx +10 -2
  157. package/docs/content/sources/index.mdx +3 -3
  158. package/docs/content/sources/notion.mdx +1 -1
  159. package/docs/content/sources/obsidian.mdx +1 -1
  160. package/docs/content/sources/payload.mdx +3 -2
  161. package/docs/content/sources/remote-mdx.mdx +4 -0
  162. package/docs/content/sources/sanity.mdx +2 -2
  163. package/docs/content/sources/strapi.mdx +3 -2
  164. package/docs/content/syntax.mdx +2 -2
  165. package/docs/discoverability/agent-discovery.mdx +1 -1
  166. package/docs/discoverability/index.mdx +1 -1
  167. package/docs/discoverability/json-api.mdx +2 -0
  168. package/docs/discoverability/llms-txt.mdx +1 -1
  169. package/docs/discoverability/markdown.mdx +3 -1
  170. package/docs/discoverability/mcp.mdx +4 -0
  171. package/docs/discoverability/sitemap-and-robots.mdx +1 -1
  172. package/docs/references/api-pages.mdx +1 -1
  173. package/docs/references/asyncapi.mdx +3 -1
  174. package/docs/references/graphql.mdx +13 -1
  175. package/docs/references/openapi.mdx +50 -8
  176. package/package.json +9 -9
  177. package/skills/blume-migrate/SKILL.md +1 -1
  178. package/skills/blume-migrate/references/docusaurus.md +4 -4
  179. package/skills/blume-migrate/references/fumadocs.md +17 -13
  180. package/skills/blume-migrate/references/nextra.md +8 -5
  181. package/skills/blume-migrate/references/starlight.md +9 -7
  182. package/src/ai/ask-context.ts +33 -3
  183. package/src/ai/ask-limits.ts +13 -0
  184. package/src/ai/changelog-markdown.ts +11 -6
  185. package/src/ai/component-markdown.ts +1 -1
  186. package/src/ai/cors.ts +12 -3
  187. package/src/ai/mcp/query.ts +14 -6
  188. package/src/ai/openapi-components.ts +28 -4
  189. package/src/ai/relative-links.ts +4 -38
  190. package/src/astro/generate.ts +78 -48
  191. package/src/astro/integration.ts +16 -13
  192. package/src/astro/markdown-negotiation.ts +3 -0
  193. package/src/astro/module-types.ts +3 -1
  194. package/src/astro/pages.ts +23 -0
  195. package/src/astro/templates.ts +125 -80
  196. package/src/audit/catalog.ts +19 -3
  197. package/src/audit/checks/links.ts +14 -1
  198. package/src/audit/checks/network.ts +110 -6
  199. package/src/audit/checks/redirects.ts +6 -4
  200. package/src/audit/checks/robots.ts +58 -2
  201. package/src/audit/run.ts +3 -0
  202. package/src/audit/types.ts +2 -0
  203. package/src/audit/url.ts +14 -5
  204. package/src/blume-modules.d.ts +5 -1
  205. package/src/cli/args.ts +51 -0
  206. package/src/cli/build-failure.ts +23 -0
  207. package/src/cli/commands/audit.ts +10 -1
  208. package/src/cli/commands/dev.ts +18 -2
  209. package/src/cli/commands/doctor.ts +45 -12
  210. package/src/cli/commands/eval.ts +36 -4
  211. package/src/cli/commands/init.ts +6 -4
  212. package/src/cli/commands/validate.ts +10 -1
  213. package/src/cli/init/scaffold.ts +3 -3
  214. package/src/cli/prepare.ts +6 -3
  215. package/src/cli/required-secrets.ts +1 -1
  216. package/src/components/content/ApiPlayground.astro +6 -2
  217. package/src/components/content/api-field.ts +4 -1
  218. package/src/components/content/api-page.ts +31 -7
  219. package/src/components/content/mermaid-element.ts +15 -1
  220. package/src/components/islands/hooks.ts +27 -1
  221. package/src/components/layout/PageActions.astro +139 -24
  222. package/src/components/layout/RootLayout.astro +10 -2
  223. package/src/components/layout/Search.astro +17 -3
  224. package/src/components/layout/TableOfContents.astro +24 -17
  225. package/src/components/layout/head-scripts.ts +6 -1
  226. package/src/components/layout/search/algolia.ts +10 -1
  227. package/src/components/layout/search/analytics.ts +42 -14
  228. package/src/components/layout/search/endpoint.ts +48 -5
  229. package/src/components/layout/search/pagefind.ts +114 -5
  230. package/src/components/layout/search/types.ts +12 -0
  231. package/src/components/layout/search/typesense.ts +18 -6
  232. package/src/components/openapi/AsyncApiOperation.astro +14 -7
  233. package/src/components/openapi/Authorization.astro +10 -0
  234. package/src/components/openapi/GraphqlOperation.astro +5 -0
  235. package/src/components/openapi/Operation.astro +24 -1
  236. package/src/components/openapi/ParametersTable.astro +8 -4
  237. package/src/components/openapi/Playground.astro +7 -1
  238. package/src/components/openapi/RequestBody.astro +19 -2
  239. package/src/components/openapi/Responses.astro +1 -0
  240. package/src/components/openapi/SchemaTable.astro +15 -3
  241. package/src/components/openapi/async-snippets.ts +61 -5
  242. package/src/components/openapi/async.ts +123 -29
  243. package/src/components/openapi/graphql-helpers.ts +35 -12
  244. package/src/components/openapi/helpers.ts +36 -2
  245. package/src/components/openapi/message-model.ts +54 -11
  246. package/src/components/openapi/message.ts +14 -4
  247. package/src/components/openapi/operation-model.ts +16 -7
  248. package/src/components/openapi/playground-client.ts +24 -5
  249. package/src/components/openapi/playground-schema.ts +1 -1
  250. package/src/components/openapi/schema-tree.ts +44 -14
  251. package/src/components/openapi/security.ts +10 -0
  252. package/src/components/openapi/snippets.ts +5 -3
  253. package/src/components/openapi/validate-json.ts +4 -3
  254. package/src/core/changelog-index.ts +31 -1
  255. package/src/core/component-diagnostics.ts +53 -0
  256. package/src/core/component-overrides.ts +32 -0
  257. package/src/core/config-input.ts +31 -3
  258. package/src/core/data.ts +2 -0
  259. package/src/core/diagnostics.ts +5 -0
  260. package/src/core/i18n-ui.ts +2 -0
  261. package/src/core/includes.ts +1 -1
  262. package/src/core/links.ts +47 -17
  263. package/src/core/probe.ts +26 -6
  264. package/src/core/project-graph.ts +59 -18
  265. package/src/core/redirect-patterns.ts +36 -7
  266. package/src/core/schema.ts +21 -2
  267. package/src/core/sources/cache.ts +6 -0
  268. package/src/core/sources/contentful-rich-text.ts +29 -6
  269. package/src/core/sources/contentful.ts +135 -25
  270. package/src/core/sources/filesystem.ts +26 -4
  271. package/src/core/sources/github-releases.ts +12 -1
  272. package/src/core/sources/lexical.ts +15 -10
  273. package/src/core/sources/mdx-remote.ts +62 -10
  274. package/src/core/sources/normalize.ts +227 -41
  275. package/src/core/sources/notion.ts +7 -1
  276. package/src/core/sources/obsidian.ts +16 -3
  277. package/src/core/sources/payload.ts +38 -15
  278. package/src/core/sources/remote.ts +72 -15
  279. package/src/core/sources/resolve.ts +11 -1
  280. package/src/core/sources/sanity.ts +43 -25
  281. package/src/core/sources/strapi.ts +2 -2
  282. package/src/core/types.ts +18 -0
  283. package/src/core/ui-packs/ar.ts +1 -0
  284. package/src/core/ui-packs/bg.ts +1 -0
  285. package/src/core/ui-packs/bn.ts +1 -0
  286. package/src/core/ui-packs/ca.ts +1 -0
  287. package/src/core/ui-packs/cs.ts +1 -0
  288. package/src/core/ui-packs/da.ts +1 -0
  289. package/src/core/ui-packs/de.ts +1 -0
  290. package/src/core/ui-packs/el.ts +1 -0
  291. package/src/core/ui-packs/es.ts +1 -0
  292. package/src/core/ui-packs/fa.ts +1 -0
  293. package/src/core/ui-packs/fi.ts +1 -0
  294. package/src/core/ui-packs/fr.ts +1 -0
  295. package/src/core/ui-packs/he.ts +1 -0
  296. package/src/core/ui-packs/hi.ts +1 -0
  297. package/src/core/ui-packs/hr.ts +1 -0
  298. package/src/core/ui-packs/hu.ts +1 -0
  299. package/src/core/ui-packs/id.ts +1 -0
  300. package/src/core/ui-packs/it.ts +1 -0
  301. package/src/core/ui-packs/ja.ts +1 -0
  302. package/src/core/ui-packs/ko.ts +1 -0
  303. package/src/core/ui-packs/nl.ts +1 -0
  304. package/src/core/ui-packs/no.ts +1 -0
  305. package/src/core/ui-packs/pl.ts +1 -0
  306. package/src/core/ui-packs/pt-br.ts +1 -0
  307. package/src/core/ui-packs/pt.ts +1 -0
  308. package/src/core/ui-packs/ro.ts +1 -0
  309. package/src/core/ui-packs/ru.ts +1 -0
  310. package/src/core/ui-packs/sk.ts +1 -0
  311. package/src/core/ui-packs/sr.ts +1 -0
  312. package/src/core/ui-packs/sv.ts +1 -0
  313. package/src/core/ui-packs/th.ts +1 -0
  314. package/src/core/ui-packs/tr.ts +1 -0
  315. package/src/core/ui-packs/uk.ts +1 -0
  316. package/src/core/ui-packs/vi.ts +1 -0
  317. package/src/core/ui-packs/zh-tw.ts +1 -0
  318. package/src/core/ui-packs/zh.ts +1 -0
  319. package/src/deploy/adapter-output.ts +7 -2
  320. package/src/deploy/adapters/cloudflare.ts +7 -5
  321. package/src/deploy/adapters/node.ts +47 -9
  322. package/src/deploy/adapters/types.ts +7 -3
  323. package/src/deploy/artifacts.ts +2 -1
  324. package/src/deploy/platforms/cloudflare.ts +13 -9
  325. package/src/deploy/platforms/netlify.ts +2 -1
  326. package/src/deploy/platforms/node.ts +12 -2
  327. package/src/deploy/platforms/types.ts +21 -7
  328. package/src/deploy/platforms/vercel.ts +113 -16
  329. package/src/deploy/redirects.ts +66 -6
  330. package/src/deploy/rss.ts +3 -1
  331. package/src/deploy/sitemap.ts +39 -5
  332. package/src/deploy/vercel-negotiation.ts +290 -102
  333. package/src/eval/agents.ts +18 -1
  334. package/src/eval/prompts.ts +6 -3
  335. package/src/eval/report.ts +34 -13
  336. package/src/eval/run.ts +5 -0
  337. package/src/markdown/relative-links.ts +3 -2
  338. package/src/openapi/checks.ts +273 -0
  339. package/src/openapi/graphql-build.ts +21 -0
  340. package/src/openapi/graphql.ts +11 -0
  341. package/src/openapi/model.ts +15 -3
  342. package/src/openapi/parse.ts +127 -2
  343. package/src/openapi/references.ts +43 -3
  344. package/src/openapi/source.ts +39 -14
  345. package/src/ratelimit/index.ts +1 -1
  346. package/src/ratelimit/runtime.ts +37 -19
  347. package/src/ratelimit/upstash.ts +40 -10
  348. package/src/reference/graphql.ts +15 -1
  349. package/src/reference/index.ts +1 -0
  350. package/src/reference/openapi.ts +2 -2
  351. package/src/reference/options.ts +29 -1
  352. package/src/registry/eject.ts +3 -1
  353. package/src/search/adapters/locale-scope.ts +30 -0
  354. package/src/search/adapters/typesense.ts +14 -4
  355. package/src/search/build.ts +21 -3
  356. package/src/search/orama-index.ts +163 -31
  357. package/src/search/source-pages.ts +29 -0
  358. package/src/search/sync/algolia.ts +99 -9
  359. package/src/search/sync/index.ts +52 -13
  360. package/src/search/sync/typesense.ts +72 -27
  361. package/src/sources/contentful.ts +12 -0
  362. package/src/sources/github-releases.ts +7 -1
  363. package/src/sources/shared.ts +3 -0
  364. package/src/theme/entry.ts +29 -3
  365. package/src/translate/validate.ts +23 -1
  366. package/dist/cli/chunk-0gz36akr.js.map +0 -12
  367. package/dist/cli/chunk-2n4ps1b3.js.map +0 -15
  368. package/dist/cli/chunk-43zcf4gz.js.map +0 -36
  369. package/dist/cli/chunk-4ejxbzee.js +0 -39
  370. package/dist/cli/chunk-4ejxbzee.js.map +0 -10
  371. package/dist/cli/chunk-4h8z9vv5.js.map +0 -16
  372. package/dist/cli/chunk-713mngdq.js.map +0 -14
  373. package/dist/cli/chunk-9xr4dgee.js.map +0 -10
  374. package/dist/cli/chunk-amrb6hgh.js +0 -145
  375. package/dist/cli/chunk-amrb6hgh.js.map +0 -11
  376. package/dist/cli/chunk-nd1821sk.js.map +0 -10
  377. package/dist/cli/chunk-rekqz4qg.js.map +0 -10
  378. package/dist/cli/chunk-ta27feev.js.map +0 -10
  379. package/dist/cli/chunk-xkz5xbtv.js.map +0 -213
  380. package/dist/cli/chunk-y5h4r2y0.js.map +0 -10
  381. /package/dist/cli/{chunk-gp84za0h.js.map → chunk-3v3ffbrj.js.map} +0 -0
  382. /package/dist/cli/{chunk-p804f3pe.js.map → chunk-8y2kmjv8.js.map} +0 -0
  383. /package/dist/cli/{chunk-2f7n4qzt.js.map → chunk-c1y538kd.js.map} +0 -0
  384. /package/dist/cli/{chunk-1n228xqb.js.map → chunk-c887fj0s.js.map} +0 -0
  385. /package/dist/cli/{chunk-cmtcnh9f.js.map → chunk-dzwehpy7.js.map} +0 -0
  386. /package/dist/cli/{chunk-ntv4qrt7.js.map → chunk-e9q7a2ak.js.map} +0 -0
  387. /package/dist/cli/{chunk-5wj2qwn2.js.map → chunk-ft9vfys6.js.map} +0 -0
  388. /package/dist/cli/{chunk-zhvd6adm.js.map → chunk-hx3xqnk9.js.map} +0 -0
  389. /package/dist/cli/{chunk-y4ykbark.js.map → chunk-kma9f6sf.js.map} +0 -0
  390. /package/dist/cli/{chunk-py62fewh.js.map → chunk-mrcccgnh.js.map} +0 -0
  391. /package/dist/cli/{chunk-2f3ndkpw.js.map → chunk-n0v7m8ry.js.map} +0 -0
  392. /package/dist/cli/{chunk-9geymqns.js.map → chunk-nq1d20w2.js.map} +0 -0
  393. /package/dist/cli/{chunk-g2z9x01v.js.map → chunk-nrns10mj.js.map} +0 -0
  394. /package/dist/cli/{chunk-e7vvk5ya.js.map → chunk-sds923fa.js.map} +0 -0
  395. /package/dist/cli/{chunk-yj17vs39.js.map → chunk-tdqhyxfz.js.map} +0 -0
  396. /package/dist/cli/{chunk-q3wxxq39.js.map → chunk-tphac74f.js.map} +0 -0
  397. /package/dist/cli/{chunk-6bm0xpgd.js.map → chunk-w1r4vase.js.map} +0 -0
@@ -6,6 +6,7 @@ import {
6
6
  i18nDiagnostics,
7
7
  blumeReferences,
8
8
  frontmatter_default,
9
+ resolveLocalizable,
9
10
  resolveDocsCollection,
10
11
  CHANGELOG_INDEX_ROUTE,
11
12
  hasChangelogIndex,
@@ -22,6 +23,7 @@ import {
22
23
  pagesByRef,
23
24
  buildNavigation,
24
25
  PUBLIC_API_URL,
26
+ isPatternPath,
25
27
  pathsUnderPattern,
26
28
  hasVariables,
27
29
  substituteVariables,
@@ -33,6 +35,7 @@ import {
33
35
  slugify,
34
36
  slugifyPath,
35
37
  unloadablePathDiagnostic,
38
+ frontmatterYamlDiagnostic,
36
39
  extractHeadings,
37
40
  strippedLineOffset,
38
41
  isStringValue,
@@ -73,7 +76,7 @@ import {
73
76
  createModuleLoader,
74
77
  resolveProjectContext,
75
78
  loadConfig
76
- } from "./chunk-xkz5xbtv.js";
79
+ } from "./chunk-ehweg4fr.js";
77
80
  import {
78
81
  getBlumeVersion,
79
82
  BlumeError,
@@ -81,6 +84,7 @@ import {
81
84
  } from "./index.js";
82
85
  import {
83
86
  trimChar,
87
+ normalizePath,
84
88
  isInternalPath,
85
89
  withBasePath,
86
90
  mountBasePath
@@ -93,6 +97,232 @@ import {
93
97
  ignoringWatchListener
94
98
  } from "./chunk-kwrg63tm.js";
95
99
 
100
+ // src/theme/icon-kind.ts
101
+ var IMAGE_ICON = /^(?:https?:\/\/|data:image\/|\/|\.{1,2}\/)|\.(?:avif|gif|jpe?g|png|svg|webp)$/iu;
102
+ var INLINE_SVG = /^\s*<svg[\s\S]*<\/svg>\s*$/u;
103
+ var isImageIcon = (value) => IMAGE_ICON.test(value);
104
+ var isInlineSvg = (value) => INLINE_SVG.test(value);
105
+ var isAssetIcon = (value) => isInlineSvg(value) || isImageIcon(value);
106
+
107
+ // src/theme/icons.ts
108
+ import { createRequire } from "node:module";
109
+ import { getIconData, iconToSVG } from "@iconify/utils";
110
+ var requireJson = createRequire(import.meta.url);
111
+ var loadSet = (pkg) => requireJson(pkg);
112
+ var SETS = {
113
+ lucide: loadSet("@iconify-json/lucide/icons.json")
114
+ };
115
+ var DEFAULT_SET = "lucide";
116
+ var PREFIX_SETS = {
117
+ lucide: "lucide"
118
+ };
119
+ var ownEntry = (map, key) => Object.hasOwn(map, key) ? map[key] : undefined;
120
+ var normalize = (name) => name.trim().toLowerCase().replaceAll(/[\s_]+/gu, "-");
121
+ var fromSet = (setName, iconName) => {
122
+ const set = ownEntry(SETS, setName);
123
+ const data = set && getIconData(set, iconName);
124
+ if (!data) {
125
+ return null;
126
+ }
127
+ const { attributes, body } = iconToSVG(data, { height: "auto" });
128
+ return { body, name: iconName, viewBox: attributes.viewBox };
129
+ };
130
+ var resolveIcon = (name) => {
131
+ const normalized = normalize(name);
132
+ const colon = normalized.indexOf(":");
133
+ if (colon > 0) {
134
+ const setName = ownEntry(PREFIX_SETS, normalized.slice(0, colon));
135
+ return setName ? fromSet(setName, normalized.slice(colon + 1)) : null;
136
+ }
137
+ return fromSet(DEFAULT_SET, normalized);
138
+ };
139
+ var hasIcon = (name) => resolveIcon(name) !== null;
140
+
141
+ // src/core/nav-diagnostics.ts
142
+ var ICON_FORMAT_HINT = "Use a built-in icon name, an image path/URL, or inline SVG markup.";
143
+ var flattenNodes = (nodes) => nodes.flatMap((node) => node.kind === "group" ? [node, ...flattenNodes(node.children)] : [node]);
144
+ var collectIcons = (navigation) => {
145
+ const icons = [];
146
+ const push = (icon, where) => {
147
+ if (icon) {
148
+ icons.push({ icon, where });
149
+ }
150
+ };
151
+ for (const tab of navigation.tabs) {
152
+ push(tab.icon, `tab "${tab.label}"`);
153
+ for (const item of tab.items ?? []) {
154
+ push(item.icon, `tab item "${item.label}"`);
155
+ }
156
+ }
157
+ for (const selector of navigation.selectors) {
158
+ for (const item of selector.items) {
159
+ push(item.icon, `selector "${item.label}"`);
160
+ }
161
+ }
162
+ for (const link of navigation.featured) {
163
+ push(link.icon, `featured link "${link.label}"`);
164
+ }
165
+ const sidebars = [navigation.sidebar];
166
+ for (const sidebar of sidebars) {
167
+ for (const node of flattenNodes(sidebar)) {
168
+ push(node.icon, `"${node.label}"`);
169
+ }
170
+ }
171
+ return icons;
172
+ };
173
+ var unknownIconDiagnostics = (icons, suggestion) => {
174
+ const seen = new Set;
175
+ const diagnostics = [];
176
+ for (const { icon, where } of icons) {
177
+ if (isAssetIcon(icon) || hasIcon(icon) || seen.has(icon)) {
178
+ continue;
179
+ }
180
+ seen.add(icon);
181
+ const message = icon.trimStart().startsWith("<") ? `Icon markup "${icon}" (${where}) isn't a complete inline <svg> element, so it won't render.` : `Unknown icon "${icon}" (${where}) — it isn't in Blume's icon set.`;
182
+ diagnostics.push({
183
+ code: "BLUME_UNKNOWN_ICON",
184
+ message,
185
+ severity: "warning",
186
+ suggestion
187
+ });
188
+ }
189
+ return diagnostics;
190
+ };
191
+ var validateNavIcons = (navigation) => unknownIconDiagnostics(collectIcons(navigation), ICON_FORMAT_HINT);
192
+ var validateSearchPopularIcons = (popular) => {
193
+ const icons = popular.flatMap((link) => link.icon ? [{ icon: link.icon, where: `popular link "${link.label}"` }] : []);
194
+ return unknownIconDiagnostics(icons, ICON_FORMAT_HINT);
195
+ };
196
+ var resolvesToPages = (routes, path) => routes.has(path) || [...routes].some((route) => route.startsWith(`${path}/`));
197
+ var validateNavTargets = (navigation, routes) => {
198
+ const targets = [
199
+ ...navigation.tabs.map((tab) => ({ label: tab.label, path: tab.path })),
200
+ ...navigation.selectors.flatMap((selector) => selector.items.map((item) => ({ label: item.label, path: item.path }))),
201
+ ...navigation.featured.map((link) => ({
202
+ label: link.label,
203
+ path: link.href
204
+ })),
205
+ ...(navigation.actions ?? []).map((action) => ({
206
+ label: action.label,
207
+ path: action.href
208
+ })),
209
+ ...navigation.cta ? [{ label: navigation.cta.label, path: navigation.cta.href }] : []
210
+ ];
211
+ const diagnostics = [];
212
+ const seen = new Set;
213
+ for (const { label, path } of targets) {
214
+ if (!isInternalPath(path) || path.startsWith("/#") || seen.has(path)) {
215
+ continue;
216
+ }
217
+ if (!resolvesToPages(routes, path.split(/[?#]/u)[0] ?? path)) {
218
+ seen.add(path);
219
+ diagnostics.push({
220
+ code: "BLUME_NAV_MISSING_PAGE",
221
+ message: `Navigation entry "${label}" points to ${path}, but no page matches it.`,
222
+ severity: "warning",
223
+ suggestion: "Fix the path, or add a page at that route."
224
+ });
225
+ }
226
+ }
227
+ return diagnostics;
228
+ };
229
+ var validateSidebarItems = (items, resolves) => {
230
+ const diagnostics = [];
231
+ const walk = (list) => {
232
+ for (const item of list) {
233
+ if (isPageRef(item)) {
234
+ if (!resolves(normalizeRef(item))) {
235
+ diagnostics.push({
236
+ code: "BLUME_NAV_MISSING_PAGE",
237
+ message: `Sidebar entry "${item}" matches no page, so it's left out of the sidebar.`,
238
+ severity: "warning",
239
+ suggestion: "Fix the route, or add a page at it."
240
+ });
241
+ }
242
+ continue;
243
+ }
244
+ if (item.root && !resolves(normalizeRef(item.root))) {
245
+ diagnostics.push({
246
+ code: "BLUME_NAV_MISSING_PAGE",
247
+ message: `Sidebar entry "${item.label}" has root "${item.root}", but no page matches it, so its link leads nowhere.`,
248
+ severity: "warning",
249
+ suggestion: "Fix the root, or add a page at that route."
250
+ });
251
+ }
252
+ if (item.items) {
253
+ walk(item.items);
254
+ } else if (!(item.root || item.href)) {
255
+ diagnostics.push({
256
+ code: "BLUME_NAV_EMPTY_ITEM",
257
+ message: `Sidebar entry "${item.label}" has no page, href, root, or items, so it's left out of the sidebar.`,
258
+ severity: "warning",
259
+ suggestion: "Give it an href or a root to link somewhere, or items to make it a group."
260
+ });
261
+ }
262
+ }
263
+ };
264
+ walk(items);
265
+ return diagnostics;
266
+ };
267
+ var duplicateLabelDiagnostics = (navigation) => {
268
+ const diagnostics = [];
269
+ const checkLevel = (nodes, where) => {
270
+ const counts = new Map;
271
+ for (const node of nodes) {
272
+ counts.set(node.label, (counts.get(node.label) ?? 0) + 1);
273
+ }
274
+ for (const [label, count] of counts) {
275
+ if (count > 1) {
276
+ diagnostics.push({
277
+ code: "BLUME_NAV_DUPLICATE_LABEL",
278
+ message: `Duplicate sidebar label "${label}" appears ${count} times ${where}.`,
279
+ severity: "warning",
280
+ suggestion: "Give the entries distinct titles."
281
+ });
282
+ }
283
+ }
284
+ for (const node of nodes) {
285
+ if (node.kind === "group") {
286
+ checkLevel(node.children, `under "${node.label}"`);
287
+ }
288
+ }
289
+ };
290
+ const sidebars = [
291
+ { nodes: navigation.sidebar, where: "at the top level" }
292
+ ];
293
+ for (const { nodes, where } of sidebars) {
294
+ checkLevel(nodes, where);
295
+ }
296
+ return diagnostics;
297
+ };
298
+ var hiddenInSidebarDiagnostics = (navigation, pages) => {
299
+ const hidden = new Set(pages.flatMap((page) => page.meta.sidebar.hidden ? [page.id] : []));
300
+ if (hidden.size === 0) {
301
+ return [];
302
+ }
303
+ const sidebars = [navigation.sidebar];
304
+ const diagnostics = [];
305
+ const seen = new Set;
306
+ for (const sidebar of sidebars) {
307
+ for (const node of flattenNodes(sidebar)) {
308
+ if (node.kind === "page" && hidden.has(node.pageId) && !seen.has(node.pageId)) {
309
+ seen.add(node.pageId);
310
+ diagnostics.push({
311
+ code: "BLUME_NAV_HIDDEN_IN_SIDEBAR",
312
+ message: `Page "${node.label}" is marked hidden but appears in the sidebar (and its pagination).`,
313
+ severity: "warning",
314
+ suggestion: "Remove it from the navigation config, or unset sidebar.hidden."
315
+ });
316
+ }
317
+ }
318
+ }
319
+ return diagnostics;
320
+ };
321
+ var validateNavStructure = (navigation, pages) => [
322
+ ...duplicateLabelDiagnostics(navigation),
323
+ ...hiddenInSidebarDiagnostics(navigation, pages)
324
+ ];
325
+
96
326
  // src/core/manifest.ts
97
327
  var versionAlternateKey = (versionKey, locale) => `${versionKey}\x00${locale}`;
98
328
  var versionAlternatesFor = (byKey, versionKey, locale) => {
@@ -251,304 +481,69 @@ var LITERAL_MARKERS = new Map([
251
481
  var labelSource = (label) => label.length > 0 ? `[${mdastToString(label, { includeImageAlt: false })}]` : "";
252
482
  var attributeSource = (node) => {
253
483
  const attributes = Object.entries(node.attributes ?? {}).map(([key, value]) => value ? `${key}="${value}"` : key);
254
- return attributes.length > 0 ? `{${attributes.join(" ")}}` : "";
255
- };
256
- var spannedSource = (node, source) => {
257
- const start = node.position?.start?.offset;
258
- const end = node.position?.end?.offset;
259
- return start !== undefined && end !== undefined ? source.slice(start, end) : null;
260
- };
261
- var directiveSource = (node, marker, source) => {
262
- const opening = `${marker}${node.name}`;
263
- const slice = spannedSource(node, source);
264
- if (slice?.startsWith(opening)) {
265
- return slice;
266
- }
267
- return `${opening}${labelSource(node.children ?? [])}${attributeSource(node)}`;
268
- };
269
-
270
- // src/core/directive-diagnostics.ts
271
- var CONTAINER_OPENING = /^[\t >]*:{3,}(?<name>[a-z][\w-]*)/gimu;
272
- var codeList = (names) => [...names].map((name) => `\`${name}\``).join(", ");
273
- var CALLOUT_NAMES = `${codeList(CALLOUT_TYPES)} (or the aliases ${codeList(Object.keys(CALLOUT_ALIASES))})`;
274
- var unknownContainers = (text) => {
275
- const named = [...text.matchAll(CONTAINER_OPENING)].some((match) => calloutTypeFor(match.groups?.name ?? "") === null);
276
- if (!named) {
277
- return [];
278
- }
279
- let tree;
280
- try {
281
- tree = mdxToMdast(text, { features: MDX_BODY_FEATURES });
282
- } catch {
283
- return [];
284
- }
285
- const found = [];
286
- const walk = (node) => {
287
- if (node.type === "containerDirective" && calloutTypeFor(node.name) === null) {
288
- found.push({ line: node.position?.start.line ?? 1, name: node.name });
289
- }
290
- if ("children" in node) {
291
- for (const child of node.children) {
292
- walk(child);
293
- }
294
- }
295
- };
296
- walk(tree);
297
- return found;
298
- };
299
- var unknownDirectiveDiagnostics = (entry, sourceName) => {
300
- if (entry.body.format !== "mdx") {
301
- return [];
302
- }
303
- const page = entry.sourcePath ?? `${sourceName}:${entry.ref}`;
304
- const offset = entry.bodyLineOffset ?? strippedLineOffset(entry.raw, entry.body.text);
305
- return unknownContainers(entry.expanded?.text ?? entry.body.text).map(({ line, name }) => {
306
- const origin = entry.expanded?.origins[line - 1];
307
- return {
308
- code: "BLUME_UNKNOWN_DIRECTIVE",
309
- file: origin?.file ?? page,
310
- line: origin?.line ?? line + offset,
311
- message: `\`:::${name}\` isn't a callout type, so the page shows its \`:::\` lines as written around its content.`,
312
- severity: "warning",
313
- suggestion: `Use a callout type — ${CALLOUT_NAMES} — or remove the \`:::\` lines to keep the content as plain prose.`
314
- };
315
- });
316
- };
317
-
318
- // src/core/localizable.ts
319
- var isSingleLabel = (label) => typeof label === "string";
320
- var resolveLocalizable = (label, locale, defaultLocale) => {
321
- if (isSingleLabel(label)) {
322
- return label;
323
- }
324
- return (locale === undefined ? undefined : label[locale]) ?? (defaultLocale === undefined ? undefined : label[defaultLocale]) ?? Object.values(label)[0] ?? "";
325
- };
326
-
327
- // src/theme/icon-kind.ts
328
- var IMAGE_ICON = /^(?:https?:\/\/|data:image\/|\/|\.{1,2}\/)|\.(?:avif|gif|jpe?g|png|svg|webp)$/iu;
329
- var INLINE_SVG = /^\s*<svg[\s\S]*<\/svg>\s*$/u;
330
- var isImageIcon = (value) => IMAGE_ICON.test(value);
331
- var isInlineSvg = (value) => INLINE_SVG.test(value);
332
- var isAssetIcon = (value) => isInlineSvg(value) || isImageIcon(value);
333
-
334
- // src/theme/icons.ts
335
- import { createRequire } from "node:module";
336
- import { getIconData, iconToSVG } from "@iconify/utils";
337
- var requireJson = createRequire(import.meta.url);
338
- var loadSet = (pkg) => requireJson(pkg);
339
- var SETS = {
340
- lucide: loadSet("@iconify-json/lucide/icons.json")
341
- };
342
- var DEFAULT_SET = "lucide";
343
- var PREFIX_SETS = {
344
- lucide: "lucide"
345
- };
346
- var ownEntry = (map, key) => Object.hasOwn(map, key) ? map[key] : undefined;
347
- var normalize = (name) => name.trim().toLowerCase().replaceAll(/[\s_]+/gu, "-");
348
- var fromSet = (setName, iconName) => {
349
- const set = ownEntry(SETS, setName);
350
- const data = set && getIconData(set, iconName);
351
- if (!data) {
352
- return null;
353
- }
354
- const { attributes, body } = iconToSVG(data, { height: "auto" });
355
- return { body, name: iconName, viewBox: attributes.viewBox };
356
- };
357
- var resolveIcon = (name) => {
358
- const normalized = normalize(name);
359
- const colon = normalized.indexOf(":");
360
- if (colon > 0) {
361
- const setName = ownEntry(PREFIX_SETS, normalized.slice(0, colon));
362
- return setName ? fromSet(setName, normalized.slice(colon + 1)) : null;
363
- }
364
- return fromSet(DEFAULT_SET, normalized);
365
- };
366
- var hasIcon = (name) => resolveIcon(name) !== null;
367
-
368
- // src/core/nav-diagnostics.ts
369
- var ICON_FORMAT_HINT = "Use a built-in icon name, an image path/URL, or inline SVG markup.";
370
- var flattenNodes = (nodes) => nodes.flatMap((node) => node.kind === "group" ? [node, ...flattenNodes(node.children)] : [node]);
371
- var collectIcons = (navigation) => {
372
- const icons = [];
373
- const push = (icon, where) => {
374
- if (icon) {
375
- icons.push({ icon, where });
376
- }
377
- };
378
- for (const tab of navigation.tabs) {
379
- push(tab.icon, `tab "${tab.label}"`);
380
- for (const item of tab.items ?? []) {
381
- push(item.icon, `tab item "${item.label}"`);
382
- }
383
- }
384
- for (const selector of navigation.selectors) {
385
- for (const item of selector.items) {
386
- push(item.icon, `selector "${item.label}"`);
387
- }
388
- }
389
- for (const link of navigation.featured) {
390
- push(link.icon, `featured link "${link.label}"`);
391
- }
392
- const sidebars = [navigation.sidebar];
393
- for (const sidebar of sidebars) {
394
- for (const node of flattenNodes(sidebar)) {
395
- push(node.icon, `"${node.label}"`);
396
- }
397
- }
398
- return icons;
399
- };
400
- var unknownIconDiagnostics = (icons, suggestion) => {
401
- const seen = new Set;
402
- const diagnostics = [];
403
- for (const { icon, where } of icons) {
404
- if (isAssetIcon(icon) || hasIcon(icon) || seen.has(icon)) {
405
- continue;
406
- }
407
- seen.add(icon);
408
- const message = icon.trimStart().startsWith("<") ? `Icon markup "${icon}" (${where}) isn't a complete inline <svg> element, so it won't render.` : `Unknown icon "${icon}" (${where}) — it isn't in Blume's icon set.`;
409
- diagnostics.push({
410
- code: "BLUME_UNKNOWN_ICON",
411
- message,
412
- severity: "warning",
413
- suggestion
414
- });
415
- }
416
- return diagnostics;
484
+ return attributes.length > 0 ? `{${attributes.join(" ")}}` : "";
417
485
  };
418
- var validateNavIcons = (navigation) => unknownIconDiagnostics(collectIcons(navigation), ICON_FORMAT_HINT);
419
- var validateSearchPopularIcons = (popular) => {
420
- const icons = popular.flatMap((link) => link.icon ? [{ icon: link.icon, where: `popular link "${link.label}"` }] : []);
421
- return unknownIconDiagnostics(icons, ICON_FORMAT_HINT);
486
+ var spannedSource = (node, source) => {
487
+ const start = node.position?.start?.offset;
488
+ const end = node.position?.end?.offset;
489
+ return start !== undefined && end !== undefined ? source.slice(start, end) : null;
422
490
  };
423
- var resolvesToPages = (routes, path) => routes.has(path) || [...routes].some((route) => route.startsWith(`${path}/`));
424
- var validateNavTargets = (navigation, routes) => {
425
- const targets = [
426
- ...navigation.tabs.map((tab) => ({ label: tab.label, path: tab.path })),
427
- ...navigation.selectors.flatMap((selector) => selector.items.map((item) => ({ label: item.label, path: item.path }))),
428
- ...navigation.featured.map((link) => ({
429
- label: link.label,
430
- path: link.href
431
- })),
432
- ...(navigation.actions ?? []).map((action) => ({
433
- label: action.label,
434
- path: action.href
435
- })),
436
- ...navigation.cta ? [{ label: navigation.cta.label, path: navigation.cta.href }] : []
437
- ];
438
- const diagnostics = [];
439
- const seen = new Set;
440
- for (const { label, path } of targets) {
441
- if (!isInternalPath(path) || path.startsWith("/#") || seen.has(path)) {
442
- continue;
443
- }
444
- if (!resolvesToPages(routes, path.split(/[?#]/u)[0] ?? path)) {
445
- seen.add(path);
446
- diagnostics.push({
447
- code: "BLUME_NAV_MISSING_PAGE",
448
- message: `Navigation entry "${label}" points to ${path}, but no page matches it.`,
449
- severity: "warning",
450
- suggestion: "Fix the path, or add a page at that route."
451
- });
452
- }
491
+ var directiveSource = (node, marker, source) => {
492
+ const opening = `${marker}${node.name}`;
493
+ const slice = spannedSource(node, source);
494
+ if (slice?.startsWith(opening)) {
495
+ return slice;
453
496
  }
454
- return diagnostics;
455
- };
456
- var validateSidebarItems = (items, resolves) => {
457
- const diagnostics = [];
458
- const walk = (list) => {
459
- for (const item of list) {
460
- if (isPageRef(item)) {
461
- if (!resolves(normalizeRef(item))) {
462
- diagnostics.push({
463
- code: "BLUME_NAV_MISSING_PAGE",
464
- message: `Sidebar entry "${item}" matches no page, so it's left out of the sidebar.`,
465
- severity: "warning",
466
- suggestion: "Fix the route, or add a page at it."
467
- });
468
- }
469
- continue;
470
- }
471
- if (item.root && !resolves(normalizeRef(item.root))) {
472
- diagnostics.push({
473
- code: "BLUME_NAV_MISSING_PAGE",
474
- message: `Sidebar entry "${item.label}" has root "${item.root}", but no page matches it, so its link leads nowhere.`,
475
- severity: "warning",
476
- suggestion: "Fix the root, or add a page at that route."
477
- });
478
- }
479
- if (item.items) {
480
- walk(item.items);
481
- } else if (!(item.root || item.href)) {
482
- diagnostics.push({
483
- code: "BLUME_NAV_EMPTY_ITEM",
484
- message: `Sidebar entry "${item.label}" has no page, href, root, or items, so it's left out of the sidebar.`,
485
- severity: "warning",
486
- suggestion: "Give it an href or a root to link somewhere, or items to make it a group."
487
- });
488
- }
489
- }
490
- };
491
- walk(items);
492
- return diagnostics;
497
+ return `${opening}${labelSource(node.children ?? [])}${attributeSource(node)}`;
493
498
  };
494
- var duplicateLabelDiagnostics = (navigation) => {
495
- const diagnostics = [];
496
- const checkLevel = (nodes, where) => {
497
- const counts = new Map;
498
- for (const node of nodes) {
499
- counts.set(node.label, (counts.get(node.label) ?? 0) + 1);
500
- }
501
- for (const [label, count] of counts) {
502
- if (count > 1) {
503
- diagnostics.push({
504
- code: "BLUME_NAV_DUPLICATE_LABEL",
505
- message: `Duplicate sidebar label "${label}" appears ${count} times ${where}.`,
506
- severity: "warning",
507
- suggestion: "Give the entries distinct titles."
508
- });
509
- }
499
+
500
+ // src/core/directive-diagnostics.ts
501
+ var CONTAINER_OPENING = /^[\t >]*:{3,}(?<name>[a-z][\w-]*)/gimu;
502
+ var codeList = (names) => [...names].map((name) => `\`${name}\``).join(", ");
503
+ var CALLOUT_NAMES = `${codeList(CALLOUT_TYPES)} (or the aliases ${codeList(Object.keys(CALLOUT_ALIASES))})`;
504
+ var unknownContainers = (text) => {
505
+ const named = [...text.matchAll(CONTAINER_OPENING)].some((match) => calloutTypeFor(match.groups?.name ?? "") === null);
506
+ if (!named) {
507
+ return [];
508
+ }
509
+ let tree;
510
+ try {
511
+ tree = mdxToMdast(text, { features: MDX_BODY_FEATURES });
512
+ } catch {
513
+ return [];
514
+ }
515
+ const found = [];
516
+ const walk = (node) => {
517
+ if (node.type === "containerDirective" && calloutTypeFor(node.name) === null) {
518
+ found.push({ line: node.position?.start.line ?? 1, name: node.name });
510
519
  }
511
- for (const node of nodes) {
512
- if (node.kind === "group") {
513
- checkLevel(node.children, `under "${node.label}"`);
520
+ if ("children" in node) {
521
+ for (const child of node.children) {
522
+ walk(child);
514
523
  }
515
524
  }
516
525
  };
517
- const sidebars = [
518
- { nodes: navigation.sidebar, where: "at the top level" }
519
- ];
520
- for (const { nodes, where } of sidebars) {
521
- checkLevel(nodes, where);
522
- }
523
- return diagnostics;
526
+ walk(tree);
527
+ return found;
524
528
  };
525
- var hiddenInSidebarDiagnostics = (navigation, pages) => {
526
- const hidden = new Set(pages.flatMap((page) => page.meta.sidebar.hidden ? [page.id] : []));
527
- if (hidden.size === 0) {
529
+ var unknownDirectiveDiagnostics = (entry, sourceName) => {
530
+ if (entry.body.format !== "mdx") {
528
531
  return [];
529
532
  }
530
- const sidebars = [navigation.sidebar];
531
- const diagnostics = [];
532
- const seen = new Set;
533
- for (const sidebar of sidebars) {
534
- for (const node of flattenNodes(sidebar)) {
535
- if (node.kind === "page" && hidden.has(node.pageId) && !seen.has(node.pageId)) {
536
- seen.add(node.pageId);
537
- diagnostics.push({
538
- code: "BLUME_NAV_HIDDEN_IN_SIDEBAR",
539
- message: `Page "${node.label}" is marked hidden but appears in the sidebar (and its pagination).`,
540
- severity: "warning",
541
- suggestion: "Remove it from the navigation config, or unset sidebar.hidden."
542
- });
543
- }
544
- }
545
- }
546
- return diagnostics;
533
+ const page = entry.sourcePath ?? `${sourceName}:${entry.ref}`;
534
+ const offset = entry.bodyLineOffset ?? strippedLineOffset(entry.raw, entry.body.text);
535
+ return unknownContainers(entry.expanded?.text ?? entry.body.text).map(({ line, name }) => {
536
+ const origin = entry.expanded?.origins[line - 1];
537
+ return {
538
+ code: "BLUME_UNKNOWN_DIRECTIVE",
539
+ file: origin?.file ?? page,
540
+ line: origin?.line ?? line + offset,
541
+ message: `\`:::${name}\` isn't a callout type, so the page shows its \`:::\` lines as written around its content.`,
542
+ severity: "warning",
543
+ suggestion: `Use a callout type — ${CALLOUT_NAMES} — or remove the \`:::\` lines to keep the content as plain prose.`
544
+ };
545
+ });
547
546
  };
548
- var validateNavStructure = (navigation, pages) => [
549
- ...duplicateLabelDiagnostics(navigation),
550
- ...hiddenInSidebarDiagnostics(navigation, pages)
551
- ];
552
547
 
553
548
  // src/core/graph.ts
554
549
  var collectRoutes = (pages) => {
@@ -1016,7 +1011,9 @@ var assetFromEntry = (asset) => {
1016
1011
  return null;
1017
1012
  }
1018
1013
  return {
1014
+ contentType: asString(getPath(asset, "fields.file.contentType")),
1019
1015
  description: asString(getPath(asset, "fields.description")),
1016
+ fileName: asString(getPath(asset, "fields.file.fileName")),
1020
1017
  title: asString(getPath(asset, "fields.title")),
1021
1018
  url: absoluteUrl(url)
1022
1019
  };
@@ -1084,6 +1081,11 @@ var inlineNodeParts = (node, options) => {
1084
1081
  case "asset-hyperlink": {
1085
1082
  return linkParts(inlineParts(children(node), options), linkedAsset(node, options)?.url);
1086
1083
  }
1084
+ case "entry-hyperlink": {
1085
+ const entry = linkedTarget(node, options.resolveEntry);
1086
+ const href = entry ? options.entryHref?.(entry) : undefined;
1087
+ return linkParts(inlineParts(children(node), options), href);
1088
+ }
1087
1089
  case "embedded-entry-inline": {
1088
1090
  return [{ markdown: embeddedEntry(node, options) }];
1089
1091
  }
@@ -1135,7 +1137,14 @@ var renderBlock = (node, options) => {
1135
1137
  }
1136
1138
  case "embedded-asset-block": {
1137
1139
  const asset = linkedAsset(node, options);
1138
- return asset ? image(asset.description ?? asset.title ?? "", asset.url) : "";
1140
+ if (!asset) {
1141
+ return "";
1142
+ }
1143
+ if (asset.contentType && !asset.contentType.startsWith("image/")) {
1144
+ const label = asset.title ?? asset.fileName ?? asset.url;
1145
+ return renderInline(linkParts([{ marks: {}, text: label }], asset.url));
1146
+ }
1147
+ return image(asset.description ?? asset.title ?? "", asset.url);
1139
1148
  }
1140
1149
  case "embedded-entry-block": {
1141
1150
  return embeddedEntry(node, options);
@@ -1157,9 +1166,17 @@ var remoteSource = (options, ctx) => {
1157
1166
  let snapshot = new Map;
1158
1167
  const load = async (refresh = ctx?.refresh ?? true) => {
1159
1168
  options.assertConfigured?.();
1160
- const result = await loadWithCache(options.name, cache, options.fetchEntries, refresh);
1169
+ let warnings = [];
1170
+ const result = await loadWithCache(options.name, cache, async () => {
1171
+ const found = [];
1172
+ const entries = await options.fetchEntries((diagnostic) => {
1173
+ found.push(diagnostic);
1174
+ });
1175
+ warnings = found;
1176
+ return entries;
1177
+ }, refresh);
1161
1178
  snapshot = new Map(result.entries.map((entry) => [entry.ref, entry]));
1162
- return result;
1179
+ return { ...result, diagnostics: [...result.diagnostics, ...warnings] };
1163
1180
  };
1164
1181
  const read = async (ref) => {
1165
1182
  const cached = snapshot.get(ref);
@@ -1190,6 +1207,12 @@ var stagedEntry = (slug, data, markdown, lastModified, format = "md") => {
1190
1207
  ref: `${slug}.${format}`
1191
1208
  };
1192
1209
  };
1210
+ var missingSecretError = (name, env) => new BlumeError({
1211
+ code: "BLUME_MISSING_SECRET",
1212
+ message: `Source "${name}" needs ${env}, which is not set.`,
1213
+ severity: "error",
1214
+ suggestion: `Set ${env} in .env.local for local dev, or in your host's environment for production.`
1215
+ });
1193
1216
  var REMOTE_TIMEOUT_MS = 30000;
1194
1217
  var fetchJson = async (url, client) => {
1195
1218
  const doFetch = client.fetchImpl ?? globalThis.fetch;
@@ -1211,9 +1234,9 @@ var queryString = (params) => {
1211
1234
  }
1212
1235
  return search.toString();
1213
1236
  };
1237
+ var documentSlug = (doc, slugField, id) => slugifyPath(asString(getPath(doc, slugField)) ?? id) || slugify(id) || "untitled";
1214
1238
  var documentEntry = (doc, fields, id, lower, draft = false, lowersToMdx = false) => {
1215
- const slugValue = asString(getPath(doc, fields.slug)) ?? id;
1216
- const slug = slugifyPath(slugValue) || slugify(id) || "untitled";
1239
+ const slug = documentSlug(doc, fields.slug, id);
1217
1240
  const data = {};
1218
1241
  const title = asString(getPath(doc, fields.title));
1219
1242
  const description = asString(getPath(doc, fields.description));
@@ -1226,6 +1249,10 @@ var documentEntry = (doc, fields, id, lower, draft = false, lowersToMdx = false)
1226
1249
  if (draft) {
1227
1250
  data.draft = true;
1228
1251
  }
1252
+ const order = fields.order ? asNumber(getPath(doc, fields.order)) : undefined;
1253
+ if (order !== undefined) {
1254
+ data.sidebar = { order };
1255
+ }
1229
1256
  const body = getPath(doc, fields.body);
1230
1257
  let markdown = "";
1231
1258
  let format = "md";
@@ -1240,8 +1267,8 @@ var documentEntry = (doc, fields, id, lower, draft = false, lowersToMdx = false)
1240
1267
  };
1241
1268
 
1242
1269
  // src/core/sources/contentful.ts
1243
- var DELIVERY_HOST = "https://cdn.contentful.com";
1244
- var PREVIEW_HOST = "https://preview.contentful.com";
1270
+ var DELIVERY_HOST = "cdn.contentful.com";
1271
+ var PREVIEW_HOST = "preview.contentful.com";
1245
1272
  var PAGE_SIZE = 100;
1246
1273
  var DEFAULT_FIELDS = {
1247
1274
  body: "body",
@@ -1251,20 +1278,50 @@ var DEFAULT_FIELDS = {
1251
1278
  title: "title"
1252
1279
  };
1253
1280
  var byId = (objects) => new Map(objects.map((object) => [asString(getPath(object, "sys.id")) ?? "", object]));
1281
+ var entryView = (item) => {
1282
+ const sys = asObject(item.sys) ?? {};
1283
+ return {
1284
+ id: asString(sys.id) ?? "",
1285
+ view: { ...asObject(item.fields), sys }
1286
+ };
1287
+ };
1254
1288
  var contentfulSource = (options, ctx) => {
1255
1289
  const fields = { ...DEFAULT_FIELDS, ...options.fields };
1256
- const toEntry = (item, assets, linked) => {
1257
- const sys = asObject(item.sys) ?? {};
1258
- const id = asString(sys.id) ?? "";
1259
- const view = { ...asObject(item.fields), sys };
1260
- return documentEntry(view, fields, id, (body) => isJsonObject(body) ? contentfulRichTextToMarkdown(body, {
1290
+ const routeOf = (view, id) => {
1291
+ const route = resolveEntryRoute({ ref: `${documentSlug(view, fields.slug, id)}.md` }, ".md", undefined, { i18n: options.i18n, prefix: options.prefix, versions: options.versions });
1292
+ return localizedRoute(route.logicalRoute, route.locales[0] ?? "", options.i18n);
1293
+ };
1294
+ const toEntry = (item, { assets, linked }, routes, warn) => {
1295
+ const { id, view } = entryView(item);
1296
+ const missing = new Set;
1297
+ const entry = documentEntry(view, fields, id, (body) => isJsonObject(body) ? contentfulRichTextToMarkdown(body, {
1298
+ entryHref: (target) => routes.get(asString(getPath(target, "sys.id")) ?? ""),
1261
1299
  resolveAsset: (assetId) => {
1262
1300
  const asset = assets.get(assetId);
1263
- return asset ? assetFromEntry(asset) : null;
1301
+ if (!asset) {
1302
+ missing.add(`asset "${assetId}"`);
1303
+ return null;
1304
+ }
1305
+ return assetFromEntry(asset);
1306
+ },
1307
+ resolveEntry: (entryId) => {
1308
+ const target = linked.get(entryId);
1309
+ if (!target) {
1310
+ missing.add(`entry "${entryId}"`);
1311
+ }
1312
+ return target ?? null;
1264
1313
  },
1265
- resolveEntry: (entryId) => linked.get(entryId) ?? null,
1266
1314
  serializers: options.serializers
1267
1315
  }) : "", false, writesMdx(options.serializers));
1316
+ for (const link of missing) {
1317
+ warn({
1318
+ code: "BLUME_SOURCE_UNRESOLVED_LINK",
1319
+ message: `Source "${options.name}": "${entry.ref}" links to Contentful ${link}, which the response didn't include, so the page renders without it.`,
1320
+ severity: "warning",
1321
+ suggestion: "Publish the linked asset or entry, or remove the link: the Delivery API returns only published content."
1322
+ });
1323
+ }
1324
+ return entry;
1268
1325
  };
1269
1326
  const preview = ctx?.preview ?? false;
1270
1327
  const previewToken = () => options.previewToken ?? process.env.CONTENTFUL_PREVIEW_TOKEN;
@@ -1278,15 +1335,18 @@ var contentfulSource = (options, ctx) => {
1278
1335
  });
1279
1336
  }
1280
1337
  };
1281
- const fetchEntries = async () => {
1338
+ const fetchEntries = async (warn) => {
1282
1339
  const token = preview ? previewToken() : options.token ?? process.env.CONTENTFUL_ACCESS_TOKEN;
1340
+ if (!token) {
1341
+ throw missingSecretError(options.name, "CONTENTFUL_ACCESS_TOKEN");
1342
+ }
1283
1343
  const client = {
1284
1344
  fetchImpl: options.fetchImpl,
1285
- headers: token ? { authorization: `Bearer ${token}` } : {}
1345
+ headers: { authorization: `Bearer ${token}` }
1286
1346
  };
1287
- const host = preview ? PREVIEW_HOST : DELIVERY_HOST;
1288
- const base = `${host}/spaces/${options.space}/environments/${options.environment ?? "master"}/entries`;
1289
- const entries = [];
1347
+ const host = preview ? options.previewHost ?? PREVIEW_HOST : options.host ?? DELIVERY_HOST;
1348
+ const base = `https://${host}/spaces/${options.space}/environments/${options.environment ?? "master"}/entries`;
1349
+ const pages = [];
1290
1350
  let skip = 0;
1291
1351
  let more = true;
1292
1352
  while (more) {
@@ -1302,16 +1362,20 @@ var contentfulSource = (options, ctx) => {
1302
1362
  if (!page) {
1303
1363
  throw new Error("Contentful returned a non-object response");
1304
1364
  }
1305
- const assets = byId(objectsIn(getPath(page, "includes.Asset")));
1306
- const linked = byId(objectsIn(getPath(page, "includes.Entry")));
1307
1365
  const items = objectsIn(page.items);
1308
- for (const item of items) {
1309
- entries.push(toEntry(item, assets, linked));
1310
- }
1366
+ pages.push({
1367
+ assets: byId(objectsIn(getPath(page, "includes.Asset"))),
1368
+ items,
1369
+ linked: byId([...objectsIn(getPath(page, "includes.Entry")), ...items])
1370
+ });
1311
1371
  skip += items.length;
1312
1372
  more = items.length > 0 && skip < (asNumber(page.total) ?? 0);
1313
1373
  }
1314
- return entries;
1374
+ const routes = new Map(pages.flatMap(({ items }) => items.map((item) => {
1375
+ const { id, view } = entryView(item);
1376
+ return [id, routeOf(view, id)];
1377
+ })));
1378
+ return pages.flatMap((page) => page.items.map((item) => toEntry(item, page, routes, warn)));
1315
1379
  };
1316
1380
  return remoteSource({
1317
1381
  assertConfigured,
@@ -1326,6 +1390,7 @@ var contentfulSource = (options, ctx) => {
1326
1390
  // src/core/sources/filesystem.ts
1327
1391
  import { existsSync, watch as fsWatch } from "node:fs";
1328
1392
  import { readFile } from "node:fs/promises";
1393
+ import { YAMLException } from "js-yaml";
1329
1394
  import { extname, isAbsolute, join as join2, relative as relative3, resolve } from "pathe";
1330
1395
  import { glob as glob2 } from "tinyglobby";
1331
1396
  var filesystemSource = (options) => {
@@ -1348,11 +1413,19 @@ var filesystemSource = (options) => {
1348
1413
  loadable.push(file);
1349
1414
  }
1350
1415
  }
1351
- const entries = await Promise.all(loadable.map(async (file) => {
1416
+ const read = await Promise.all(loadable.map(async (file) => {
1352
1417
  const source = await readFile(file, "utf-8");
1353
1418
  const ext = extname(file).toLowerCase();
1354
1419
  const format = ext === ".mdx" ? "mdx" : "md";
1355
- const parsed = frontmatter_default(source);
1420
+ let parsed;
1421
+ try {
1422
+ parsed = frontmatter_default(source);
1423
+ } catch (error) {
1424
+ if (error instanceof YAMLException) {
1425
+ return frontmatterYamlDiagnostic(error, file);
1426
+ }
1427
+ throw error;
1428
+ }
1356
1429
  return {
1357
1430
  body: { format, text: parsed.content },
1358
1431
  data: parsed.data,
@@ -1361,6 +1434,14 @@ var filesystemSource = (options) => {
1361
1434
  sourcePath: file
1362
1435
  };
1363
1436
  }));
1437
+ const entries = [];
1438
+ for (const item of read) {
1439
+ if ("body" in item) {
1440
+ entries.push(item);
1441
+ } else {
1442
+ diagnostics.push(item);
1443
+ }
1444
+ }
1364
1445
  return { diagnostics, entries };
1365
1446
  };
1366
1447
  const validate = () => {
@@ -1526,11 +1607,17 @@ var githubHeaders = () => {
1526
1607
  }
1527
1608
  return headers;
1528
1609
  };
1610
+ var releaseCategory = (release) => {
1611
+ if (release.draft) {
1612
+ return "Draft";
1613
+ }
1614
+ return release.prerelease ? "Prerelease" : "Release";
1615
+ };
1529
1616
  var releaseToEntry = (release, site) => {
1530
1617
  const version = release.tag_name.replace(LEADING_V, "");
1531
1618
  const title = release.name?.trim() || release.tag_name;
1532
1619
  const date = release.published_at ?? release.created_at;
1533
- const category = release.prerelease ? "Prerelease" : "Release";
1620
+ const category = releaseCategory(release);
1534
1621
  const body = liftHeadings(relativizeOwnLinks(neutralizeUnsafeLinks((release.body ?? "").replaceAll(`\r
1535
1622
  `, `
1536
1623
  `)).trim(), site));
@@ -1641,6 +1728,11 @@ var githubReleasesSource = (options, ctx) => {
1641
1728
 
1642
1729
  // src/core/sources/mdx-remote.ts
1643
1730
  import picomatch from "picomatch";
1731
+ var includeMatcher = (include) => {
1732
+ const included = picomatch(include.filter((pattern) => !pattern.startsWith("!")));
1733
+ const excluded = picomatch(include.filter((pattern) => pattern.startsWith("!")).map((pattern) => pattern.slice(1)));
1734
+ return (ref) => included(ref) && !excluded(ref);
1735
+ };
1644
1736
  var GITHUB_HOSTS = new Set(["api.github.com", "raw.githubusercontent.com"]);
1645
1737
  var githubHeaders2 = (url) => {
1646
1738
  const token = process.env.GITHUB_TOKEN;
@@ -1679,7 +1771,7 @@ var enumerateGithub = async (github, include, doFetch) => {
1679
1771
  const text = await fetchText(treeUrl, doFetch);
1680
1772
  const body = JSON.parse(text);
1681
1773
  const prefix = base ? `${base}/` : "";
1682
- const included = picomatch(include);
1774
+ const included = includeMatcher(include);
1683
1775
  const refs = (body.tree ?? []).flatMap((node) => {
1684
1776
  if (!(node.type === "blob" && node.path.startsWith(prefix))) {
1685
1777
  return [];
@@ -1696,7 +1788,13 @@ var enumerateGithub = async (github, include, doFetch) => {
1696
1788
  }
1697
1789
  ];
1698
1790
  });
1699
- return { refs, truncated: body.truncated === true };
1791
+ const truncated = body.truncated === true;
1792
+ const found = prefix === "" || truncated || (body.tree ?? []).some((node) => node.path.startsWith(prefix));
1793
+ return {
1794
+ missing: found ? undefined : `"${base}" in ${owner}/${repo} at ${ref}`,
1795
+ refs,
1796
+ truncated
1797
+ };
1700
1798
  };
1701
1799
  var mdxRemoteSource = (options, ctx) => {
1702
1800
  const doFetch = options.fetchImpl ?? globalThis.fetch;
@@ -1717,7 +1815,7 @@ var mdxRemoteSource = (options, ctx) => {
1717
1815
  return await enumerateGithub(options.github, options.include, doFetch);
1718
1816
  }
1719
1817
  const base = (options.url ?? "").replace(/\/$/u, "");
1720
- const included = picomatch(options.include);
1818
+ const included = includeMatcher(options.include);
1721
1819
  const refs = (options.files ?? []).flatMap((ref) => included(ref) ? [{ editUrl: `${base}/${ref}`, fetchUrl: `${base}/${ref}`, ref }] : []);
1722
1820
  return { refs, truncated: false };
1723
1821
  };
@@ -1738,12 +1836,21 @@ var mdxRemoteSource = (options, ctx) => {
1738
1836
  assertConfigured();
1739
1837
  const skipped = [];
1740
1838
  const result = await loadWithCache(options.name, cache, async () => {
1741
- const { refs, truncated } = await enumerate();
1839
+ const { missing, refs, truncated } = await enumerate();
1840
+ if (missing) {
1841
+ skipped.push({
1842
+ code: "BLUME_SOURCE_PATH_MISSING",
1843
+ message: `Source "${options.name}" read no files: there is no folder ${missing}.`,
1844
+ severity: "warning",
1845
+ suggestion: "Check github.path and github.ref against the repository."
1846
+ });
1847
+ }
1742
1848
  if (truncated) {
1743
1849
  skipped.push({
1744
1850
  code: "BLUME_SOURCE_TRUNCATED",
1745
- message: `Source "${options.name}" hit GitHub's tree listing limit; some files were not enumerated. Narrow the source path or split the repo.`,
1746
- severity: "warning"
1851
+ message: `Source "${options.name}" hit GitHub's tree listing limit for the whole repository; some files were not enumerated.`,
1852
+ severity: "warning",
1853
+ suggestion: "List the files to read with { url, files } instead, or split the repository."
1747
1854
  });
1748
1855
  }
1749
1856
  const reasons = [];
@@ -2115,7 +2222,11 @@ var notionSource = (options, ctx) => {
2115
2222
  severity: "error"
2116
2223
  });
2117
2224
  }
2118
- return new Client({ auth: options.token ?? process.env.NOTION_TOKEN });
2225
+ const auth = options.token ?? process.env.NOTION_TOKEN;
2226
+ if (!auth) {
2227
+ throw missingSecretError(options.name, "NOTION_TOKEN");
2228
+ }
2229
+ return new Client({ auth });
2119
2230
  };
2120
2231
  const childrenOf = (client, blockId) => collectAll((cursor) => notionCall(() => client.blocks.children.list({ block_id: blockId, start_cursor: cursor })));
2121
2232
  const renderContainer = async (client, block, render) => {
@@ -2311,6 +2422,7 @@ ${nested}`;
2311
2422
  // src/core/sources/obsidian.ts
2312
2423
  import { existsSync as existsSync2, watch as fsWatch2, statSync } from "node:fs";
2313
2424
  import { readdir as readdir2, readFile as readFile2, stat } from "node:fs/promises";
2425
+ import { YAMLException as YAMLException2 } from "js-yaml";
2314
2426
  import { basename as basename2, isAbsolute as isAbsolute2, join as join5, relative as relative4, resolve as resolve2 } from "pathe";
2315
2427
  var MARKDOWN_FILE = /\.md$/iu;
2316
2428
  var OBSIDIAN_COMMENT = /%%.*?%%/gu;
@@ -2643,6 +2755,9 @@ var readNote = async (vaultDir, rel, options) => {
2643
2755
  rel
2644
2756
  };
2645
2757
  } catch (error) {
2758
+ if (error instanceof YAMLException2) {
2759
+ return frontmatterYamlDiagnostic(error, absPath);
2760
+ }
2646
2761
  if (error.code === "ENOENT") {
2647
2762
  return null;
2648
2763
  }
@@ -2733,7 +2848,8 @@ var obsidianSource = (options, ctx) => {
2733
2848
  }
2734
2849
  }
2735
2850
  const read = await Promise.all(files.map((rel) => readNote(vaultDir, rel, options)));
2736
- const notes = read.filter((note) => note !== null);
2851
+ const notes = read.filter((note) => note !== null && ("rel" in note));
2852
+ skipped.push(...read.filter((note) => note !== null && ("code" in note)));
2737
2853
  const { index, pairs } = buildLinkIndex(notes, options);
2738
2854
  const unresolved = noTargets();
2739
2855
  const entries = pairs.map((pair) => noteToEntry(pair, index, options, unresolved));
@@ -2809,6 +2925,10 @@ var marksOf2 = (node) => {
2809
2925
  };
2810
2926
  };
2811
2927
  var children2 = (node) => objectsIn(node.children);
2928
+ var leftOut = (what, options) => {
2929
+ options.onUnsupported?.(what);
2930
+ return unsupported(what, writesMdx(options.serializers));
2931
+ };
2812
2932
  var typeOf = (node) => asString(node.type) ?? "";
2813
2933
  var customBlock = (node, options) => {
2814
2934
  const fields = asObject(node.fields);
@@ -2817,7 +2937,7 @@ var customBlock = (node, options) => {
2817
2937
  if (fields && serializer) {
2818
2938
  return serializer(fields);
2819
2939
  }
2820
- return unsupported(`Lexical block${blockType ? `: ${blockType}` : ""}`, writesMdx(options.serializers));
2940
+ return leftOut(`Lexical block${blockType ? `: ${blockType}` : ""}`, options);
2821
2941
  };
2822
2942
  var inlineParts2 = (nodes, options) => nodes.flatMap((node) => inlineNodeParts2(node, options));
2823
2943
  var inlineNodeParts2 = (node, options) => {
@@ -2850,7 +2970,7 @@ var renderUpload = (node, options) => {
2850
2970
  const value = asObject(node.value);
2851
2971
  const url = value ? asString(value.url) : undefined;
2852
2972
  if (!(value && url)) {
2853
- return unsupported("Lexical upload (fetch with depth 1 to populate it)", writesMdx(options.serializers));
2973
+ return leftOut("Lexical upload (fetch with depth 1 to populate it)", options);
2854
2974
  }
2855
2975
  const alt = asString(value.alt) ?? asString(value.filename) ?? "";
2856
2976
  return image(alt, absoluteUrl(url, options.baseUrl));
@@ -2909,7 +3029,7 @@ var renderBlock2 = (node, options) => {
2909
3029
  return customBlock(node, options);
2910
3030
  }
2911
3031
  default: {
2912
- return unsupported(`Lexical node: ${typeOf(node)}`, writesMdx(options.serializers));
3032
+ return leftOut(`Lexical node: ${typeOf(node)}`, options);
2913
3033
  }
2914
3034
  }
2915
3035
  };
@@ -2930,11 +3050,26 @@ var DEFAULT_FIELDS2 = {
2930
3050
  var payloadSource = (options, ctx) => {
2931
3051
  const fields = { ...DEFAULT_FIELDS2, ...options.fields };
2932
3052
  const origin = options.url.replace(/\/+$/u, "");
2933
- const lower = (body) => lexicalToMarkdown(body, {
3053
+ const lower = (body, leftOut) => lexicalToMarkdown(body, {
2934
3054
  baseUrl: origin,
3055
+ onUnsupported: (what) => leftOut.add(what),
2935
3056
  serializers: options.serializers
2936
3057
  });
2937
- const fetchEntries = async () => {
3058
+ const toEntry = (doc, draft, warn) => {
3059
+ const leftOut = new Set;
3060
+ const id = asString(doc.id) ?? String(doc.id ?? "");
3061
+ const entry = documentEntry(doc, fields, id, (body) => isJsonObject(body) ? lower(body, leftOut) : "", draft, writesMdx(options.serializers));
3062
+ for (const what of leftOut) {
3063
+ warn({
3064
+ code: "BLUME_SOURCE_UNSUPPORTED_NODE",
3065
+ message: `Source "${options.name}": "${entry.ref}" has content Blume has no Markdown for (${what}), left out as a comment.`,
3066
+ severity: "warning",
3067
+ suggestion: "Map a block type to a component with a serializer on payloadSource, passed to custom(); other nodes have no Markdown equivalent."
3068
+ });
3069
+ }
3070
+ return entry;
3071
+ };
3072
+ const fetchEntries = async (warn) => {
2938
3073
  const preview = ctx?.preview ?? false;
2939
3074
  const token = options.token ?? process.env.PAYLOAD_API_KEY;
2940
3075
  const client = {
@@ -2964,8 +3099,7 @@ var payloadSource = (options, ctx) => {
2964
3099
  if (draft && !preview) {
2965
3100
  continue;
2966
3101
  }
2967
- const id = asString(doc.id) ?? String(doc.id ?? "");
2968
- entries.push(documentEntry(doc, fields, id, (body) => isJsonObject(body) ? lower(body) : "", draft, writesMdx(options.serializers)));
3102
+ entries.push(toEntry(doc, draft, warn));
2969
3103
  }
2970
3104
  more = result.hasNextPage === true;
2971
3105
  page += 1;
@@ -3126,15 +3260,22 @@ var sanitySource = (options, ctx) => {
3126
3260
  if (description) {
3127
3261
  data.description = description;
3128
3262
  }
3129
- const blocks = getPath(doc, fields.body ?? "body") ?? [];
3130
- const markdown = Array.isArray(blocks) ? portableTextToMarkdown(blocks, {
3131
- imageUrl: (block) => {
3132
- const ref = block.asset?._ref;
3133
- return ref ? imageUrlFromRef(ref, options.projectId, options.dataset) : null;
3134
- },
3135
- serializers: options.serializers
3136
- }) : "";
3137
- const format = writesMdx(options.serializers) ? "mdx" : "md";
3263
+ const body = getPath(doc, fields.body ?? "body");
3264
+ let markdown = "";
3265
+ let format = writesMdx(options.serializers) ? "mdx" : "md";
3266
+ if (isStringValue2(body)) {
3267
+ markdown = neutralizeUnsafeLinks(`${body.trimEnd()}
3268
+ `);
3269
+ format = "md";
3270
+ } else if (Array.isArray(body)) {
3271
+ markdown = portableTextToMarkdown(body, {
3272
+ imageUrl: (block) => {
3273
+ const ref = block.asset?._ref;
3274
+ return ref ? imageUrlFromRef(ref, options.projectId, options.dataset) : null;
3275
+ },
3276
+ serializers: options.serializers
3277
+ });
3278
+ }
3138
3279
  const raw = frontmatter_default.stringify(markdown, data);
3139
3280
  return {
3140
3281
  body: { format, text: markdown },
@@ -3146,13 +3287,22 @@ var sanitySource = (options, ctx) => {
3146
3287
  };
3147
3288
  };
3148
3289
  const load = async (refresh = ctx?.refresh ?? true) => {
3290
+ const empty = [];
3149
3291
  const result = await loadWithCache(options.name, cache, async () => {
3150
3292
  const client = resolveClient(options, ctx?.preview ?? false);
3151
3293
  const docs = await client.fetch(options.query);
3294
+ if (docs.length === 0 && !(options.token ?? process.env.SANITY_TOKEN)) {
3295
+ empty.push({
3296
+ code: "BLUME_MISSING_SECRET",
3297
+ message: `Source "${options.name}" found no documents, and SANITY_TOKEN is not set. A private dataset returns nothing to a query without a token.`,
3298
+ severity: "warning",
3299
+ suggestion: "Set SANITY_TOKEN to a token with read access if the dataset is private."
3300
+ });
3301
+ }
3152
3302
  return docs.map(toEntry);
3153
3303
  }, refresh);
3154
3304
  snapshot = new Map(result.entries.map((entry) => [entry.ref, entry]));
3155
- return result;
3305
+ return { ...result, diagnostics: [...result.diagnostics, ...empty] };
3156
3306
  };
3157
3307
  const read = async (ref) => {
3158
3308
  const cached = snapshot.get(ref);
@@ -3389,7 +3539,12 @@ var buildSource = (adapter, name, config, context, runtime) => {
3389
3539
  return notionSource({ ...adapter.options, name }, ctx());
3390
3540
  }
3391
3541
  case "contentful": {
3392
- return contentfulSource({ ...adapter.options, name }, ctx());
3542
+ return contentfulSource({
3543
+ ...adapter.options,
3544
+ i18n: config.i18n,
3545
+ name,
3546
+ versions: config.versions
3547
+ }, ctx());
3393
3548
  }
3394
3549
  case "payload": {
3395
3550
  return payloadSource({ ...adapter.options, name }, ctx());
@@ -3543,7 +3698,20 @@ var substituteEntryVariables = (entry, variables) => {
3543
3698
  };
3544
3699
  var substituteLoadedVariables = (loaded, variables) => hasVariables(variables) ? loaded.flatMap(({ entries }) => entries.flatMap((entry) => substituteEntryVariables(entry, variables))) : [];
3545
3700
  var redirectPageDiagnostics = (config, pages, configFile) => config.redirects.flatMap((redirect) => {
3546
- const matched = pathsUnderPattern(withBasePath(config.basePath, redirect.from), pages);
3701
+ const from = withBasePath(config.basePath, redirect.from);
3702
+ if (!isPatternPath(redirect.from)) {
3703
+ const page = normalizePath(from);
3704
+ return pages.includes(page) ? [
3705
+ {
3706
+ code: "BLUME_REDIRECT_MATCHES_PAGE",
3707
+ file: configFile ?? undefined,
3708
+ message: `The redirect from ${redirect.from} is also the page ${page}, which never publishes: its URL redirects to ${redirect.to} instead.`,
3709
+ severity: "warning",
3710
+ suggestion: "If the page moved, delete it or give it a new slug; otherwise remove the redirect."
3711
+ }
3712
+ ] : [];
3713
+ }
3714
+ const matched = pathsUnderPattern(from, pages);
3547
3715
  const [first] = matched;
3548
3716
  if (first === undefined) {
3549
3717
  return [];
@@ -3560,6 +3728,14 @@ var redirectPageDiagnostics = (config, pages, configFile) => config.redirects.fl
3560
3728
  ];
3561
3729
  });
3562
3730
  var bannerLinkHref = (banner) => banner === undefined || isBannerShorthand(banner) ? undefined : banner.link?.href;
3731
+ var validatedSources = (config, context, runtime, beforeSources) => {
3732
+ beforeSources?.(config);
3733
+ const sources = resolveSources(config, context, runtime);
3734
+ for (const source of sources) {
3735
+ source.validate?.();
3736
+ }
3737
+ return sources;
3738
+ };
3563
3739
  var scanProject = async (root, options = {}) => {
3564
3740
  const mode = options.mode ?? "dev";
3565
3741
  const preview = options.preview ?? false;
@@ -3570,14 +3746,7 @@ var scanProject = async (root, options = {}) => {
3570
3746
  const context = resolveProjectContext(root, config, {
3571
3747
  runtimeDir: options.runtimeDir
3572
3748
  });
3573
- const sources = resolveSources(config, context, {
3574
- mode,
3575
- preview,
3576
- refresh: options.refresh
3577
- });
3578
- for (const source of sources) {
3579
- source.validate?.();
3580
- }
3749
+ const sources = validatedSources(config, context, { mode, preview, refresh: options.refresh }, options.beforeSources);
3581
3750
  const metaSources = sources.flatMap((source) => source.staged || !source.contentRoot ? [] : [
3582
3751
  {
3583
3752
  exclude: source.exclude,
@@ -3681,7 +3850,7 @@ var scanProject = async (root, options = {}) => {
3681
3850
  };
3682
3851
  };
3683
3852
 
3684
- export { directiveSource, resolveLocalizable, validateSearchPopularIcons, validateNavTargets, contentIndexable, buildManifest, findFolderMetaFiles, isWithin, scanProject };
3853
+ export { directiveSource, validateSearchPopularIcons, validateNavTargets, contentIndexable, buildManifest, findFolderMetaFiles, isWithin, scanProject };
3685
3854
 
3686
- //# debugId=89E5C13A316C75D864756E2164756E21
3687
- //# sourceMappingURL=chunk-43zcf4gz.js.map
3855
+ //# debugId=1F41F69354D6E33464756E2164756E21
3856
+ //# sourceMappingURL=chunk-ncj7w596.js.map