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
@@ -1,6 +1,7 @@
1
1
  import { existsSync, readFileSync } from "node:fs";
2
2
 
3
3
  import GithubSlugger from "github-slugger";
4
+ import type { YAMLException } from "js-yaml";
4
5
  import type { Nodes } from "mdast";
5
6
  import { extname } from "pathe";
6
7
  import { markdownToMdast } from "satteri";
@@ -22,7 +23,13 @@ import type {
22
23
  ResolvedI18nConfig,
23
24
  } from "../schema.ts";
24
25
  import { trimChar } from "../trim.ts";
25
- import type { Diagnostic, Heading, PageLink, PageRecord } from "../types.ts";
26
+ import type {
27
+ Diagnostic,
28
+ ExampleUse,
29
+ Heading,
30
+ PageLink,
31
+ PageRecord,
32
+ } from "../types.ts";
26
33
  import { detectVersionRef, versionizeRoute } from "../versions.ts";
27
34
  import type { NormalizeContext, SourceEntry } from "./types.ts";
28
35
 
@@ -127,6 +134,29 @@ export const unloadablePathDiagnostic = (
127
134
  }
128
135
  : undefined;
129
136
 
137
+ /**
138
+ * The error for a content file whose front matter isn't valid YAML — most
139
+ * often an unquoted value holding `: `, which YAML reads as a nested mapping.
140
+ * Thrown out of a source's scan, it would fail every command with
141
+ * BLUME_INTERNAL; a source leaves the file out and reports this instead, like
142
+ * a page whose front matter fails the schema. js-yaml counts lines from the
143
+ * text after the opening `---`, which starts with the rest of that fence
144
+ * line, so its 0-based line is already the file's line minus one.
145
+ */
146
+ export const frontmatterYamlDiagnostic = (
147
+ error: YAMLException,
148
+ file: string
149
+ ): Diagnostic => ({
150
+ code: "BLUME_FRONTMATTER_INVALID",
151
+ column: error.mark ? error.mark.column + 1 : undefined,
152
+ file,
153
+ line: error.mark ? error.mark.line + 1 : undefined,
154
+ message: `Front matter isn't valid YAML (${error.reason}), so the page was left out of the site.`,
155
+ severity: "error",
156
+ suggestion:
157
+ 'Wrap the value at that line in double quotes, e.g. description: "Setup: the easy way".',
158
+ });
159
+
130
160
  /**
131
161
  * Fold one raw path part into the accumulating route segments/groups.
132
162
  * `ordered` parts are file or folder names, whose ordering prefix is dropped.
@@ -235,9 +265,11 @@ const PROMPT_CLOSE = /<\/Prompt>/u;
235
265
  // the same marker — and the only spelling that survives the MDX parser, where
236
266
  // a bare `{…}` is a JSX expression and `#id` is not a valid one (`Could not
237
267
  // parse expression with acorn`). Further bracket markers may follow the brace
238
- // (`{#id} [toc]`), nothing else.
268
+ // (`{#id} [toc]`), nothing else. The spaced `{ #id }` and kramdown's
269
+ // `{: #id }` (MkDocs `attr_list` writes both) fail the compile the same way,
270
+ // so they match too, though neither is an anchor in `.md`.
239
271
  const BARE_CURLY_MARKER =
240
- /(?<!\\)\{#(?<id>[^\s}]+)\}(?:\s*\[(?:#[^\s\]]+|!?toc)\])*\s*$/u;
272
+ /(?<!\\)(?<marker>\{:?\s*#(?<id>[^\s}]+)\s*\})(?:\s*\[(?:#[^\s\]]+|!?toc)\])*\s*$/u;
241
273
 
242
274
  // A raw HTML element carrying an `id` — `<a id="…">`, `<section id='…'>`,
243
275
  // the unquoted `<a id=plain>`, or the JSX spelling `<div id={"…"}>` — whose
@@ -259,6 +291,8 @@ export interface CurlyMarker {
259
291
  id: string;
260
292
  /** 1-based line of the heading (a setext heading's first text line) in the body. */
261
293
  line: number;
294
+ /** The braces as written (`{#id}`, `{ #id }`, `{: #id }`). */
295
+ marker: string;
262
296
  }
263
297
 
264
298
  /** Scanner state: the open fence plus the paragraph lines accumulated so far. */
@@ -628,9 +662,9 @@ const noteCurlyMarker = (
628
662
  line: number,
629
663
  state: HeadingScanState
630
664
  ): void => {
631
- const id = text.match(BARE_CURLY_MARKER)?.groups?.id;
632
- if (id !== undefined) {
633
- state.curlyMarkers.push({ id, line });
665
+ const groups = text.match(BARE_CURLY_MARKER)?.groups;
666
+ if (groups?.id !== undefined && groups.marker !== undefined) {
667
+ state.curlyMarkers.push({ id: groups.id, line, marker: groups.marker });
634
668
  }
635
669
  };
636
670
 
@@ -890,6 +924,42 @@ export const targetOffsetIn = (
890
924
  title: string | undefined
891
925
  ): number => matched.length - 1 - (title?.length ?? 0) - target.length;
892
926
 
927
+ // A link-reference definition's destination, at the start of what follows its
928
+ // `[label]:` (see `REF_DEFINITION`): `<…>`, which may hold spaces, or else the
929
+ // run up to the first space, before any title.
930
+ const DEFINITION_DESTINATION = /^(?:<(?<angle>[^<>]*)>|(?<bare>\S+))/u;
931
+
932
+ // A CommonMark autolink (`<https://example.com>`). Only http(s) targets are
933
+ // ever checked, so no other scheme is read. MDX rejects the syntax, so in
934
+ // practice it's a `.md` page's.
935
+ const AUTOLINK = /<(?<target>https?:\/\/[^\s<>]*)>/giu;
936
+
937
+ /**
938
+ * Record the destination of a link-reference definition on `line`: a
939
+ * reference link (`[text][label]`, `[label]`) renders with it, so it's the
940
+ * target to check, once for every link that cites it. A footnote definition
941
+ * (`[^1]: …`) is no link; the links inside it are read like any others.
942
+ */
943
+ const scanDefinition = (
944
+ line: string,
945
+ lineNumber: number,
946
+ links: PageLink[]
947
+ ): void => {
948
+ const groups = line.match(REF_DEFINITION)?.groups;
949
+ if (groups?.label === undefined || groups.label.startsWith("^")) {
950
+ return;
951
+ }
952
+ const rest = groups.rest ?? "";
953
+ const destination = rest.match(DEFINITION_DESTINATION)?.groups;
954
+ const target = destination?.angle ?? destination?.bare;
955
+ if (target) {
956
+ // `rest` runs to the end of the line; an angle-bracketed target starts
957
+ // one past its `<`.
958
+ const at = line.length - rest.length + (destination?.angle ? 1 : 0);
959
+ links.push({ column: at + 1, line: lineNumber, target });
960
+ }
961
+ };
962
+
893
963
  /**
894
964
  * Extract link targets from a markdown body for later validation, recording the
895
965
  * 1-based line/column of each target. Skips fenced code blocks and inline code.
@@ -954,18 +1024,32 @@ const scanLinkLine = (
954
1024
  });
955
1025
  }
956
1026
  }
1027
+ for (const match of masked.matchAll(AUTOLINK)) {
1028
+ const target = match.groups?.target ?? "";
1029
+ links.push({ column: match.index + 2, line: lineNumber, target });
1030
+ }
1031
+ scanDefinition(masked, lineNumber, links);
957
1032
  return next;
958
1033
  };
959
1034
 
960
1035
  /**
961
- * A component's string `href` (`<Card href="./install" />`): the attribute
962
- * may sit on a later line than the tag name, where a formatter wraps a long
963
- * element, so the gap between them spans lines. An expression-valued
964
- * `href={…}` isn't a literal target, and a lowercase tag is raw HTML in a
965
- * `.md` page, so neither is matched.
1036
+ * A component's string `href` (`<Card href="./install" />`), or a lowercase
1037
+ * `<a href>`: the attribute may sit on a later line than the tag name, where
1038
+ * a formatter wraps a long element, so the gap between them spans lines. An
1039
+ * expression-valued `href={…}` isn't a literal target, so it isn't matched,
1040
+ * and no other lowercase tag is a link.
966
1041
  */
967
- const COMPONENT_HREF =
968
- /<[A-Z][\w.]*(?=[\s/>])[^<>]*?\shref=(?:"(?<double>[^"]*)"|'(?<single>[^']*)')/gu;
1042
+ const ELEMENT_HREF =
1043
+ /<(?:[A-Z][\w.]*|(?<anchor>a))(?=[\s/>])[^<>]*?\shref=(?:"(?<double>[^"]*)"|'(?<single>[^']*)')/gu;
1044
+
1045
+ /**
1046
+ * An inline link whose label wraps onto later lines of its paragraph
1047
+ * (`[a long⏎label](/x)`), as a formatter leaves one: the line scan can't see
1048
+ * it. The label holds a line break but no blank line (which would end the
1049
+ * paragraph) and no brackets, so a match never repeats a one-line link.
1050
+ */
1051
+ const WRAPPED_LINK =
1052
+ /\[[^[\]\n]*(?:\n(?![ \t]*(?:\n|$))[^[\]\n]*)+\]\((?<target>(?:[^()\s]|\([^()\s]*\))+)(?<title>\s+"[^"]*")?\)/gu;
969
1053
 
970
1054
  /** The body with fenced blocks and inline code blanked, shape preserved. */
971
1055
  const maskCode = (lines: readonly string[]): string => {
@@ -981,24 +1065,83 @@ const maskCode = (lines: readonly string[]): string => {
981
1065
  return masked.join("\n");
982
1066
  };
983
1067
 
984
- /** Every component `href` target in `body`, with its 1-based position. */
985
- const componentHrefs = (
1068
+ /** The offset each of `lines` starts at once they're joined with `\n`. */
1069
+ const lineStartsOf = (lines: readonly string[]): number[] => {
1070
+ const starts: number[] = [];
1071
+ let at = 0;
1072
+ for (const line of lines) {
1073
+ starts.push(at);
1074
+ at += line.length + 1;
1075
+ }
1076
+ return starts;
1077
+ };
1078
+
1079
+ /**
1080
+ * The 1-based position of offset `at` in the joined text whose `lineStarts`
1081
+ * are given, lines shifted by `lineOffset`. A binary search, so a page with
1082
+ * many links isn't rescanned from the top for each one.
1083
+ */
1084
+ const positionIn = (
1085
+ lineStarts: readonly number[],
1086
+ at: number,
1087
+ lineOffset: number
1088
+ ): Pick<PageLink, "column" | "line"> => {
1089
+ let low = 0;
1090
+ let high = lineStarts.length - 1;
1091
+ while (low < high) {
1092
+ const mid = Math.ceil((low + high) / 2);
1093
+ if ((lineStarts[mid] ?? 0) <= at) {
1094
+ low = mid;
1095
+ } else {
1096
+ high = mid - 1;
1097
+ }
1098
+ }
1099
+ return {
1100
+ column: at - (lineStarts[low] ?? 0) + 1,
1101
+ line: lineOffset + low + 1,
1102
+ };
1103
+ };
1104
+
1105
+ /**
1106
+ * Every link in `body` that can span lines, with its 1-based position: an
1107
+ * element's `href` (see `ELEMENT_HREF`) and a link whose label wraps (see
1108
+ * `WRAPPED_LINK`). A lowercase `<a>` is raw HTML in `.md` and a plain element
1109
+ * in `.mdx`; the Markdown pipeline passes either through as written, so its
1110
+ * target is marked `raw`.
1111
+ */
1112
+ const multilineLinks = (
986
1113
  lines: readonly string[],
987
1114
  lineOffset: number
988
1115
  ): PageLink[] => {
989
1116
  const links: PageLink[] = [];
990
1117
  const text = maskCode(lines);
991
- for (const match of text.matchAll(COMPONENT_HREF)) {
1118
+ const lineStarts = lineStartsOf(lines);
1119
+ for (const match of text.matchAll(ELEMENT_HREF)) {
992
1120
  const target = match.groups?.double ?? match.groups?.single ?? "";
993
1121
  // The value ends one character (its closing quote) before the match does.
994
1122
  const at = match.index + match[0].length - target.length - 1;
995
- const before = text.slice(0, at);
996
- const lineStart = before.lastIndexOf("\n") + 1;
997
- links.push({
998
- column: at - lineStart + 1,
999
- line: lineOffset + before.split("\n").length,
1123
+ const link: PageLink = {
1124
+ ...positionIn(lineStarts, at, lineOffset),
1000
1125
  target,
1001
- });
1126
+ };
1127
+ if (match.groups?.anchor) {
1128
+ link.raw = true;
1129
+ }
1130
+ links.push(link);
1131
+ }
1132
+ for (const match of text.matchAll(WRAPPED_LINK)) {
1133
+ const target = match.groups?.target ?? "";
1134
+ const at =
1135
+ match.index + targetOffsetIn(match[0], target, match.groups?.title);
1136
+ const link: PageLink = {
1137
+ ...positionIn(lineStarts, at, lineOffset),
1138
+ target,
1139
+ };
1140
+ // As on one line, a preceding `!` makes it an image embed.
1141
+ if (text[match.index - 1] === "!") {
1142
+ link.image = true;
1143
+ }
1144
+ links.push(link);
1002
1145
  }
1003
1146
  return links;
1004
1147
  };
@@ -1014,9 +1157,37 @@ export const extractLinks = (body: string, lineOffset = 0): PageLink[] => {
1014
1157
  fence = scanLinkLine(line, lineNumber, fence, links);
1015
1158
  }
1016
1159
 
1017
- return body.includes("href=")
1018
- ? [...links, ...componentHrefs(lines, lineOffset)]
1019
- : links;
1160
+ return [...links, ...multilineLinks(lines, lineOffset)];
1161
+ };
1162
+
1163
+ /**
1164
+ * A `<Component>` example's string `path` (`<Component path="forms/login" />`),
1165
+ * wrapped onto a later line or not, read the way `ELEMENT_HREF` reads an
1166
+ * `href`. An expression-valued `path={…}` isn't a literal, so it isn't matched.
1167
+ */
1168
+ const COMPONENT_PATH =
1169
+ /<Component(?=[\s/>])[^<>]*?\spath=(?:"(?<double>[^"]*)"|'(?<single>[^']*)')/gu;
1170
+
1171
+ /**
1172
+ * Every `<Component path>` in an `.mdx` body, with the 1-based position of its
1173
+ * value; lines are shifted by `lineOffset`, as {@link extractLinks} does. Code
1174
+ * blocks and inline code are skipped.
1175
+ */
1176
+ export const extractExampleUses = (
1177
+ body: string,
1178
+ lineOffset = 0
1179
+ ): ExampleUse[] => {
1180
+ if (!body.includes("<Component")) {
1181
+ return [];
1182
+ }
1183
+ const lines = body.split("\n");
1184
+ const lineStarts = lineStartsOf(lines);
1185
+ return [...maskCode(lines).matchAll(COMPONENT_PATH)].map((match) => {
1186
+ const path = match.groups?.double ?? match.groups?.single ?? "";
1187
+ // The value ends one character (its closing quote) before the match does.
1188
+ const at = match.index + match[0].length - path.length - 1;
1189
+ return { ...positionIn(lineStarts, at, lineOffset), path };
1190
+ });
1020
1191
  };
1021
1192
 
1022
1193
  // Double-quoted strings hold JSX attribute values and JSON in `{...}` props; a
@@ -1082,23 +1253,29 @@ export const strippedLineOffset = (
1082
1253
  const entryLineOffset = (entry: SourceEntry): number =>
1083
1254
  entry.bodyLineOffset ?? strippedLineOffset(entry.raw, entry.body.text);
1084
1255
 
1256
+ /** A link (or `<Component>` example) found in a body: its line, and file. */
1257
+ interface Located {
1258
+ file?: string;
1259
+ line: number;
1260
+ }
1261
+
1085
1262
  /**
1086
1263
  * Map links extracted from include-expanded text back to the file and raw
1087
1264
  * line each expanded line came from, so a broken link inside a partial is
1088
1265
  * reported against the partial. Links whose origin is the page's own source
1089
1266
  * carry no `file` override (origins already hold raw-file lines).
1090
1267
  */
1091
- const remapExpandedLinks = (
1092
- links: PageLink[],
1268
+ const remapExpandedLinks = <T extends Located>(
1269
+ links: T[],
1093
1270
  origins: { file: string; line: number }[],
1094
1271
  sourcePath: string | undefined
1095
- ): PageLink[] =>
1272
+ ): T[] =>
1096
1273
  links.map((link) => {
1097
1274
  const origin = origins[link.line - 1];
1098
1275
  if (!origin) {
1099
1276
  return link;
1100
1277
  }
1101
- const remapped: PageLink = { ...link, line: origin.line };
1278
+ const remapped: T = { ...link, line: origin.line };
1102
1279
  if (origin.file !== sourcePath) {
1103
1280
  remapped.file = origin.file;
1104
1281
  }
@@ -1112,22 +1289,26 @@ const entryIncludes = (entry: SourceEntry): string[] | undefined =>
1112
1289
  : undefined;
1113
1290
 
1114
1291
  /**
1115
- * Extract an entry's links for validation. When the scan expanded includes,
1116
- * extraction runs over the expanded text (origins already hold raw-file
1117
- * lines); otherwise over the stripped body, shifted by the stripped front
1118
- * matter block's height.
1292
+ * Extract an entry's links (or `<Component>` examples) for validation. When
1293
+ * the scan expanded includes, extraction runs over the expanded text (origins
1294
+ * already hold raw-file lines); otherwise over the stripped body, shifted by
1295
+ * the stripped front matter block's height.
1119
1296
  */
1120
- const entryLinks = (entry: SourceEntry): PageLink[] =>
1297
+ const entryLinks = <T extends Located>(
1298
+ entry: SourceEntry,
1299
+ extract: (body: string, lineOffset?: number) => T[]
1300
+ ): T[] =>
1121
1301
  entry.expanded
1122
1302
  ? remapExpandedLinks(
1123
- extractLinks(entry.expanded.text),
1303
+ extract(entry.expanded.text),
1124
1304
  entry.expanded.origins,
1125
1305
  entry.sourcePath
1126
1306
  )
1127
- : extractLinks(entry.body.text, entryLineOffset(entry));
1307
+ : extract(entry.body.text, entryLineOffset(entry));
1128
1308
 
1129
1309
  /**
1130
- * Diagnostics for `{#id}` heading markers in an `.mdx` page. The MDX parser
1310
+ * Diagnostics for `{#id}` heading markers (and the spaced `{ #id }` and
1311
+ * kramdown `{: #id }` spellings) in an `.mdx` page. The MDX parser
1131
1312
  * reads a bare `{…}` as a JSX expression, so the page fails to compile —
1132
1313
  * reported here, at the marker's source line, instead of as a raw acorn error
1133
1314
  * at render time. An included `.md` partial is spliced into the including
@@ -1140,7 +1321,7 @@ const curlyMarkerDiagnostics = (
1140
1321
  markers: CurlyMarker[],
1141
1322
  sourceName: string
1142
1323
  ): Diagnostic[] =>
1143
- markers.map(({ id, line }) => {
1324
+ markers.map(({ id, line, marker }) => {
1144
1325
  const origin = entry.expanded?.origins[line - 1];
1145
1326
  const page = entry.sourcePath ?? `${sourceName}:${entry.ref}`;
1146
1327
  const inPartial = origin !== undefined && origin.file !== entry.sourcePath;
@@ -1149,8 +1330,8 @@ const curlyMarkerDiagnostics = (
1149
1330
  file: origin?.file ?? page,
1150
1331
  line: origin?.line ?? line + entryLineOffset(entry),
1151
1332
  message: inPartial
1152
- ? `\`{#${id}}\` is a JSX expression once this partial is included in ${page} (.mdx), so that page fails to compile.`
1153
- : `\`{#${id}}\` is a JSX expression in .mdx, so this page fails to compile.`,
1333
+ ? `\`${marker}\` is a JSX expression once this partial is included in ${page} (.mdx), so that page fails to compile.`
1334
+ : `\`${marker}\` is a JSX expression in .mdx, so this page fails to compile.`,
1154
1335
  severity: "error",
1155
1336
  suggestion: `Write \`[#${id}]\` or escape it as \`\\{#${id}\\}\` — both pin the same anchor in .md and .mdx.`,
1156
1337
  };
@@ -1549,13 +1730,18 @@ export const normalizeEntry = (
1549
1730
  description: meta.description,
1550
1731
  editUrl: entry.editUrl,
1551
1732
  entryId: staged ? `${ctx.source.name}/${entry.ref}` : undefined,
1733
+ examplesUsed:
1734
+ format === "mdx" ? entryLinks(entry, extractExampleUses) : undefined,
1552
1735
  format,
1553
1736
  groups,
1554
1737
  headings,
1555
1738
  id: `${ctx.source.name}:${entry.ref}`,
1556
1739
  includes: entryIncludes(entry),
1557
1740
  lastModified: meta.lastModified ?? entry.lastModified,
1558
- links: [...entryLinks(entry), ...relatedPageLinks(meta.related)],
1741
+ links: [
1742
+ ...entryLinks(entry, extractLinks),
1743
+ ...relatedPageLinks(meta.related),
1744
+ ],
1559
1745
  meta,
1560
1746
  monolingual: ctx.source.monolingual,
1561
1747
  navPath,
@@ -24,6 +24,7 @@ import {
24
24
  renderInline,
25
25
  } from "./lower.ts";
26
26
  import { slugify, slugifyPath } from "./normalize.ts";
27
+ import { missingSecretError } from "./remote.ts";
27
28
  import type {
28
29
  ContentSource,
29
30
  SourceContext,
@@ -446,7 +447,12 @@ export const notionSource = (
446
447
  severity: "error",
447
448
  });
448
449
  }
449
- return new Client({ auth: options.token ?? process.env.NOTION_TOKEN });
450
+ // Every Notion API call needs the integration token.
451
+ const auth = options.token ?? process.env.NOTION_TOKEN;
452
+ if (!auth) {
453
+ throw missingSecretError(options.name, "NOTION_TOKEN");
454
+ }
455
+ return new Client({ auth });
450
456
  };
451
457
 
452
458
  const childrenOf = (
@@ -2,6 +2,7 @@ import { existsSync, watch as fsWatch, statSync } from "node:fs";
2
2
  import type { Dirent } from "node:fs";
3
3
  import { readdir, readFile, stat } from "node:fs/promises";
4
4
 
5
+ import { YAMLException } from "js-yaml";
5
6
  import { basename, isAbsolute, join, relative, resolve } from "pathe";
6
7
 
7
8
  import { nextFenceState } from "../code-fences.ts";
@@ -19,6 +20,7 @@ import {
19
20
  localizedRoute,
20
21
  placeEntryRef,
21
22
  resolveEntryRoute,
23
+ frontmatterYamlDiagnostic,
22
24
  slugifyPath,
23
25
  strippedLineOffset,
24
26
  unloadablePathDiagnostic,
@@ -785,13 +787,14 @@ const noteToEntry = (
785
787
  * Read and split one note, or null when it vanished between the walk and the
786
788
  * read. Obsidian renames and deletes notes while a dev server watches the
787
789
  * vault, and one file disappearing mid-load must not fail the whole reload.
788
- * Any other read failure still throws.
790
+ * A note whose front matter isn't valid YAML comes back as its diagnostic,
791
+ * so it's left out the same way. Any other read failure still throws.
789
792
  */
790
793
  const readNote = async (
791
794
  vaultDir: string,
792
795
  rel: string,
793
796
  options: Pick<ObsidianSourceOptions, "i18n" | "versions">
794
- ): Promise<ParsedNote | null> => {
797
+ ): Promise<ParsedNote | Diagnostic | null> => {
795
798
  const absPath = join(vaultDir, rel);
796
799
  try {
797
800
  const file = await readFile(absPath, "utf-8");
@@ -808,6 +811,9 @@ const readNote = async (
808
811
  rel,
809
812
  };
810
813
  } catch (error) {
814
+ if (error instanceof YAMLException) {
815
+ return frontmatterYamlDiagnostic(error, absPath);
816
+ }
811
817
  // SAFETY: a rejected `readFile` always yields a Node system error, whose
812
818
  // `code` is the only field read here.
813
819
  if ((error as NodeJS.ErrnoException).code === "ENOENT") {
@@ -976,7 +982,14 @@ export const obsidianSource = (
976
982
  const read = await Promise.all(
977
983
  files.map((rel) => readNote(vaultDir, rel, options))
978
984
  );
979
- const notes = read.filter((note): note is ParsedNote => note !== null);
985
+ const notes = read.filter(
986
+ (note): note is ParsedNote => note !== null && "rel" in note
987
+ );
988
+ skipped.push(
989
+ ...read.filter(
990
+ (note): note is Diagnostic => note !== null && "code" in note
991
+ )
992
+ );
980
993
  // Resolving `[[Note#H]]` needs the target's route and headings, so every
981
994
  // note is parsed and indexed before any body is rewritten.
982
995
  const { index, pairs } = buildLinkIndex(notes, options);
@@ -1,8 +1,9 @@
1
+ import type { Diagnostic } from "../types.ts";
1
2
  import type { JsonObject } from "./json.ts";
2
3
  import { asObject, asString, isJsonObject, objectsIn } from "./json.ts";
3
4
  import { lexicalToMarkdown } from "./lexical.ts";
4
5
  import { writesMdx } from "./lower.ts";
5
- import type { RemoteFieldMap, RestClient } from "./remote.ts";
6
+ import type { RemoteFieldMap, RemoteFields, RestClient } from "./remote.ts";
6
7
  import {
7
8
  documentEntry,
8
9
  fetchJson,
@@ -44,7 +45,7 @@ export interface PayloadSourceOptions {
44
45
 
45
46
  const PAGE_SIZE = 100;
46
47
 
47
- const DEFAULT_FIELDS: Required<RemoteFieldMap> = {
48
+ const DEFAULT_FIELDS: RemoteFields = {
48
49
  body: "content",
49
50
  description: "description",
50
51
  lastModified: "updatedAt",
@@ -64,13 +65,45 @@ export const payloadSource = (
64
65
  const fields = { ...DEFAULT_FIELDS, ...options.fields };
65
66
  const origin = options.url.replace(/\/+$/u, "");
66
67
 
67
- const lower = (body: JsonObject): string =>
68
+ const lower = (body: JsonObject, leftOut: Set<string>): string =>
68
69
  lexicalToMarkdown(body, {
69
70
  baseUrl: origin,
71
+ onUnsupported: (what) => leftOut.add(what),
70
72
  serializers: options.serializers,
71
73
  });
72
74
 
73
- const fetchEntries = async (): Promise<SourceEntry[]> => {
75
+ // A document mapped to its entry, with a warning for each kind of node its
76
+ // body left out as a comment.
77
+ const toEntry = (
78
+ doc: JsonObject,
79
+ draft: boolean,
80
+ warn: (diagnostic: Diagnostic) => void
81
+ ): SourceEntry => {
82
+ const leftOut = new Set<string>();
83
+ const id = asString(doc.id) ?? String(doc.id ?? "");
84
+ const entry = documentEntry(
85
+ doc,
86
+ fields,
87
+ id,
88
+ (body) => (isJsonObject(body) ? lower(body, leftOut) : ""),
89
+ draft,
90
+ writesMdx(options.serializers)
91
+ );
92
+ for (const what of leftOut) {
93
+ warn({
94
+ code: "BLUME_SOURCE_UNSUPPORTED_NODE",
95
+ message: `Source "${options.name}": "${entry.ref}" has content Blume has no Markdown for (${what}), left out as a comment.`,
96
+ severity: "warning",
97
+ suggestion:
98
+ "Map a block type to a component with a serializer on payloadSource, passed to custom(); other nodes have no Markdown equivalent.",
99
+ });
100
+ }
101
+ return entry;
102
+ };
103
+
104
+ const fetchEntries = async (
105
+ warn: (diagnostic: Diagnostic) => void
106
+ ): Promise<SourceEntry[]> => {
74
107
  const preview = ctx?.preview ?? false;
75
108
  const token = options.token ?? process.env.PAYLOAD_API_KEY;
76
109
  const client: RestClient = {
@@ -105,17 +138,7 @@ export const payloadSource = (
105
138
  if (draft && !preview) {
106
139
  continue;
107
140
  }
108
- const id = asString(doc.id) ?? String(doc.id ?? "");
109
- entries.push(
110
- documentEntry(
111
- doc,
112
- fields,
113
- id,
114
- (body) => (isJsonObject(body) ? lower(body) : ""),
115
- draft,
116
- writesMdx(options.serializers)
117
- )
118
- );
141
+ entries.push(toEntry(doc, draft, warn));
119
142
  }
120
143
  more = result.hasNextPage === true;
121
144
  page += 1;