blume 2.0.3 → 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 (607) hide show
  1. package/AGENTS.md +1 -0
  2. package/CHANGELOG.md +142 -0
  3. package/README.md +1 -1
  4. package/dist/cli/{chunk-5f86nr5m.js → chunk-073rdvgb.js} +21 -48
  5. package/dist/cli/{chunk-5f86nr5m.js.map → chunk-073rdvgb.js.map} +4 -5
  6. package/dist/cli/{chunk-ga0pf4aj.js → chunk-0n9xkp3d.js} +12 -3
  7. package/dist/cli/{chunk-ga0pf4aj.js.map → chunk-0n9xkp3d.js.map} +3 -3
  8. package/dist/cli/chunk-150jk7km.js +52 -0
  9. package/dist/cli/chunk-150jk7km.js.map +10 -0
  10. package/dist/cli/{chunk-9t7a85s3.js → chunk-313vnn40.js} +30 -19
  11. package/dist/cli/chunk-313vnn40.js.map +10 -0
  12. package/dist/cli/{chunk-tkacnehg.js → chunk-3v3ffbrj.js} +2 -2
  13. package/dist/cli/{chunk-q58y5e6a.js → chunk-3x6x3pph.js} +46 -24
  14. package/dist/cli/chunk-3x6x3pph.js.map +10 -0
  15. package/dist/cli/{chunk-1d7ve1dm.js → chunk-42t4jxdx.js} +37 -5
  16. package/dist/cli/chunk-42t4jxdx.js.map +12 -0
  17. package/dist/cli/{chunk-8ktnccpt.js → chunk-54ztw1nj.js} +21 -2
  18. package/dist/cli/chunk-54ztw1nj.js.map +10 -0
  19. package/dist/cli/{chunk-hm3vjy5s.js → chunk-5vjtgs6e.js} +28 -10
  20. package/dist/cli/{chunk-hm3vjy5s.js.map → chunk-5vjtgs6e.js.map} +4 -4
  21. package/dist/cli/chunk-66w9j7p6.js +322 -0
  22. package/dist/cli/chunk-66w9j7p6.js.map +14 -0
  23. package/dist/cli/{chunk-2hsdwb9n.js → chunk-7ppf1rva.js} +34 -18
  24. package/dist/cli/chunk-7ppf1rva.js.map +10 -0
  25. package/dist/cli/{chunk-r9rcc4w7.js → chunk-8y2kmjv8.js} +7 -7
  26. package/dist/cli/{chunk-r20tn01b.js → chunk-c1y538kd.js} +8 -2
  27. package/dist/cli/{chunk-r20tn01b.js.map → chunk-c1y538kd.js.map} +3 -3
  28. package/dist/cli/{chunk-2eytanqx.js → chunk-c887fj0s.js} +2 -2
  29. package/dist/cli/{chunk-p3v96n38.js → chunk-cfyg47zg.js} +16 -6
  30. package/dist/cli/chunk-cfyg47zg.js.map +10 -0
  31. package/dist/cli/chunk-cm89scdv.js +443 -0
  32. package/dist/cli/chunk-cm89scdv.js.map +11 -0
  33. package/dist/cli/{chunk-akbpwfxc.js → chunk-dzwehpy7.js} +2 -2
  34. package/dist/cli/{chunk-wrr3j9w9.js → chunk-e6atmn4h.js} +299 -72
  35. package/dist/cli/chunk-e6atmn4h.js.map +36 -0
  36. package/dist/cli/{chunk-tzmab476.js → chunk-e8xw9qkh.js} +8 -6
  37. package/dist/cli/{chunk-tzmab476.js.map → chunk-e8xw9qkh.js.map} +3 -3
  38. package/dist/cli/{chunk-6dsbexzp.js → chunk-e9q7a2ak.js} +17 -15
  39. package/dist/cli/{chunk-6dsbexzp.js.map → chunk-e9q7a2ak.js.map} +2 -2
  40. package/dist/cli/{chunk-pv29h0wf.js → chunk-ehweg4fr.js} +5155 -1249
  41. package/dist/cli/chunk-ehweg4fr.js.map +218 -0
  42. package/dist/cli/{chunk-c8chx29p.js → chunk-ep6nd0rn.js} +390 -318
  43. package/dist/cli/chunk-ep6nd0rn.js.map +36 -0
  44. package/dist/cli/{chunk-p73c0m7w.js → chunk-fdrkhpjy.js} +18 -15
  45. package/dist/cli/{chunk-p73c0m7w.js.map → chunk-fdrkhpjy.js.map} +3 -3
  46. package/dist/cli/{chunk-2z928egk.js → chunk-ft9vfys6.js} +5 -5
  47. package/dist/cli/{chunk-5m5nmvyq.js → chunk-gb420vvz.js} +316 -79
  48. package/dist/cli/chunk-gb420vvz.js.map +17 -0
  49. package/dist/cli/{chunk-vg9r4eb9.js → chunk-hebn0tpx.js} +86 -13
  50. package/dist/cli/chunk-hebn0tpx.js.map +14 -0
  51. package/dist/cli/{chunk-35d4wj9f.js → chunk-htrwgabr.js} +41 -23
  52. package/dist/cli/chunk-htrwgabr.js.map +15 -0
  53. package/dist/cli/{chunk-g4hq16wv.js → chunk-hx3xqnk9.js} +16 -14
  54. package/dist/cli/{chunk-g4hq16wv.js.map → chunk-hx3xqnk9.js.map} +2 -2
  55. package/dist/cli/chunk-j134vrp2.js +65 -0
  56. package/dist/cli/chunk-j134vrp2.js.map +10 -0
  57. package/dist/cli/{chunk-a58773jm.js → chunk-kma9f6sf.js} +2 -2
  58. package/dist/cli/chunk-kwrg63tm.js +31 -0
  59. package/dist/cli/chunk-kwrg63tm.js.map +10 -0
  60. package/dist/cli/{chunk-ey84smr6.js → chunk-mrcccgnh.js} +6 -6
  61. package/dist/cli/{chunk-ey84smr6.js.map → chunk-mrcccgnh.js.map} +1 -1
  62. package/dist/cli/{chunk-yfyb25rh.js → chunk-n0v7m8ry.js} +24 -8
  63. package/dist/cli/chunk-n0v7m8ry.js.map +14 -0
  64. package/dist/cli/{chunk-j85vccga.js → chunk-ncj7w596.js} +576 -367
  65. package/dist/cli/chunk-ncj7w596.js.map +35 -0
  66. package/dist/cli/{chunk-5xvm6tfj.js → chunk-nq1d20w2.js} +17 -15
  67. package/dist/cli/{chunk-5xvm6tfj.js.map → chunk-nq1d20w2.js.map} +2 -2
  68. package/dist/cli/{chunk-pehfxfta.js → chunk-nrns10mj.js} +6 -98
  69. package/dist/cli/chunk-nrns10mj.js.map +10 -0
  70. package/dist/cli/{chunk-crgn1q09.js → chunk-sds923fa.js} +4 -4
  71. package/dist/cli/chunk-t2m5j4pr.js +104 -0
  72. package/dist/cli/chunk-t2m5j4pr.js.map +11 -0
  73. package/dist/cli/{chunk-acanzt5p.js → chunk-tdqhyxfz.js} +6 -6
  74. package/dist/cli/chunk-tphac74f.js +126 -0
  75. package/dist/cli/chunk-tphac74f.js.map +11 -0
  76. package/dist/cli/chunk-w1r4vase.js +18 -0
  77. package/dist/cli/chunk-w1r4vase.js.map +10 -0
  78. package/dist/cli/{chunk-3em5wd2y.js → chunk-zccbjcj0.js} +4 -3
  79. package/dist/cli/{chunk-3em5wd2y.js.map → chunk-zccbjcj0.js.map} +2 -2
  80. package/dist/cli/index.js +31 -20
  81. package/dist/cli/index.js.map +5 -5
  82. package/dist/types/ai/agent-surface.d.ts +8 -7
  83. package/dist/types/ai/ai-catalog.d.ts +7 -0
  84. package/dist/types/ai/ask-context.d.ts +26 -0
  85. package/dist/types/ai/ask.d.ts +271 -58
  86. package/dist/types/ai/component-markdown.d.ts +2 -0
  87. package/dist/types/ai/index.d.ts +2 -2
  88. package/dist/types/ai/openapi-components.d.ts +4 -1
  89. package/dist/types/ai/site-skill.d.ts +26 -0
  90. package/dist/types/ai/skills.d.ts +16 -0
  91. package/dist/types/astro/integration.d.ts +9 -0
  92. package/dist/types/astro/markdown-negotiation.d.ts +3 -0
  93. package/dist/types/astro/pages.d.ts +10 -0
  94. package/dist/types/captcha/hcaptcha.d.ts +27 -0
  95. package/dist/types/captcha/index.d.ts +28 -0
  96. package/dist/types/captcha/schema.d.ts +29 -0
  97. package/dist/types/captcha/turnstile.d.ts +28 -0
  98. package/dist/types/cli/init/scaffold.d.ts +1 -1
  99. package/dist/types/components/content/api-field.d.ts +62 -0
  100. package/dist/types/components/content/api-page.d.ts +100 -0
  101. package/dist/types/components/layout/nav-utils.d.ts +26 -0
  102. package/dist/types/components/openapi/code-samples.d.ts +29 -0
  103. package/dist/types/components/openapi/helpers.d.ts +158 -0
  104. package/dist/types/components/openapi/security.d.ts +95 -0
  105. package/dist/types/components/openapi/snippets.d.ts +60 -0
  106. package/dist/types/consent/ethyca.d.ts +48 -0
  107. package/dist/types/consent/index.d.ts +37 -0
  108. package/dist/types/consent/native.d.ts +28 -0
  109. package/dist/types/consent/osano.d.ts +42 -0
  110. package/dist/types/consent/schema.d.ts +110 -0
  111. package/dist/types/core/changelog-index.d.ts +8 -0
  112. package/dist/types/core/config-input.d.ts +217 -23
  113. package/dist/types/core/data.d.ts +53 -4
  114. package/dist/types/core/footer.d.ts +8 -0
  115. package/dist/types/core/i18n-ui.d.ts +88 -0
  116. package/dist/types/core/includes.d.ts +8 -2
  117. package/dist/types/core/links.d.ts +8 -0
  118. package/dist/types/core/localizable.d.ts +7 -0
  119. package/dist/types/core/page-modes.d.ts +29 -0
  120. package/dist/types/core/probe.d.ts +2 -0
  121. package/dist/types/core/project-graph.d.ts +6 -0
  122. package/dist/types/core/redirect-patterns.d.ts +152 -0
  123. package/dist/types/core/related.d.ts +40 -0
  124. package/dist/types/core/schema.d.ts +670 -56
  125. package/dist/types/core/sources/cache.d.ts +3 -0
  126. package/dist/types/core/sources/contentful-rich-text.d.ts +11 -2
  127. package/dist/types/core/sources/contentful.d.ts +17 -1
  128. package/dist/types/core/sources/lexical.d.ts +5 -0
  129. package/dist/types/core/sources/normalize.d.ts +20 -1
  130. package/dist/types/core/sources/read.d.ts +5 -0
  131. package/dist/types/core/sources/remote.d.ts +37 -10
  132. package/dist/types/core/sources/sanity.d.ts +1 -1
  133. package/dist/types/core/types.d.ts +18 -1
  134. package/dist/types/core/variables.d.ts +42 -0
  135. package/dist/types/deploy/adapter-output.d.ts +2 -1
  136. package/dist/types/deploy/adapters/cloudflare.d.ts +7 -5
  137. package/dist/types/deploy/adapters/node.d.ts +58 -23
  138. package/dist/types/deploy/adapters/registry.d.ts +10 -0
  139. package/dist/types/deploy/adapters/types.d.ts +3 -2
  140. package/dist/types/deploy/node-headers.d.ts +13 -2
  141. package/dist/types/deploy/platforms/cloudflare.d.ts +15 -7
  142. package/dist/types/deploy/platforms/netlify.d.ts +7 -0
  143. package/dist/types/deploy/platforms/node.d.ts +3 -1
  144. package/dist/types/deploy/platforms/types.d.ts +18 -7
  145. package/dist/types/deploy/platforms/vercel.d.ts +26 -1
  146. package/dist/types/deploy/redirects.d.ts +55 -11
  147. package/dist/types/deploy/sitemap.d.ts +2 -1
  148. package/dist/types/deploy/vercel-negotiation.d.ts +51 -21
  149. package/dist/types/markdown/code-title.d.ts +17 -0
  150. package/dist/types/markdown/features.d.ts +3 -2
  151. package/dist/types/narration/build.d.ts +70 -0
  152. package/dist/types/narration/provider.d.ts +26 -0
  153. package/dist/types/narration/script.d.ts +154 -0
  154. package/dist/types/openapi/checks.d.ts +27 -0
  155. package/dist/types/openapi/graphql.d.ts +6 -0
  156. package/dist/types/openapi/model.d.ts +27 -10
  157. package/dist/types/openapi/overlay.d.ts +54 -0
  158. package/dist/types/openapi/parse.d.ts +16 -0
  159. package/dist/types/openapi/references.d.ts +30 -4
  160. package/dist/types/ratelimit/cloudflare.d.ts +39 -0
  161. package/dist/types/ratelimit/index.d.ts +27 -0
  162. package/dist/types/ratelimit/memory.d.ts +36 -0
  163. package/dist/types/ratelimit/schema.d.ts +119 -0
  164. package/dist/types/ratelimit/upstash.d.ts +46 -0
  165. package/dist/types/ratelimit/wrangler.d.ts +23 -0
  166. package/dist/types/reference/asyncapi.d.ts +10 -10
  167. package/dist/types/reference/graphql.d.ts +89 -11
  168. package/dist/types/reference/index.d.ts +1 -1
  169. package/dist/types/reference/openapi.d.ts +33 -12
  170. package/dist/types/reference/options.d.ts +82 -6
  171. package/dist/types/reference/scalar.d.ts +13 -0
  172. package/dist/types/reference/schema.d.ts +144 -36
  173. package/dist/types/search/adapters/registry.d.ts +3 -0
  174. package/dist/types/search/adapters/typesense.d.ts +14 -4
  175. package/dist/types/search/build.d.ts +3 -1
  176. package/dist/types/search/documents.d.ts +12 -1
  177. package/dist/types/search/orama-index.d.ts +13 -9
  178. package/dist/types/search/sync/algolia.d.ts +5 -2
  179. package/dist/types/search/sync/index.d.ts +3 -1
  180. package/dist/types/search/sync/typesense.d.ts +11 -7
  181. package/dist/types/seo/metatags.d.ts +20 -0
  182. package/dist/types/sources/contentful.d.ts +16 -0
  183. package/dist/types/sources/github-releases.d.ts +8 -1
  184. package/dist/types/sources/payload.d.ts +2 -0
  185. package/dist/types/sources/registry.d.ts +54 -0
  186. package/dist/types/sources/shared.d.ts +3 -0
  187. package/dist/types/sources/strapi.d.ts +2 -0
  188. package/docs/01-quickstart.mdx +4 -2
  189. package/docs/02-deployment.mdx +71 -17
  190. package/docs/03-upgrading.mdx +4 -4
  191. package/docs/04-migrating.mdx +1 -1
  192. package/docs/advanced/changelog.mdx +13 -2
  193. package/docs/advanced/skills.mdx +15 -1
  194. package/docs/cli/audit.mdx +51 -8
  195. package/docs/cli/doctor.mdx +5 -1
  196. package/docs/cli/evals.mdx +4 -4
  197. package/docs/cli/index.mdx +1 -0
  198. package/docs/cli/translate.mdx +1 -0
  199. package/docs/cli/validate.mdx +4 -3
  200. package/docs/configuration/analytics.mdx +3 -1
  201. package/docs/configuration/assistant.mdx +194 -38
  202. package/docs/configuration/consent.mdx +100 -0
  203. package/docs/configuration/customization.mdx +8 -6
  204. package/docs/configuration/export.mdx +5 -3
  205. package/docs/configuration/index.mdx +64 -5
  206. package/docs/configuration/meta.ts +3 -0
  207. package/docs/configuration/narration.mdx +119 -0
  208. package/docs/configuration/rate-limiting.mdx +90 -0
  209. package/docs/configuration/search.mdx +63 -12
  210. package/docs/configuration/theming.mdx +3 -1
  211. package/docs/content/components.mdx +108 -2
  212. package/docs/content/frontmatter.mdx +80 -2
  213. package/docs/content/i18n.mdx +39 -4
  214. package/docs/content/includes.mdx +16 -0
  215. package/docs/content/meta.mdx +1 -0
  216. package/docs/content/meta.ts +1 -0
  217. package/docs/content/navigation.mdx +32 -10
  218. package/docs/content/sources/contentful.mdx +35 -0
  219. package/docs/content/sources/custom.mdx +43 -0
  220. package/docs/content/sources/github-releases.mdx +39 -0
  221. package/docs/content/sources/index.mdx +91 -0
  222. package/docs/content/sources/meta.ts +16 -0
  223. package/docs/content/sources/notion.mdx +32 -0
  224. package/docs/content/sources/obsidian.mdx +37 -0
  225. package/docs/content/sources/payload.mdx +31 -0
  226. package/docs/content/sources/remote-mdx.mdx +44 -0
  227. package/docs/content/sources/sanity.mdx +29 -0
  228. package/docs/content/sources/strapi.mdx +32 -0
  229. package/docs/content/syntax.mdx +69 -4
  230. package/docs/content/variables.mdx +41 -0
  231. package/docs/discoverability/agent-discovery.mdx +29 -2
  232. package/docs/discoverability/index.mdx +2 -2
  233. package/docs/discoverability/json-api.mdx +2 -0
  234. package/docs/discoverability/llms-txt.mdx +2 -2
  235. package/docs/discoverability/markdown.mdx +3 -1
  236. package/docs/discoverability/mcp.mdx +4 -0
  237. package/docs/discoverability/metadata.mdx +17 -0
  238. package/docs/discoverability/sitemap-and-robots.mdx +1 -1
  239. package/docs/references/api-pages.mdx +74 -0
  240. package/docs/references/asyncapi.mdx +4 -2
  241. package/docs/references/graphql.mdx +14 -2
  242. package/docs/references/meta.ts +1 -1
  243. package/docs/references/openapi.mdx +148 -7
  244. package/docs/references/scalar.mdx +3 -1
  245. package/package.json +43 -8
  246. package/skills/blume/SKILL.md +3 -0
  247. package/skills/blume-migrate/SKILL.md +6 -6
  248. package/skills/blume-migrate/references/docusaurus.md +7 -7
  249. package/skills/blume-migrate/references/fumadocs.md +17 -13
  250. package/skills/blume-migrate/references/mintlify.md +37 -19
  251. package/skills/blume-migrate/references/nextra.md +10 -7
  252. package/skills/blume-migrate/references/starlight.md +11 -8
  253. package/skills/blume-migrate/scripts/mintlify-codemod.mjs +60 -14
  254. package/skills/blume-write-skill/SKILL.md +82 -0
  255. package/src/ai/agent-readability.ts +2 -1
  256. package/src/ai/agent-surface.ts +9 -7
  257. package/src/ai/ai-catalog.ts +6 -3
  258. package/src/ai/ask-context.ts +103 -20
  259. package/src/ai/ask-data.ts +6 -0
  260. package/src/ai/ask-limits.ts +13 -0
  261. package/src/ai/ask-tools.ts +142 -0
  262. package/src/ai/ask.ts +354 -76
  263. package/src/ai/changelog-markdown.ts +11 -6
  264. package/src/ai/component-markdown.ts +76 -1
  265. package/src/ai/cors.ts +14 -3
  266. package/src/ai/index.ts +12 -0
  267. package/src/ai/llms.ts +6 -2
  268. package/src/ai/markdown.ts +2 -1
  269. package/src/ai/mcp/data.ts +6 -0
  270. package/src/ai/mcp/query.ts +14 -6
  271. package/src/ai/openapi-components.ts +36 -6
  272. package/src/ai/relative-links.ts +4 -38
  273. package/src/ai/site-skill.ts +274 -0
  274. package/src/ai/skills.ts +35 -2
  275. package/src/ai/write-skill.ts +97 -0
  276. package/src/astro/generate.ts +210 -66
  277. package/src/astro/index.ts +1 -0
  278. package/src/astro/integration.ts +42 -4
  279. package/src/astro/markdown-negotiation.ts +3 -0
  280. package/src/astro/module-types.ts +9 -1
  281. package/src/astro/pages.ts +23 -0
  282. package/src/astro/templates.ts +406 -108
  283. package/src/astro/variables.ts +31 -0
  284. package/src/audit/catalog.ts +38 -3
  285. package/src/audit/checks/contrast.ts +183 -0
  286. package/src/audit/checks/i18n.ts +2 -3
  287. package/src/audit/checks/indexability.ts +2 -3
  288. package/src/audit/checks/links.ts +19 -13
  289. package/src/audit/checks/network.ts +110 -6
  290. package/src/audit/checks/redirects.ts +6 -4
  291. package/src/audit/checks/robots.ts +58 -2
  292. package/src/audit/checks/sitemap.ts +2 -3
  293. package/src/audit/redirects.ts +46 -15
  294. package/src/audit/run.ts +5 -0
  295. package/src/audit/types.ts +4 -1
  296. package/src/audit/url.ts +14 -5
  297. package/src/blume-modules.d.ts +12 -1
  298. package/src/captcha/client.ts +152 -0
  299. package/src/captcha/hcaptcha.ts +36 -0
  300. package/src/captcha/index.ts +28 -0
  301. package/src/captcha/schema.ts +32 -0
  302. package/src/captcha/turnstile.ts +38 -0
  303. package/src/captcha/verify.ts +71 -0
  304. package/src/cli/args.ts +51 -0
  305. package/src/cli/build-failure.ts +23 -0
  306. package/src/cli/command-meta.ts +5 -0
  307. package/src/cli/commands/audit.ts +10 -1
  308. package/src/cli/commands/dev.ts +18 -2
  309. package/src/cli/commands/doctor.ts +67 -28
  310. package/src/cli/commands/eval.ts +36 -4
  311. package/src/cli/commands/init.ts +6 -4
  312. package/src/cli/commands/skill.ts +96 -0
  313. package/src/cli/commands/validate.ts +10 -1
  314. package/src/cli/index.ts +5 -0
  315. package/src/cli/init/scaffold.ts +3 -3
  316. package/src/cli/prepare.ts +6 -3
  317. package/src/cli/required-secrets.ts +72 -30
  318. package/src/components/BlumePage.astro +2 -0
  319. package/src/components/content/ApiEndpoint.astro +18 -0
  320. package/src/components/content/ApiField.astro +79 -0
  321. package/src/components/content/ApiPlayground.astro +66 -0
  322. package/src/components/content/ApiRail.astro +19 -0
  323. package/src/components/content/Color.astro +1 -0
  324. package/src/components/content/FileTree.astro +1 -0
  325. package/src/components/content/GithubInfo.astro +1 -0
  326. package/src/components/content/Panel.astro +1 -0
  327. package/src/components/content/ParamField.astro +25 -0
  328. package/src/components/content/RequestExample.astro +17 -0
  329. package/src/components/content/ResponseExample.astro +17 -0
  330. package/src/components/content/ResponseField.astro +32 -0
  331. package/src/components/content/Step.astro +1 -0
  332. package/src/components/content/Tile.astro +1 -0
  333. package/src/components/content/Tooltip.astro +1 -0
  334. package/src/components/content/Tree.astro +1 -0
  335. package/src/components/content/TypeTable.astro +4 -1
  336. package/src/components/content/View.astro +21 -0
  337. package/src/components/content/ViewSwitcher.astro +168 -0
  338. package/src/components/content/api-field.ts +103 -0
  339. package/src/components/content/api-page.ts +248 -0
  340. package/src/components/content/mermaid-element.ts +15 -1
  341. package/src/components/content/view-switcher.ts +71 -0
  342. package/src/components/index.ts +2 -0
  343. package/src/components/islands/Assistant.astro +10 -1
  344. package/src/components/islands/assistant.tsx +104 -14
  345. package/src/components/islands/code-question.ts +62 -0
  346. package/src/components/islands/hooks.ts +136 -21
  347. package/src/components/islands/support-link.ts +68 -0
  348. package/src/components/layout/Analytics.astro +11 -2
  349. package/src/components/layout/Banner.astro +14 -10
  350. package/src/components/layout/Breadcrumbs.astro +6 -2
  351. package/src/components/layout/ConsentBanner.astro +62 -0
  352. package/src/components/layout/ConsentHead.astro +38 -0
  353. package/src/components/layout/Directory.astro +94 -0
  354. package/src/components/layout/DrawerSwitchers.astro +51 -0
  355. package/src/components/layout/Empty.astro +2 -2
  356. package/src/components/layout/Header.astro +122 -80
  357. package/src/components/layout/LanguageSwitcher.astro +48 -15
  358. package/src/components/layout/NarrationPlayer.astro +1085 -0
  359. package/src/components/layout/NavSelector.astro +33 -7
  360. package/src/components/layout/PageActions.astro +139 -24
  361. package/src/components/layout/PageFeedback.astro +54 -49
  362. package/src/components/layout/PageLayout.astro +71 -3
  363. package/src/components/layout/ReferenceLayout.astro +17 -6
  364. package/src/components/layout/RelatedPages.astro +42 -0
  365. package/src/components/layout/RootLayout.astro +318 -47
  366. package/src/components/layout/Search.astro +47 -19
  367. package/src/components/layout/SiteFooter.astro +124 -0
  368. package/src/components/layout/SiteMetatags.astro +16 -0
  369. package/src/components/layout/TableOfContents.astro +24 -17
  370. package/src/components/layout/consent/native.ts +79 -0
  371. package/src/components/layout/consent/types.ts +37 -0
  372. package/src/components/layout/footer-links.ts +53 -0
  373. package/src/components/layout/footer-socials.ts +87 -0
  374. package/src/components/layout/head-scripts.ts +6 -1
  375. package/src/components/layout/locale-redirect.ts +107 -0
  376. package/src/components/layout/nav-utils.ts +83 -0
  377. package/src/components/layout/page-feedback.ts +96 -0
  378. package/src/components/layout/search/algolia.ts +10 -1
  379. package/src/components/layout/search/analytics.ts +126 -0
  380. package/src/components/layout/search/endpoint.ts +48 -5
  381. package/src/components/layout/search/flexsearch.ts +17 -5
  382. package/src/components/layout/search/orama-cloud.ts +26 -14
  383. package/src/components/layout/search/pagefind.ts +114 -5
  384. package/src/components/layout/search/types.ts +32 -0
  385. package/src/components/layout/search/typesense.ts +19 -3
  386. package/src/components/openapi/AsyncApiOperation.astro +14 -7
  387. package/src/components/openapi/Authorization.astro +10 -0
  388. package/src/components/openapi/GraphqlOperation.astro +5 -0
  389. package/src/components/openapi/Operation.astro +184 -13
  390. package/src/components/openapi/ParametersTable.astro +8 -4
  391. package/src/components/openapi/Playground.astro +7 -1
  392. package/src/components/openapi/RequestBody.astro +24 -5
  393. package/src/components/openapi/RequestPanel.astro +59 -7
  394. package/src/components/openapi/Responses.astro +7 -4
  395. package/src/components/openapi/SchemaTable.astro +15 -3
  396. package/src/components/openapi/async-snippets.ts +65 -9
  397. package/src/components/openapi/async.ts +123 -29
  398. package/src/components/openapi/code-samples.ts +97 -0
  399. package/src/components/openapi/graphql-helpers.ts +35 -12
  400. package/src/components/openapi/helpers.ts +36 -2
  401. package/src/components/openapi/message-model.ts +54 -11
  402. package/src/components/openapi/message.ts +14 -4
  403. package/src/components/openapi/operation-model.ts +16 -7
  404. package/src/components/openapi/playground-client.ts +24 -5
  405. package/src/components/openapi/playground-schema.ts +1 -1
  406. package/src/components/openapi/schema-tree.ts +44 -14
  407. package/src/components/openapi/security.ts +16 -3
  408. package/src/components/openapi/snippets.ts +873 -10
  409. package/src/components/openapi/validate-json.ts +4 -3
  410. package/src/components/props.ts +6 -0
  411. package/src/consent/client.ts +95 -0
  412. package/src/consent/clients.ts +19 -0
  413. package/src/consent/ethyca.ts +71 -0
  414. package/src/consent/head.ts +24 -0
  415. package/src/consent/index.ts +37 -0
  416. package/src/consent/init.ts +29 -0
  417. package/src/consent/native.ts +36 -0
  418. package/src/consent/osano.ts +59 -0
  419. package/src/consent/schema.ts +30 -0
  420. package/src/core/builtin-tags.ts +5 -0
  421. package/src/core/changelog-index.ts +31 -1
  422. package/src/core/client-address.ts +19 -0
  423. package/src/core/component-diagnostics.ts +53 -0
  424. package/src/core/component-overrides.ts +32 -0
  425. package/src/core/config-input.ts +225 -23
  426. package/src/core/data.ts +46 -4
  427. package/src/core/diagnostics.ts +8 -2
  428. package/src/core/footer.ts +26 -0
  429. package/src/core/graph.ts +39 -36
  430. package/src/core/i18n-ui.ts +84 -2
  431. package/src/core/includes.ts +21 -4
  432. package/src/core/links.ts +63 -19
  433. package/src/core/localizable.ts +26 -0
  434. package/src/core/navigation.ts +43 -12
  435. package/src/core/page-modes.ts +56 -0
  436. package/src/core/probe.ts +26 -6
  437. package/src/core/project-graph.ts +163 -16
  438. package/src/core/redirect-patterns.ts +520 -0
  439. package/src/core/related.ts +131 -0
  440. package/src/core/schema.ts +339 -66
  441. package/src/core/sources/cache.ts +6 -0
  442. package/src/core/sources/contentful-rich-text.ts +29 -6
  443. package/src/core/sources/contentful.ts +135 -25
  444. package/src/core/sources/filesystem.ts +26 -4
  445. package/src/core/sources/github-releases.ts +12 -1
  446. package/src/core/sources/lexical.ts +15 -10
  447. package/src/core/sources/mdx-remote.ts +62 -10
  448. package/src/core/sources/normalize.ts +228 -41
  449. package/src/core/sources/notion.ts +7 -1
  450. package/src/core/sources/obsidian.ts +16 -3
  451. package/src/core/sources/payload.ts +38 -15
  452. package/src/core/sources/read.ts +8 -3
  453. package/src/core/sources/remote.ts +72 -15
  454. package/src/core/sources/resolve.ts +11 -1
  455. package/src/core/sources/sanity.ts +43 -25
  456. package/src/core/sources/strapi.ts +2 -2
  457. package/src/core/types.ts +19 -1
  458. package/src/core/ui-packs/ar.ts +44 -0
  459. package/src/core/ui-packs/bg.ts +46 -0
  460. package/src/core/ui-packs/bn.ts +44 -0
  461. package/src/core/ui-packs/ca.ts +47 -0
  462. package/src/core/ui-packs/cs.ts +45 -0
  463. package/src/core/ui-packs/da.ts +46 -0
  464. package/src/core/ui-packs/de.ts +47 -0
  465. package/src/core/ui-packs/el.ts +46 -0
  466. package/src/core/ui-packs/es.ts +47 -0
  467. package/src/core/ui-packs/fa.ts +46 -0
  468. package/src/core/ui-packs/fi.ts +46 -0
  469. package/src/core/ui-packs/fr.ts +47 -0
  470. package/src/core/ui-packs/he.ts +44 -0
  471. package/src/core/ui-packs/hi.ts +45 -0
  472. package/src/core/ui-packs/hr.ts +46 -0
  473. package/src/core/ui-packs/hu.ts +45 -0
  474. package/src/core/ui-packs/id.ts +46 -0
  475. package/src/core/ui-packs/it.ts +46 -0
  476. package/src/core/ui-packs/ja.ts +46 -0
  477. package/src/core/ui-packs/ko.ts +45 -0
  478. package/src/core/ui-packs/nl.ts +47 -0
  479. package/src/core/ui-packs/no.ts +45 -0
  480. package/src/core/ui-packs/pl.ts +46 -0
  481. package/src/core/ui-packs/pt-br.ts +47 -0
  482. package/src/core/ui-packs/pt.ts +46 -0
  483. package/src/core/ui-packs/ro.ts +46 -0
  484. package/src/core/ui-packs/ru.ts +46 -0
  485. package/src/core/ui-packs/sk.ts +45 -0
  486. package/src/core/ui-packs/sr.ts +46 -0
  487. package/src/core/ui-packs/sv.ts +45 -0
  488. package/src/core/ui-packs/th.ts +44 -0
  489. package/src/core/ui-packs/tr.ts +46 -0
  490. package/src/core/ui-packs/uk.ts +46 -0
  491. package/src/core/ui-packs/vi.ts +46 -0
  492. package/src/core/ui-packs/zh-tw.ts +44 -0
  493. package/src/core/ui-packs/zh.ts +44 -0
  494. package/src/core/variables.ts +103 -0
  495. package/src/deploy/adapter-output.ts +7 -2
  496. package/src/deploy/adapters/cloudflare.ts +7 -5
  497. package/src/deploy/adapters/node.ts +47 -9
  498. package/src/deploy/adapters/types.ts +7 -3
  499. package/src/deploy/artifacts.ts +79 -9
  500. package/src/deploy/cloudflare-negotiation.ts +35 -5
  501. package/src/deploy/headers.ts +2 -1
  502. package/src/deploy/node-headers.ts +63 -9
  503. package/src/deploy/platforms/cloudflare.ts +45 -9
  504. package/src/deploy/platforms/netlify.ts +35 -2
  505. package/src/deploy/platforms/node.ts +12 -2
  506. package/src/deploy/platforms/types.ts +21 -7
  507. package/src/deploy/platforms/vercel.ts +149 -17
  508. package/src/deploy/redirects.ts +151 -24
  509. package/src/deploy/rss.ts +3 -1
  510. package/src/deploy/sitemap.ts +39 -5
  511. package/src/deploy/vercel-negotiation.ts +326 -102
  512. package/src/eval/agents.ts +18 -1
  513. package/src/eval/prompts.ts +6 -3
  514. package/src/eval/report.ts +34 -13
  515. package/src/eval/run.ts +5 -0
  516. package/src/markdown/api-rail.ts +259 -0
  517. package/src/markdown/code-title.ts +35 -9
  518. package/src/markdown/features.ts +3 -2
  519. package/src/markdown/fence-meta.ts +2 -0
  520. package/src/markdown/include.ts +11 -1
  521. package/src/markdown/index.ts +28 -5
  522. package/src/markdown/math.ts +4 -4
  523. package/src/markdown/relative-links.ts +3 -2
  524. package/src/markdown/variables.ts +56 -0
  525. package/src/markdown/views.ts +82 -0
  526. package/src/narration/build.ts +412 -0
  527. package/src/narration/player.ts +249 -0
  528. package/src/narration/provider.ts +29 -0
  529. package/src/narration/script.ts +592 -0
  530. package/src/og/card.ts +7 -6
  531. package/src/openapi/checks.ts +273 -0
  532. package/src/openapi/graphql-build.ts +21 -0
  533. package/src/openapi/graphql.ts +11 -0
  534. package/src/openapi/model.ts +72 -27
  535. package/src/openapi/overlay.ts +256 -0
  536. package/src/openapi/parse.ts +182 -4
  537. package/src/openapi/references.ts +64 -8
  538. package/src/openapi/render-mdx.ts +5 -1
  539. package/src/openapi/scalar.ts +40 -6
  540. package/src/openapi/source.ts +40 -15
  541. package/src/ratelimit/cloudflare.ts +54 -0
  542. package/src/ratelimit/index.ts +30 -0
  543. package/src/ratelimit/memory.ts +45 -0
  544. package/src/ratelimit/runtime.ts +247 -0
  545. package/src/ratelimit/schema.ts +28 -0
  546. package/src/ratelimit/upstash.ts +60 -0
  547. package/src/ratelimit/wrangler.ts +70 -0
  548. package/src/reference/asyncapi.ts +2 -2
  549. package/src/reference/graphql.ts +20 -3
  550. package/src/reference/index.ts +1 -0
  551. package/src/reference/openapi.ts +21 -7
  552. package/src/reference/options.ts +80 -8
  553. package/src/reference/scalar.ts +33 -8
  554. package/src/registry/eject.ts +23 -5
  555. package/src/registry/registry.ts +7 -0
  556. package/src/search/adapters/locale-scope.ts +30 -0
  557. package/src/search/adapters/typesense.ts +14 -4
  558. package/src/search/build.ts +21 -3
  559. package/src/search/documents.ts +26 -2
  560. package/src/search/orama-index.ts +191 -34
  561. package/src/search/pagefind-ranking.ts +39 -0
  562. package/src/search/source-pages.ts +29 -0
  563. package/src/search/sync/algolia.ts +111 -10
  564. package/src/search/sync/index.ts +52 -13
  565. package/src/search/sync/orama-cloud.ts +3 -0
  566. package/src/search/sync/typesense.ts +76 -26
  567. package/src/seo/metatags.ts +67 -0
  568. package/src/sources/contentful.ts +12 -0
  569. package/src/sources/github-releases.ts +7 -1
  570. package/src/sources/shared.ts +3 -0
  571. package/src/theme/chrome-icons.ts +1 -0
  572. package/src/theme/color.ts +500 -0
  573. package/src/theme/entry.ts +85 -6
  574. package/src/theme/palette.ts +121 -45
  575. package/src/translate/prompts.ts +1 -0
  576. package/src/translate/validate.ts +31 -3
  577. package/src/upgrade/upgrade.ts +4 -171
  578. package/dist/cli/chunk-1d7ve1dm.js.map +0 -12
  579. package/dist/cli/chunk-2hsdwb9n.js.map +0 -10
  580. package/dist/cli/chunk-35d4wj9f.js.map +0 -15
  581. package/dist/cli/chunk-364znk6q.js +0 -75
  582. package/dist/cli/chunk-364znk6q.js.map +0 -11
  583. package/dist/cli/chunk-5m5nmvyq.js.map +0 -13
  584. package/dist/cli/chunk-8ktnccpt.js.map +0 -10
  585. package/dist/cli/chunk-9t7a85s3.js.map +0 -10
  586. package/dist/cli/chunk-b07cmahc.js +0 -145
  587. package/dist/cli/chunk-b07cmahc.js.map +0 -11
  588. package/dist/cli/chunk-c8chx29p.js.map +0 -36
  589. package/dist/cli/chunk-e04dxsz1.js +0 -39
  590. package/dist/cli/chunk-e04dxsz1.js.map +0 -10
  591. package/dist/cli/chunk-j85vccga.js.map +0 -36
  592. package/dist/cli/chunk-p3v96n38.js.map +0 -10
  593. package/dist/cli/chunk-pehfxfta.js.map +0 -10
  594. package/dist/cli/chunk-pv29h0wf.js.map +0 -190
  595. package/dist/cli/chunk-q58y5e6a.js.map +0 -10
  596. package/dist/cli/chunk-vg9r4eb9.js.map +0 -14
  597. package/dist/cli/chunk-wrr3j9w9.js.map +0 -35
  598. package/dist/cli/chunk-yfyb25rh.js.map +0 -14
  599. package/docs/content/sources.mdx +0 -334
  600. /package/dist/cli/{chunk-tkacnehg.js.map → chunk-3v3ffbrj.js.map} +0 -0
  601. /package/dist/cli/{chunk-r9rcc4w7.js.map → chunk-8y2kmjv8.js.map} +0 -0
  602. /package/dist/cli/{chunk-2eytanqx.js.map → chunk-c887fj0s.js.map} +0 -0
  603. /package/dist/cli/{chunk-akbpwfxc.js.map → chunk-dzwehpy7.js.map} +0 -0
  604. /package/dist/cli/{chunk-2z928egk.js.map → chunk-ft9vfys6.js.map} +0 -0
  605. /package/dist/cli/{chunk-a58773jm.js.map → chunk-kma9f6sf.js.map} +0 -0
  606. /package/dist/cli/{chunk-crgn1q09.js.map → chunk-sds923fa.js.map} +0 -0
  607. /package/dist/cli/{chunk-acanzt5p.js.map → chunk-tdqhyxfz.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";
@@ -14,6 +15,7 @@ import { occupySlug, parseHeadingMarkers } from "../heading-markers.ts";
14
15
  import { localePlacement, localizeRoute } from "../i18n.ts";
15
16
  import { titleWord } from "../navigation.ts";
16
17
  import { stripOrderingPrefix } from "../ordering-prefix.ts";
18
+ import { relatedPageLinks } from "../related.ts";
17
19
  import { pageMetaSchema } from "../schema.ts";
18
20
  import type {
19
21
  FrontmatterExtend,
@@ -21,7 +23,13 @@ import type {
21
23
  ResolvedI18nConfig,
22
24
  } from "../schema.ts";
23
25
  import { trimChar } from "../trim.ts";
24
- 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";
25
33
  import { detectVersionRef, versionizeRoute } from "../versions.ts";
26
34
  import type { NormalizeContext, SourceEntry } from "./types.ts";
27
35
 
@@ -126,6 +134,29 @@ export const unloadablePathDiagnostic = (
126
134
  }
127
135
  : undefined;
128
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
+
129
160
  /**
130
161
  * Fold one raw path part into the accumulating route segments/groups.
131
162
  * `ordered` parts are file or folder names, whose ordering prefix is dropped.
@@ -234,9 +265,11 @@ const PROMPT_CLOSE = /<\/Prompt>/u;
234
265
  // the same marker — and the only spelling that survives the MDX parser, where
235
266
  // a bare `{…}` is a JSX expression and `#id` is not a valid one (`Could not
236
267
  // parse expression with acorn`). Further bracket markers may follow the brace
237
- // (`{#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`.
238
271
  const BARE_CURLY_MARKER =
239
- /(?<!\\)\{#(?<id>[^\s}]+)\}(?:\s*\[(?:#[^\s\]]+|!?toc)\])*\s*$/u;
272
+ /(?<!\\)(?<marker>\{:?\s*#(?<id>[^\s}]+)\s*\})(?:\s*\[(?:#[^\s\]]+|!?toc)\])*\s*$/u;
240
273
 
241
274
  // A raw HTML element carrying an `id` — `<a id="…">`, `<section id='…'>`,
242
275
  // the unquoted `<a id=plain>`, or the JSX spelling `<div id={"…"}>` — whose
@@ -258,6 +291,8 @@ export interface CurlyMarker {
258
291
  id: string;
259
292
  /** 1-based line of the heading (a setext heading's first text line) in the body. */
260
293
  line: number;
294
+ /** The braces as written (`{#id}`, `{ #id }`, `{: #id }`). */
295
+ marker: string;
261
296
  }
262
297
 
263
298
  /** Scanner state: the open fence plus the paragraph lines accumulated so far. */
@@ -627,9 +662,9 @@ const noteCurlyMarker = (
627
662
  line: number,
628
663
  state: HeadingScanState
629
664
  ): void => {
630
- const id = text.match(BARE_CURLY_MARKER)?.groups?.id;
631
- if (id !== undefined) {
632
- 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 });
633
668
  }
634
669
  };
635
670
 
@@ -889,6 +924,42 @@ export const targetOffsetIn = (
889
924
  title: string | undefined
890
925
  ): number => matched.length - 1 - (title?.length ?? 0) - target.length;
891
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
+
892
963
  /**
893
964
  * Extract link targets from a markdown body for later validation, recording the
894
965
  * 1-based line/column of each target. Skips fenced code blocks and inline code.
@@ -953,18 +1024,32 @@ const scanLinkLine = (
953
1024
  });
954
1025
  }
955
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);
956
1032
  return next;
957
1033
  };
958
1034
 
959
1035
  /**
960
- * A component's string `href` (`<Card href="./install" />`): the attribute
961
- * may sit on a later line than the tag name, where a formatter wraps a long
962
- * element, so the gap between them spans lines. An expression-valued
963
- * `href={…}` isn't a literal target, and a lowercase tag is raw HTML in a
964
- * `.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.
965
1041
  */
966
- const COMPONENT_HREF =
967
- /<[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;
968
1053
 
969
1054
  /** The body with fenced blocks and inline code blanked, shape preserved. */
970
1055
  const maskCode = (lines: readonly string[]): string => {
@@ -980,24 +1065,83 @@ const maskCode = (lines: readonly string[]): string => {
980
1065
  return masked.join("\n");
981
1066
  };
982
1067
 
983
- /** Every component `href` target in `body`, with its 1-based position. */
984
- 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 = (
985
1113
  lines: readonly string[],
986
1114
  lineOffset: number
987
1115
  ): PageLink[] => {
988
1116
  const links: PageLink[] = [];
989
1117
  const text = maskCode(lines);
990
- for (const match of text.matchAll(COMPONENT_HREF)) {
1118
+ const lineStarts = lineStartsOf(lines);
1119
+ for (const match of text.matchAll(ELEMENT_HREF)) {
991
1120
  const target = match.groups?.double ?? match.groups?.single ?? "";
992
1121
  // The value ends one character (its closing quote) before the match does.
993
1122
  const at = match.index + match[0].length - target.length - 1;
994
- const before = text.slice(0, at);
995
- const lineStart = before.lastIndexOf("\n") + 1;
996
- links.push({
997
- column: at - lineStart + 1,
998
- line: lineOffset + before.split("\n").length,
1123
+ const link: PageLink = {
1124
+ ...positionIn(lineStarts, at, lineOffset),
999
1125
  target,
1000
- });
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);
1001
1145
  }
1002
1146
  return links;
1003
1147
  };
@@ -1013,9 +1157,37 @@ export const extractLinks = (body: string, lineOffset = 0): PageLink[] => {
1013
1157
  fence = scanLinkLine(line, lineNumber, fence, links);
1014
1158
  }
1015
1159
 
1016
- return body.includes("href=")
1017
- ? [...links, ...componentHrefs(lines, lineOffset)]
1018
- : 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
+ });
1019
1191
  };
1020
1192
 
1021
1193
  // Double-quoted strings hold JSX attribute values and JSON in `{...}` props; a
@@ -1081,23 +1253,29 @@ export const strippedLineOffset = (
1081
1253
  const entryLineOffset = (entry: SourceEntry): number =>
1082
1254
  entry.bodyLineOffset ?? strippedLineOffset(entry.raw, entry.body.text);
1083
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
+
1084
1262
  /**
1085
1263
  * Map links extracted from include-expanded text back to the file and raw
1086
1264
  * line each expanded line came from, so a broken link inside a partial is
1087
1265
  * reported against the partial. Links whose origin is the page's own source
1088
1266
  * carry no `file` override (origins already hold raw-file lines).
1089
1267
  */
1090
- const remapExpandedLinks = (
1091
- links: PageLink[],
1268
+ const remapExpandedLinks = <T extends Located>(
1269
+ links: T[],
1092
1270
  origins: { file: string; line: number }[],
1093
1271
  sourcePath: string | undefined
1094
- ): PageLink[] =>
1272
+ ): T[] =>
1095
1273
  links.map((link) => {
1096
1274
  const origin = origins[link.line - 1];
1097
1275
  if (!origin) {
1098
1276
  return link;
1099
1277
  }
1100
- const remapped: PageLink = { ...link, line: origin.line };
1278
+ const remapped: T = { ...link, line: origin.line };
1101
1279
  if (origin.file !== sourcePath) {
1102
1280
  remapped.file = origin.file;
1103
1281
  }
@@ -1111,22 +1289,26 @@ const entryIncludes = (entry: SourceEntry): string[] | undefined =>
1111
1289
  : undefined;
1112
1290
 
1113
1291
  /**
1114
- * Extract an entry's links for validation. When the scan expanded includes,
1115
- * extraction runs over the expanded text (origins already hold raw-file
1116
- * lines); otherwise over the stripped body, shifted by the stripped front
1117
- * 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.
1118
1296
  */
1119
- const entryLinks = (entry: SourceEntry): PageLink[] =>
1297
+ const entryLinks = <T extends Located>(
1298
+ entry: SourceEntry,
1299
+ extract: (body: string, lineOffset?: number) => T[]
1300
+ ): T[] =>
1120
1301
  entry.expanded
1121
1302
  ? remapExpandedLinks(
1122
- extractLinks(entry.expanded.text),
1303
+ extract(entry.expanded.text),
1123
1304
  entry.expanded.origins,
1124
1305
  entry.sourcePath
1125
1306
  )
1126
- : extractLinks(entry.body.text, entryLineOffset(entry));
1307
+ : extract(entry.body.text, entryLineOffset(entry));
1127
1308
 
1128
1309
  /**
1129
- * 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
1130
1312
  * reads a bare `{…}` as a JSX expression, so the page fails to compile —
1131
1313
  * reported here, at the marker's source line, instead of as a raw acorn error
1132
1314
  * at render time. An included `.md` partial is spliced into the including
@@ -1139,7 +1321,7 @@ const curlyMarkerDiagnostics = (
1139
1321
  markers: CurlyMarker[],
1140
1322
  sourceName: string
1141
1323
  ): Diagnostic[] =>
1142
- markers.map(({ id, line }) => {
1324
+ markers.map(({ id, line, marker }) => {
1143
1325
  const origin = entry.expanded?.origins[line - 1];
1144
1326
  const page = entry.sourcePath ?? `${sourceName}:${entry.ref}`;
1145
1327
  const inPartial = origin !== undefined && origin.file !== entry.sourcePath;
@@ -1148,8 +1330,8 @@ const curlyMarkerDiagnostics = (
1148
1330
  file: origin?.file ?? page,
1149
1331
  line: origin?.line ?? line + entryLineOffset(entry),
1150
1332
  message: inPartial
1151
- ? `\`{#${id}}\` is a JSX expression once this partial is included in ${page} (.mdx), so that page fails to compile.`
1152
- : `\`{#${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.`,
1153
1335
  severity: "error",
1154
1336
  suggestion: `Write \`[#${id}]\` or escape it as \`\\{#${id}\\}\` — both pin the same anchor in .md and .mdx.`,
1155
1337
  };
@@ -1548,13 +1730,18 @@ export const normalizeEntry = (
1548
1730
  description: meta.description,
1549
1731
  editUrl: entry.editUrl,
1550
1732
  entryId: staged ? `${ctx.source.name}/${entry.ref}` : undefined,
1733
+ examplesUsed:
1734
+ format === "mdx" ? entryLinks(entry, extractExampleUses) : undefined,
1551
1735
  format,
1552
1736
  groups,
1553
1737
  headings,
1554
1738
  id: `${ctx.source.name}:${entry.ref}`,
1555
1739
  includes: entryIncludes(entry),
1556
1740
  lastModified: meta.lastModified ?? entry.lastModified,
1557
- links: entryLinks(entry),
1741
+ links: [
1742
+ ...entryLinks(entry, extractLinks),
1743
+ ...relatedPageLinks(meta.related),
1744
+ ],
1558
1745
  meta,
1559
1746
  monolingual: ctx.source.monolingual,
1560
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;
@@ -2,10 +2,14 @@ import { readFile } from "node:fs/promises";
2
2
 
3
3
  import { expandIncludes, hasIncludeStatements } from "../includes.ts";
4
4
  import type { PageRecord } from "../types.ts";
5
+ import { substituteVariablesInBody } from "../variables.ts";
6
+ import type { ContentVariables } from "../variables.ts";
5
7
  import type { ContentSource } from "./types.ts";
6
8
 
7
9
  /** The subset of a scanned project the entry reader needs. */
8
10
  export interface EntryReadContext {
11
+ /** The site's content variables, substituted into what's read. */
12
+ config?: { variables?: ContentVariables };
9
13
  /** The instantiated sources, keyed for `read()` lookups. */
10
14
  sources?: ContentSource[];
11
15
  }
@@ -48,9 +52,10 @@ export const readExpandedEntryText = async (
48
52
  ctx: EntryReadContext,
49
53
  page: PageRecord
50
54
  ): Promise<string> => {
55
+ const variables = ctx.config?.variables;
51
56
  const raw = await readEntryText(ctx, page);
52
57
  if (!(page.sourcePath && hasIncludeStatements(raw))) {
53
- return raw;
58
+ return substituteVariablesInBody(raw, variables);
54
59
  }
55
60
  const source = ctx.sources?.find(
56
61
  (candidate) => candidate.name === page.source.name
@@ -61,11 +66,11 @@ export const readExpandedEntryText = async (
61
66
  // here would splice text the rest of the pipeline never validated, with no
62
67
  // content-root bound on resolution.
63
68
  if (!source || source.staged || !source.contentRoot) {
64
- return raw;
69
+ return substituteVariablesInBody(raw, variables);
65
70
  }
66
71
  const expansion = await expandIncludes(raw, {
67
72
  contentRoot: source.contentRoot,
68
73
  sourcePath: page.sourcePath,
69
74
  });
70
- return expansion.text;
75
+ return substituteVariablesInBody(expansion.text, variables);
71
76
  };