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,7 +1,8 @@
1
1
  /**
2
2
  * Contentful rich text → Markdown. Covers the block, inline, and mark types
3
3
  * the Rich Text editor emits; embedded entries fall through to a serializer
4
- * keyed by content type or are noted in a comment. Links to assets and
4
+ * keyed by content type or are noted in a comment, and a link to an entry
5
+ * points at its page when the source says it has one. Links to assets and
5
6
  * entries arrive unresolved from the Delivery API (`data.target` is a
6
7
  * `sys` link), so the source hands in resolvers over the response's
7
8
  * `includes`; a document the SDK already resolved (with `fields` inline)
@@ -25,14 +26,22 @@ import {
25
26
  writesMdx,
26
27
  } from "./lower.ts";
27
28
 
28
- /** An asset's file as a Markdown image needs it. */
29
+ /** An asset's file as a Markdown image or link needs it. */
29
30
  export interface ContentfulAsset {
31
+ /** The file's MIME type; an asset that isn't an image renders as a link. */
32
+ contentType?: string;
30
33
  description?: string;
34
+ fileName?: string;
31
35
  title?: string;
32
36
  url: string;
33
37
  }
34
38
 
35
39
  export interface ContentfulRichTextOptions {
40
+ /**
41
+ * The route of a linked entry's page; an entry hyperlink to an entry with
42
+ * none keeps only its text.
43
+ */
44
+ entryHref?: (entry: JsonObject) => string | undefined;
36
45
  /** Resolve an asset link (`data.target.sys.id`) to its file. */
37
46
  resolveAsset?: (id: string) => ContentfulAsset | null;
38
47
  /** Resolve an entry link to the entry (`sys` plus `fields`). */
@@ -55,7 +64,9 @@ export const assetFromEntry = (asset: JsonObject): ContentfulAsset | null => {
55
64
  return null;
56
65
  }
57
66
  return {
67
+ contentType: asString(getPath(asset, "fields.file.contentType")),
58
68
  description: asString(getPath(asset, "fields.description")),
69
+ fileName: asString(getPath(asset, "fields.file.fileName")),
59
70
  title: asString(getPath(asset, "fields.title")),
60
71
  url: absoluteUrl(url),
61
72
  };
@@ -166,11 +177,16 @@ const inlineNodeParts = (
166
177
  linkedAsset(node, options)?.url
167
178
  );
168
179
  }
180
+ case "entry-hyperlink": {
181
+ const entry = linkedTarget(node, options.resolveEntry);
182
+ const href = entry ? options.entryHref?.(entry) : undefined;
183
+ return linkParts(inlineParts(children(node), options), href);
184
+ }
169
185
  case "embedded-entry-inline": {
170
186
  return [{ markdown: embeddedEntry(node, options) }];
171
187
  }
172
188
  default: {
173
- // entry-hyperlink (no route to point at) and anything unknown: the label.
189
+ // Anything unknown: the label.
174
190
  return inlineParts(children(node), options);
175
191
  }
176
192
  }
@@ -264,11 +280,18 @@ const renderBlock = (
264
280
  }
265
281
  case "embedded-asset-block": {
266
282
  const asset = linkedAsset(node, options);
283
+ if (!asset) {
284
+ return "";
285
+ }
286
+ // Any file can be embedded: one that isn't an image (a PDF, a video)
287
+ // would render as a broken image, so it's a link to the file.
288
+ if (asset.contentType && !asset.contentType.startsWith("image/")) {
289
+ const label = asset.title ?? asset.fileName ?? asset.url;
290
+ return renderInline(linkParts([{ marks: {}, text: label }], asset.url));
291
+ }
267
292
  // Contentful's own rich-text renderer uses the description as alt text
268
293
  // and the title only as a fallback.
269
- return asset
270
- ? image(asset.description ?? asset.title ?? "", asset.url)
271
- : "";
294
+ return image(asset.description ?? asset.title ?? "", asset.url);
272
295
  }
273
296
  case "embedded-entry-block": {
274
297
  return embeddedEntry(node, options);
@@ -1,4 +1,6 @@
1
1
  import { BlumeError } from "../diagnostics.ts";
2
+ import type { ResolvedI18nConfig, ResolvedVersionsConfig } from "../schema.ts";
3
+ import type { Diagnostic } from "../types.ts";
2
4
  import {
3
5
  assetFromEntry,
4
6
  contentfulRichTextToMarkdown,
@@ -13,10 +15,13 @@ import {
13
15
  objectsIn,
14
16
  } from "./json.ts";
15
17
  import { writesMdx } from "./lower.ts";
16
- import type { RemoteFieldMap, RestClient } from "./remote.ts";
18
+ import { localizedRoute, resolveEntryRoute } from "./normalize.ts";
19
+ import type { RemoteFieldMap, RemoteFields, RestClient } from "./remote.ts";
17
20
  import {
18
21
  documentEntry,
22
+ documentSlug,
19
23
  fetchJson,
24
+ missingSecretError,
20
25
  queryString,
21
26
  remoteSource,
22
27
  } from "./remote.ts";
@@ -29,10 +34,24 @@ export interface ContentfulSourceOptions {
29
34
  space: string;
30
35
  /** Environment id; default `master`. */
31
36
  environment?: string;
37
+ /**
38
+ * Delivery API host name; default `cdn.contentful.com`. A space with EU
39
+ * data residency reads from `cdn.eu.contentful.com`.
40
+ */
41
+ host?: string;
42
+ /**
43
+ * Preview API host name for `--preview`; default `preview.contentful.com`
44
+ * (`preview.eu.contentful.com` for EU data residency).
45
+ */
46
+ previewHost?: string;
32
47
  /** The content type id whose entries become pages. */
33
48
  contentType: string;
34
49
  /** Locale code to fetch; omit for the space's default locale. */
35
50
  locale?: string;
51
+ /** The project's i18n config, which places an entry's route by its slug. */
52
+ i18n?: ResolvedI18nConfig;
53
+ /** The project's versions config, which places an entry's route by its slug. */
54
+ versions?: ResolvedVersionsConfig;
36
55
  /**
37
56
  * Field ids mapping an entry onto Blume meta + body. Paths resolve against
38
57
  * the entry's `fields`, with `sys` reachable as `sys.<key>`. Defaults:
@@ -53,11 +72,11 @@ export interface ContentfulSourceOptions {
53
72
  fetchImpl?: typeof fetch;
54
73
  }
55
74
 
56
- const DELIVERY_HOST = "https://cdn.contentful.com";
57
- const PREVIEW_HOST = "https://preview.contentful.com";
75
+ const DELIVERY_HOST = "cdn.contentful.com";
76
+ const PREVIEW_HOST = "preview.contentful.com";
58
77
  const PAGE_SIZE = 100;
59
78
 
60
- const DEFAULT_FIELDS: Required<RemoteFieldMap> = {
79
+ const DEFAULT_FIELDS: RemoteFields = {
61
80
  body: "body",
62
81
  description: "description",
63
82
  lastModified: "sys.updatedAt",
@@ -71,11 +90,34 @@ const byId = (objects: JsonObject[]): Map<string, JsonObject> =>
71
90
  objects.map((object) => [asString(getPath(object, "sys.id")) ?? "", object])
72
91
  );
73
92
 
93
+ /** One page of the entries response: its items and what their links reach. */
94
+ interface EntriesPage {
95
+ assets: Map<string, JsonObject>;
96
+ items: JsonObject[];
97
+ linked: Map<string, JsonObject>;
98
+ }
99
+
100
+ /** An entry's id, and the entry as its field paths read it. */
101
+ interface EntryView {
102
+ id: string;
103
+ /** The entry's fields, with `sys` beside them. */
104
+ view: JsonObject;
105
+ }
106
+
107
+ const entryView = (item: JsonObject): EntryView => {
108
+ const sys = asObject(item.sys) ?? {};
109
+ return {
110
+ id: asString(sys.id) ?? "",
111
+ view: { ...asObject(item.fields), sys },
112
+ };
113
+ };
114
+
74
115
  /**
75
116
  * Contentful content source. Pages through the Delivery API (the Preview API
76
117
  * under `--preview`), maps each entry's fields to frontmatter, and lowers its
77
118
  * rich text body to Markdown with the page's `includes` resolving asset and
78
- * entry links.
119
+ * entry links. An entry hyperlink to another of the source's entries links
120
+ * to that entry's page.
79
121
  */
80
122
  export const contentfulSource = (
81
123
  options: ContentfulSourceOptions,
@@ -83,33 +125,78 @@ export const contentfulSource = (
83
125
  ): ContentSource => {
84
126
  const fields = { ...DEFAULT_FIELDS, ...options.fields };
85
127
 
128
+ // The route an entry's page publishes at: the ref `documentEntry` names it
129
+ // by, through the resolver `normalizeEntry` uses, so an entry hyperlink
130
+ // lands on the page that's built. A slug places a page in one locale, and
131
+ // `.md` stands in for either extension, which the route drops.
132
+ const routeOf = (view: JsonObject, id: string): string => {
133
+ const route = resolveEntryRoute(
134
+ { ref: `${documentSlug(view, fields.slug, id)}.md` },
135
+ ".md",
136
+ undefined,
137
+ { i18n: options.i18n, prefix: options.prefix, versions: options.versions }
138
+ );
139
+ return localizedRoute(
140
+ route.logicalRoute,
141
+ route.locales[0] ?? "",
142
+ options.i18n
143
+ );
144
+ };
145
+
86
146
  const toEntry = (
87
147
  item: JsonObject,
88
- assets: Map<string, JsonObject>,
89
- linked: Map<string, JsonObject>
148
+ { assets, linked }: EntriesPage,
149
+ routes: Map<string, string>,
150
+ warn: (diagnostic: Diagnostic) => void
90
151
  ): SourceEntry => {
91
- const sys = asObject(item.sys) ?? {};
92
- const id = asString(sys.id) ?? "";
93
- // Field paths resolve against the entry's fields, with `sys` beside them.
94
- const view: JsonObject = { ...asObject(item.fields), sys };
95
- return documentEntry(
152
+ const { id, view } = entryView(item);
153
+ // Links the response left out of `includes`: the API delivers no
154
+ // unpublished, archived, or deleted target, and the page renders without
155
+ // it.
156
+ const missing = new Set<string>();
157
+ const entry = documentEntry(
96
158
  view,
97
159
  fields,
98
160
  id,
99
161
  (body) =>
100
162
  isJsonObject(body)
101
163
  ? contentfulRichTextToMarkdown(body, {
164
+ // Only an entry the source itself turns into a page has a
165
+ // route: one of another content type, or one `params` filters
166
+ // out, keeps its text.
167
+ entryHref: (target) =>
168
+ routes.get(asString(getPath(target, "sys.id")) ?? ""),
102
169
  resolveAsset: (assetId) => {
103
170
  const asset = assets.get(assetId);
104
- return asset ? assetFromEntry(asset) : null;
171
+ if (!asset) {
172
+ missing.add(`asset "${assetId}"`);
173
+ return null;
174
+ }
175
+ return assetFromEntry(asset);
176
+ },
177
+ resolveEntry: (entryId) => {
178
+ const target = linked.get(entryId);
179
+ if (!target) {
180
+ missing.add(`entry "${entryId}"`);
181
+ }
182
+ return target ?? null;
105
183
  },
106
- resolveEntry: (entryId) => linked.get(entryId) ?? null,
107
184
  serializers: options.serializers,
108
185
  })
109
186
  : "",
110
187
  false,
111
188
  writesMdx(options.serializers)
112
189
  );
190
+ for (const link of missing) {
191
+ warn({
192
+ code: "BLUME_SOURCE_UNRESOLVED_LINK",
193
+ message: `Source "${options.name}": "${entry.ref}" links to Contentful ${link}, which the response didn't include, so the page renders without it.`,
194
+ severity: "warning",
195
+ suggestion:
196
+ "Publish the linked asset or entry, or remove the link: the Delivery API returns only published content.",
197
+ });
198
+ }
199
+ return entry;
113
200
  };
114
201
 
115
202
  const preview = ctx?.preview ?? false;
@@ -131,17 +218,26 @@ export const contentfulSource = (
131
218
  }
132
219
  };
133
220
 
134
- const fetchEntries = async (): Promise<SourceEntry[]> => {
221
+ const fetchEntries = async (
222
+ warn: (diagnostic: Diagnostic) => void
223
+ ): Promise<SourceEntry[]> => {
135
224
  const token = preview
136
225
  ? previewToken()
137
226
  : (options.token ?? process.env.CONTENTFUL_ACCESS_TOKEN);
227
+ // The Delivery API answers nothing without a token (`assertConfigured`
228
+ // already required the preview one).
229
+ if (!token) {
230
+ throw missingSecretError(options.name, "CONTENTFUL_ACCESS_TOKEN");
231
+ }
138
232
  const client: RestClient = {
139
233
  fetchImpl: options.fetchImpl,
140
- headers: token ? { authorization: `Bearer ${token}` } : {},
234
+ headers: { authorization: `Bearer ${token}` },
141
235
  };
142
- const host = preview ? PREVIEW_HOST : DELIVERY_HOST;
143
- const base = `${host}/spaces/${options.space}/environments/${options.environment ?? "master"}/entries`;
144
- const entries: SourceEntry[] = [];
236
+ const host = preview
237
+ ? (options.previewHost ?? PREVIEW_HOST)
238
+ : (options.host ?? DELIVERY_HOST);
239
+ const base = `https://${host}/spaces/${options.space}/environments/${options.environment ?? "master"}/entries`;
240
+ const pages: EntriesPage[] = [];
145
241
  let skip = 0;
146
242
  let more = true;
147
243
  while (more) {
@@ -160,16 +256,30 @@ export const contentfulSource = (
160
256
  if (!page) {
161
257
  throw new Error("Contentful returned a non-object response");
162
258
  }
163
- const assets = byId(objectsIn(getPath(page, "includes.Asset")));
164
- const linked = byId(objectsIn(getPath(page, "includes.Entry")));
165
259
  const items = objectsIn(page.items);
166
- for (const item of items) {
167
- entries.push(toEntry(item, assets, linked));
168
- }
260
+ pages.push({
261
+ assets: byId(objectsIn(getPath(page, "includes.Asset"))),
262
+ items,
263
+ // `includes` leaves out an entry the page's `items` already hold, so
264
+ // a link to one of those resolves from `items`.
265
+ linked: byId([...objectsIn(getPath(page, "includes.Entry")), ...items]),
266
+ });
169
267
  skip += items.length;
170
268
  more = items.length > 0 && skip < (asNumber(page.total) ?? 0);
171
269
  }
172
- return entries;
270
+ // A link can reach an entry a later page delivers, so every entry's route
271
+ // is known before any body lowers.
272
+ const routes = new Map(
273
+ pages.flatMap(({ items }) =>
274
+ items.map((item): [string, string] => {
275
+ const { id, view } = entryView(item);
276
+ return [id, routeOf(view, id)];
277
+ })
278
+ )
279
+ );
280
+ return pages.flatMap((page) =>
281
+ page.items.map((item) => toEntry(item, page, routes, warn))
282
+ );
173
283
  };
174
284
 
175
285
  return remoteSource(
@@ -1,13 +1,17 @@
1
1
  import { existsSync, watch as fsWatch } from "node:fs";
2
2
  import { readFile } from "node:fs/promises";
3
3
 
4
+ import { YAMLException } from "js-yaml";
4
5
  import { extname, isAbsolute, join, relative, resolve } from "pathe";
5
6
  import { glob } from "tinyglobby";
6
7
 
7
8
  import { BlumeError } from "../diagnostics.ts";
8
9
  import matter from "../frontmatter.ts";
9
10
  import type { Diagnostic } from "../types.ts";
10
- import { unloadablePathDiagnostic } from "./normalize.ts";
11
+ import {
12
+ frontmatterYamlDiagnostic,
13
+ unloadablePathDiagnostic,
14
+ } from "./normalize.ts";
11
15
  import type { ContentSource, SourceEntry, SourceLoadResult } from "./types.ts";
12
16
  import {
13
17
  baselineScanIgnore,
@@ -72,12 +76,22 @@ export const filesystemSource = (
72
76
  }
73
77
  }
74
78
 
75
- const entries = await Promise.all(
76
- loadable.map(async (file): Promise<SourceEntry> => {
79
+ const read = await Promise.all(
80
+ loadable.map(async (file): Promise<SourceEntry | Diagnostic> => {
77
81
  const source = await readFile(file, "utf-8");
78
82
  const ext = extname(file).toLowerCase();
79
83
  const format = ext === ".mdx" ? "mdx" : "md";
80
- const parsed = matter(source);
84
+ // Invalid YAML leaves this one file out, reported at its line,
85
+ // instead of failing the whole scan.
86
+ let parsed: ReturnType<typeof matter>;
87
+ try {
88
+ parsed = matter(source);
89
+ } catch (error) {
90
+ if (error instanceof YAMLException) {
91
+ return frontmatterYamlDiagnostic(error, file);
92
+ }
93
+ throw error;
94
+ }
81
95
  return {
82
96
  body: { format, text: parsed.content },
83
97
  data: parsed.data,
@@ -90,6 +104,14 @@ export const filesystemSource = (
90
104
  };
91
105
  })
92
106
  );
107
+ const entries: SourceEntry[] = [];
108
+ for (const item of read) {
109
+ if ("body" in item) {
110
+ entries.push(item);
111
+ } else {
112
+ diagnostics.push(item);
113
+ }
114
+ }
93
115
 
94
116
  return { diagnostics, entries };
95
117
  };
@@ -284,6 +284,17 @@ interface ChangelogFrontmatter {
284
284
  type?: string;
285
285
  }
286
286
 
287
+ /**
288
+ * The tag a release shows beside its title. A draft (read only with
289
+ * `drafts`) says so, since nothing else marks it as unreleased.
290
+ */
291
+ const releaseCategory = (release: GithubRelease): string => {
292
+ if (release.draft) {
293
+ return "Draft";
294
+ }
295
+ return release.prerelease ? "Prerelease" : "Release";
296
+ };
297
+
287
298
  /**
288
299
  * Lower one release to a staged Markdown entry: the notes become the body,
289
300
  * `type: changelog` frontmatter (title/date/version/category) drives the
@@ -294,7 +305,7 @@ const releaseToEntry = (release: GithubRelease, site?: string): SourceEntry => {
294
305
  const version = release.tag_name.replace(LEADING_V, "");
295
306
  const title = release.name?.trim() || release.tag_name;
296
307
  const date = release.published_at ?? release.created_at;
297
- const category = release.prerelease ? "Prerelease" : "Release";
308
+ const category = releaseCategory(release);
298
309
  // Release notes are the repository's content, not the site author's, so a
299
310
  // link whose destination isn't a web, mail, or relative address
300
311
  // (`javascript:`, `data:`) keeps only its label (see `safe-links.ts`), and
@@ -26,6 +26,11 @@ import {
26
26
  export interface LexicalOptions {
27
27
  /** The CMS origin a relative upload URL (`/media/x.png`) resolves against. */
28
28
  baseUrl?: string;
29
+ /**
30
+ * Called with the label of each node left out as a comment (a block with
31
+ * no serializer, a node type with no Markdown), so the source can warn.
32
+ */
33
+ onUnsupported?: (what: string) => void;
29
34
  /** Serializers for `block`/`inlineBlock` nodes, keyed by `fields.blockType`. */
30
35
  serializers?: Record<string, (fields: JsonObject) => string>;
31
36
  }
@@ -52,6 +57,12 @@ const marksOf = (node: JsonObject): InlineMarks => {
52
57
 
53
58
  const children = (node: JsonObject): JsonObject[] => objectsIn(node.children);
54
59
 
60
+ /** The comment for a node with no Markdown, reported to `onUnsupported`. */
61
+ const leftOut = (what: string, options: LexicalOptions): string => {
62
+ options.onUnsupported?.(what);
63
+ return unsupported(what, writesMdx(options.serializers));
64
+ };
65
+
55
66
  const typeOf = (node: JsonObject): string => asString(node.type) ?? "";
56
67
 
57
68
  const customBlock = (node: JsonObject, options: LexicalOptions): string => {
@@ -61,10 +72,7 @@ const customBlock = (node: JsonObject, options: LexicalOptions): string => {
61
72
  if (fields && serializer) {
62
73
  return serializer(fields);
63
74
  }
64
- return unsupported(
65
- `Lexical block${blockType ? `: ${blockType}` : ""}`,
66
- writesMdx(options.serializers)
67
- );
75
+ return leftOut(`Lexical block${blockType ? `: ${blockType}` : ""}`, options);
68
76
  };
69
77
 
70
78
  const inlineParts = (
@@ -119,9 +127,9 @@ const renderUpload = (node: JsonObject, options: LexicalOptions): string => {
119
127
  const value = asObject(node.value);
120
128
  const url = value ? asString(value.url) : undefined;
121
129
  if (!(value && url)) {
122
- return unsupported(
130
+ return leftOut(
123
131
  "Lexical upload (fetch with depth 1 to populate it)",
124
- writesMdx(options.serializers)
132
+ options
125
133
  );
126
134
  }
127
135
  const alt = asString(value.alt) ?? asString(value.filename) ?? "";
@@ -182,10 +190,7 @@ const renderBlock = (node: JsonObject, options: LexicalOptions): string => {
182
190
  return customBlock(node, options);
183
191
  }
184
192
  default: {
185
- return unsupported(
186
- `Lexical node: ${typeOf(node)}`,
187
- writesMdx(options.serializers)
188
- );
193
+ return leftOut(`Lexical node: ${typeOf(node)}`, options);
189
194
  }
190
195
  }
191
196
  };
@@ -39,6 +39,26 @@ export interface MdxRemoteSourceOptions {
39
39
  // thing on every source type: negation, character classes, nested braces, and
40
40
  // extglobs included. Compiled once per enumeration, not per ref.
41
41
 
42
+ /**
43
+ * Whether a ref is included, read the way tinyglobby reads an `include`
44
+ * array: a `!` pattern excludes what it matches, and the rest include. One
45
+ * picomatch matcher over the whole array would OR a negation in, and
46
+ * `!drafts/**` would include every other path, `.txt` files and all.
47
+ */
48
+ const includeMatcher = (include: string[]): ((ref: string) => boolean) => {
49
+ // An empty pattern list matches nothing, so an array of only negations
50
+ // includes nothing, as in tinyglobby.
51
+ const included = picomatch(
52
+ include.filter((pattern) => !pattern.startsWith("!"))
53
+ );
54
+ const excluded = picomatch(
55
+ include
56
+ .filter((pattern) => pattern.startsWith("!"))
57
+ .map((pattern) => pattern.slice(1))
58
+ );
59
+ return (ref) => included(ref) && !excluded(ref);
60
+ };
61
+
42
62
  /** A file to fetch: its source-local ref plus where to read it from. */
43
63
  interface RemoteRef {
44
64
  ref: string;
@@ -102,12 +122,23 @@ interface GithubTreeEntry {
102
122
  type: string;
103
123
  }
104
124
 
125
+ /** The files a source reads, and what the listing said about them. */
126
+ interface Enumeration {
127
+ /**
128
+ * The configured `path`, described, when the repository at the ref has no
129
+ * folder there, so the source read nothing.
130
+ */
131
+ missing?: string;
132
+ refs: RemoteRef[];
133
+ truncated: boolean;
134
+ }
135
+
105
136
  /** Enumerate a GitHub repo subtree, mapping blobs to remote refs. */
106
137
  const enumerateGithub = async (
107
138
  github: { owner: string; repo: string; ref: string; path: string },
108
139
  include: string[],
109
140
  doFetch: typeof fetch
110
- ): Promise<{ refs: RemoteRef[]; truncated: boolean }> => {
141
+ ): Promise<Enumeration> => {
111
142
  const { owner, repo, ref } = github;
112
143
  const base = github.path.replaceAll(/^\/|\/$/gu, "");
113
144
  const treeUrl = `https://api.github.com/repos/${owner}/${repo}/git/trees/${ref}?recursive=1`;
@@ -119,7 +150,7 @@ const enumerateGithub = async (
119
150
  truncated?: boolean;
120
151
  };
121
152
  const prefix = base ? `${base}/` : "";
122
- const included = picomatch(include);
153
+ const included = includeMatcher(include);
123
154
  const refs = (body.tree ?? []).flatMap((node) => {
124
155
  if (!(node.type === "blob" && node.path.startsWith(prefix))) {
125
156
  return [];
@@ -138,7 +169,18 @@ const enumerateGithub = async (
138
169
  });
139
170
  // GitHub caps the recursive tree response (~100k entries / 7MB) and flags it
140
171
  // with `truncated`; ignoring it would silently import only part of the repo.
141
- return { refs, truncated: body.truncated === true };
172
+ const truncated = body.truncated === true;
173
+ // Git has no empty folders, so a complete listing with nothing under
174
+ // `path` means the path is wrong at this ref (a moved docs folder, a typo).
175
+ const found =
176
+ prefix === "" ||
177
+ truncated ||
178
+ (body.tree ?? []).some((node) => node.path.startsWith(prefix));
179
+ return {
180
+ missing: found ? undefined : `"${base}" in ${owner}/${repo} at ${ref}`,
181
+ refs,
182
+ truncated,
183
+ };
142
184
  };
143
185
 
144
186
  /**
@@ -169,15 +211,12 @@ export const mdxRemoteSource = (
169
211
  });
170
212
  };
171
213
 
172
- const enumerate = async (): Promise<{
173
- refs: RemoteRef[];
174
- truncated: boolean;
175
- }> => {
214
+ const enumerate = async (): Promise<Enumeration> => {
176
215
  if (options.github) {
177
216
  return await enumerateGithub(options.github, options.include, doFetch);
178
217
  }
179
218
  const base = (options.url ?? "").replace(/\/$/u, "");
180
- const included = picomatch(options.include);
219
+ const included = includeMatcher(options.include);
181
220
  const refs = (options.files ?? []).flatMap((ref) =>
182
221
  included(ref)
183
222
  ? [{ editUrl: `${base}/${ref}`, fetchUrl: `${base}/${ref}`, ref }]
@@ -209,12 +248,25 @@ export const mdxRemoteSource = (
209
248
  options.name,
210
249
  cache,
211
250
  async () => {
212
- const { refs, truncated } = await enumerate();
251
+ const { missing, refs, truncated } = await enumerate();
252
+ if (missing) {
253
+ skipped.push({
254
+ code: "BLUME_SOURCE_PATH_MISSING",
255
+ message: `Source "${options.name}" read no files: there is no folder ${missing}.`,
256
+ severity: "warning",
257
+ suggestion:
258
+ "Check github.path and github.ref against the repository.",
259
+ });
260
+ }
213
261
  if (truncated) {
262
+ // The listing covers the whole repository at the ref, whatever
263
+ // `path` is, so narrowing it doesn't help.
214
264
  skipped.push({
215
265
  code: "BLUME_SOURCE_TRUNCATED",
216
- message: `Source "${options.name}" hit GitHub's tree listing limit; some files were not enumerated. Narrow the source path or split the repo.`,
266
+ message: `Source "${options.name}" hit GitHub's tree listing limit for the whole repository; some files were not enumerated.`,
217
267
  severity: "warning",
268
+ suggestion:
269
+ "List the files to read with { url, files } instead, or split the repository.",
218
270
  });
219
271
  }
220
272
  const reasons: string[] = [];