blume 2.0.2 → 2.1.0

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 (601) hide show
  1. package/AGENTS.md +1 -0
  2. package/CHANGELOG.md +113 -0
  3. package/README.md +1 -1
  4. package/dist/cli/{chunk-6k4ftwze.js → chunk-0gz36akr.js} +18 -2
  5. package/dist/cli/{chunk-6k4ftwze.js.map → chunk-0gz36akr.js.map} +3 -3
  6. package/dist/cli/{chunk-zp79m0ts.js → chunk-1n228xqb.js} +2 -2
  7. package/dist/cli/{chunk-ddndchfr.js → chunk-2f3ndkpw.js} +57 -29
  8. package/dist/cli/chunk-2f3ndkpw.js.map +14 -0
  9. package/dist/cli/{chunk-fsmrqk8a.js → chunk-2f7n4qzt.js} +8 -2
  10. package/dist/cli/{chunk-fsmrqk8a.js.map → chunk-2f7n4qzt.js.map} +3 -3
  11. package/dist/cli/{chunk-ppzjqwx2.js → chunk-2n4ps1b3.js} +17 -18
  12. package/dist/cli/{chunk-ppzjqwx2.js.map → chunk-2n4ps1b3.js.map} +3 -3
  13. package/dist/cli/{chunk-5r8g91qn.js → chunk-43zcf4gz.js} +398 -159
  14. package/dist/cli/chunk-43zcf4gz.js.map +36 -0
  15. package/dist/cli/chunk-4ejxbzee.js +39 -0
  16. package/dist/cli/chunk-4ejxbzee.js.map +10 -0
  17. package/dist/cli/{chunk-hr8ne106.js → chunk-4h8z9vv5.js} +296 -77
  18. package/dist/cli/chunk-4h8z9vv5.js.map +16 -0
  19. package/dist/cli/{chunk-k7pj68a8.js → chunk-5cg2rzgf.js} +18 -16
  20. package/dist/cli/{chunk-k7pj68a8.js.map → chunk-5cg2rzgf.js.map} +2 -2
  21. package/dist/cli/{chunk-h2ez8dzb.js → chunk-5ffywsmb.js} +4 -4
  22. package/dist/cli/{chunk-g698a744.js → chunk-5wj2qwn2.js} +5 -5
  23. package/dist/cli/chunk-6bm0xpgd.js +18 -0
  24. package/dist/cli/chunk-6bm0xpgd.js.map +10 -0
  25. package/dist/cli/{chunk-3yce002v.js → chunk-713mngdq.js} +15 -8
  26. package/dist/cli/chunk-713mngdq.js.map +14 -0
  27. package/dist/cli/{chunk-91ws1n6j.js → chunk-9geymqns.js} +17 -15
  28. package/dist/cli/{chunk-91ws1n6j.js.map → chunk-9geymqns.js.map} +2 -2
  29. package/dist/cli/{chunk-6dtt0zfn.js → chunk-9xr4dgee.js} +27 -18
  30. package/dist/cli/chunk-9xr4dgee.js.map +10 -0
  31. package/dist/cli/{chunk-sqw4ekg1.js → chunk-amrb6hgh.js} +2 -2
  32. package/dist/cli/chunk-cm89scdv.js +443 -0
  33. package/dist/cli/chunk-cm89scdv.js.map +11 -0
  34. package/dist/cli/{chunk-wdrt2k2v.js → chunk-cmtcnh9f.js} +90 -26
  35. package/dist/cli/chunk-cmtcnh9f.js.map +10 -0
  36. package/dist/cli/chunk-cy5bm0j0.js +104 -0
  37. package/dist/cli/chunk-cy5bm0j0.js.map +11 -0
  38. package/dist/cli/{chunk-7mbqtmgb.js → chunk-e7vvk5ya.js} +21 -12
  39. package/dist/cli/chunk-e7vvk5ya.js.map +10 -0
  40. package/dist/cli/{chunk-qkb5a8sa.js → chunk-g2z9x01v.js} +6 -98
  41. package/dist/cli/chunk-g2z9x01v.js.map +10 -0
  42. package/dist/cli/{chunk-273ygyr4.js → chunk-ga0pf4aj.js} +7 -7
  43. package/dist/cli/{chunk-273ygyr4.js.map → chunk-ga0pf4aj.js.map} +3 -3
  44. package/dist/cli/{chunk-h7k3nq3v.js → chunk-gp84za0h.js} +2 -2
  45. package/dist/cli/{chunk-hqp2ajnh.js → chunk-h2a48yhd.js} +195 -73
  46. package/dist/cli/chunk-h2a48yhd.js.map +36 -0
  47. package/dist/cli/{chunk-pbg5a4s3.js → chunk-jnwj5kjj.js} +71 -47
  48. package/dist/cli/chunk-jnwj5kjj.js.map +19 -0
  49. package/dist/cli/chunk-kwrg63tm.js +31 -0
  50. package/dist/cli/chunk-kwrg63tm.js.map +10 -0
  51. package/dist/cli/{chunk-8g8ytmgx.js → chunk-nd1821sk.js} +21 -19
  52. package/dist/cli/{chunk-8g8ytmgx.js.map → chunk-nd1821sk.js.map} +2 -2
  53. package/dist/cli/{chunk-w4bxdvsa.js → chunk-ntv4qrt7.js} +17 -15
  54. package/dist/cli/{chunk-w4bxdvsa.js.map → chunk-ntv4qrt7.js.map} +2 -2
  55. package/dist/cli/{chunk-bfwp9vp6.js → chunk-p804f3pe.js} +7 -7
  56. package/dist/cli/{chunk-4e9b9ra6.js → chunk-py62fewh.js} +6 -6
  57. package/dist/cli/{chunk-4e9b9ra6.js.map → chunk-py62fewh.js.map} +1 -1
  58. package/dist/cli/chunk-q3wxxq39.js +126 -0
  59. package/dist/cli/chunk-q3wxxq39.js.map +11 -0
  60. package/dist/cli/{chunk-j85scx15.js → chunk-rekqz4qg.js} +142 -46
  61. package/dist/cli/chunk-rekqz4qg.js.map +10 -0
  62. package/dist/cli/{chunk-00gs3wqs.js → chunk-ta27feev.js} +1 -1
  63. package/dist/cli/{chunk-v6ya5kcb.js → chunk-xkz5xbtv.js} +4031 -1132
  64. package/dist/cli/chunk-xkz5xbtv.js.map +213 -0
  65. package/dist/cli/{chunk-bbnwccaz.js → chunk-y4ykbark.js} +2 -2
  66. package/dist/cli/{chunk-n1yg3tj3.js → chunk-y5h4r2y0.js} +6 -6
  67. package/dist/cli/{chunk-n1yg3tj3.js.map → chunk-y5h4r2y0.js.map} +3 -3
  68. package/dist/cli/{chunk-nfcyttvj.js → chunk-yj17vs39.js} +9 -9
  69. package/dist/cli/{chunk-nfcyttvj.js.map → chunk-yj17vs39.js.map} +1 -1
  70. package/dist/cli/{chunk-7vtckvaw.js → chunk-yxmk4pec.js} +16 -14
  71. package/dist/cli/{chunk-7vtckvaw.js.map → chunk-yxmk4pec.js.map} +2 -2
  72. package/dist/cli/{chunk-gs7r695n.js → chunk-zccbjcj0.js} +22 -8
  73. package/dist/cli/{chunk-gs7r695n.js.map → chunk-zccbjcj0.js.map} +3 -3
  74. package/dist/cli/{chunk-xh43dwgw.js → chunk-zf748avv.js} +415 -225
  75. package/dist/cli/chunk-zf748avv.js.map +37 -0
  76. package/dist/cli/{chunk-d1v5rhy0.js → chunk-zhvd6adm.js} +16 -14
  77. package/dist/cli/{chunk-d1v5rhy0.js.map → chunk-zhvd6adm.js.map} +2 -2
  78. package/dist/cli/index.js +28 -20
  79. package/dist/cli/index.js.map +5 -5
  80. package/dist/types/ai/agent-surface.d.ts +8 -7
  81. package/dist/types/ai/ai-catalog.d.ts +7 -0
  82. package/dist/types/ai/ask-context.d.ts +22 -0
  83. package/dist/types/ai/ask.d.ts +271 -58
  84. package/dist/types/ai/index.d.ts +2 -2
  85. package/dist/types/ai/link-headers.d.ts +8 -1
  86. package/dist/types/ai/openapi-components.d.ts +5 -2
  87. package/dist/types/ai/relative-links.d.ts +9 -7
  88. package/dist/types/ai/site-skill.d.ts +26 -0
  89. package/dist/types/ai/skills.d.ts +20 -1
  90. package/dist/types/ai/tar.d.ts +1 -3
  91. package/dist/types/analytics/index.d.ts +2 -0
  92. package/dist/types/analytics/one-dollar-stats.d.ts +48 -0
  93. package/dist/types/analytics/schema.d.ts +14 -0
  94. package/dist/types/astro/integration.d.ts +11 -2
  95. package/dist/types/captcha/hcaptcha.d.ts +27 -0
  96. package/dist/types/captcha/index.d.ts +28 -0
  97. package/dist/types/captcha/schema.d.ts +29 -0
  98. package/dist/types/captcha/turnstile.d.ts +28 -0
  99. package/dist/types/components/content/api-field.d.ts +59 -0
  100. package/dist/types/components/content/api-page.d.ts +95 -0
  101. package/dist/types/components/layout/nav-utils.d.ts +26 -0
  102. package/dist/types/components/openapi/helpers.d.ts +137 -0
  103. package/dist/types/components/openapi/security.d.ts +85 -0
  104. package/dist/types/consent/ethyca.d.ts +48 -0
  105. package/dist/types/consent/index.d.ts +37 -0
  106. package/dist/types/consent/native.d.ts +28 -0
  107. package/dist/types/consent/osano.d.ts +42 -0
  108. package/dist/types/consent/schema.d.ts +110 -0
  109. package/dist/types/core/base-path.d.ts +21 -7
  110. package/dist/types/core/config-input.d.ts +196 -27
  111. package/dist/types/core/data.d.ts +49 -4
  112. package/dist/types/core/directive-diagnostics.d.ts +12 -0
  113. package/dist/types/core/footer.d.ts +8 -0
  114. package/dist/types/core/heading-markers.d.ts +5 -7
  115. package/dist/types/core/i18n-ui.d.ts +86 -0
  116. package/dist/types/core/i18n.d.ts +2 -0
  117. package/dist/types/core/includes.d.ts +8 -2
  118. package/dist/types/core/last-modified.d.ts +10 -0
  119. package/dist/types/core/localizable.d.ts +7 -0
  120. package/dist/types/core/meta.d.ts +8 -0
  121. package/dist/types/core/page-modes.d.ts +29 -0
  122. package/dist/types/core/redirect-patterns.d.ts +144 -0
  123. package/dist/types/core/related.d.ts +40 -0
  124. package/dist/types/core/schema.d.ts +558 -56
  125. package/dist/types/core/sources/lower.d.ts +29 -16
  126. package/dist/types/core/sources/normalize.d.ts +13 -1
  127. package/dist/types/core/sources/read.d.ts +5 -0
  128. package/dist/types/core/sources/watch.d.ts +12 -5
  129. package/dist/types/core/standard-schema.d.ts +5 -0
  130. package/dist/types/core/types.d.ts +1 -1
  131. package/dist/types/core/variables.d.ts +42 -0
  132. package/dist/types/deploy/artifacts.d.ts +6 -4
  133. package/dist/types/deploy/headers.d.ts +5 -0
  134. package/dist/types/deploy/node-headers.d.ts +13 -2
  135. package/dist/types/deploy/platforms/cloudflare.d.ts +5 -0
  136. package/dist/types/deploy/platforms/netlify.d.ts +7 -0
  137. package/dist/types/deploy/platforms/types.d.ts +8 -0
  138. package/dist/types/deploy/platforms/vercel.d.ts +6 -0
  139. package/dist/types/deploy/redirects.d.ts +61 -22
  140. package/dist/types/deploy/vercel-negotiation.d.ts +9 -0
  141. package/dist/types/markdown/code-title.d.ts +17 -0
  142. package/dist/types/markdown/directives.d.ts +62 -0
  143. package/dist/types/markdown/features.d.ts +24 -2
  144. package/dist/types/markdown/mdast.d.ts +63 -0
  145. package/dist/types/narration/build.d.ts +70 -0
  146. package/dist/types/narration/provider.d.ts +26 -0
  147. package/dist/types/narration/script.d.ts +154 -0
  148. package/dist/types/openapi/asyncapi.d.ts +4 -2
  149. package/dist/types/openapi/model.d.ts +12 -6
  150. package/dist/types/openapi/overlay.d.ts +54 -0
  151. package/dist/types/openapi/parse.d.ts +13 -0
  152. package/dist/types/openapi/references.d.ts +14 -4
  153. package/dist/types/ratelimit/cloudflare.d.ts +39 -0
  154. package/dist/types/ratelimit/index.d.ts +27 -0
  155. package/dist/types/ratelimit/memory.d.ts +36 -0
  156. package/dist/types/ratelimit/schema.d.ts +111 -0
  157. package/dist/types/ratelimit/upstash.d.ts +21 -0
  158. package/dist/types/ratelimit/wrangler.d.ts +23 -0
  159. package/dist/types/reference/asyncapi.d.ts +10 -10
  160. package/dist/types/reference/graphql.d.ts +13 -10
  161. package/dist/types/reference/openapi.d.ts +33 -12
  162. package/dist/types/reference/options.d.ts +45 -5
  163. package/dist/types/reference/scalar.d.ts +13 -0
  164. package/dist/types/reference/schema.d.ts +60 -36
  165. package/dist/types/search/documents.d.ts +12 -1
  166. package/dist/types/search/orama-index.d.ts +4 -0
  167. package/dist/types/seo/metatags.d.ts +20 -0
  168. package/docs/02-deployment.mdx +44 -6
  169. package/docs/03-upgrading.mdx +4 -4
  170. package/docs/04-migrating.mdx +1 -1
  171. package/docs/08-faq.mdx +1 -1
  172. package/docs/advanced/changelog.mdx +2 -2
  173. package/docs/advanced/custom-pages.mdx +3 -3
  174. package/docs/advanced/skills.mdx +14 -0
  175. package/docs/cli/audit.mdx +38 -2
  176. package/docs/cli/evals.mdx +3 -3
  177. package/docs/cli/index.mdx +1 -0
  178. package/docs/cli/translate.mdx +3 -3
  179. package/docs/cli/version.mdx +1 -1
  180. package/docs/configuration/analytics.mdx +22 -1
  181. package/docs/configuration/assistant.mdx +188 -36
  182. package/docs/configuration/consent.mdx +100 -0
  183. package/docs/configuration/customization.mdx +6 -4
  184. package/docs/configuration/index.mdx +63 -5
  185. package/docs/configuration/meta.ts +3 -0
  186. package/docs/configuration/narration.mdx +119 -0
  187. package/docs/configuration/rate-limiting.mdx +69 -0
  188. package/docs/configuration/search.mdx +30 -2
  189. package/docs/configuration/theming.mdx +3 -1
  190. package/docs/content/components.mdx +107 -1
  191. package/docs/content/frontmatter.mdx +77 -1
  192. package/docs/content/i18n.mdx +40 -3
  193. package/docs/content/includes.mdx +16 -0
  194. package/docs/content/index.mdx +4 -2
  195. package/docs/content/islands.mdx +1 -1
  196. package/docs/content/meta.mdx +4 -1
  197. package/docs/content/meta.ts +1 -0
  198. package/docs/content/navigation.mdx +28 -6
  199. package/docs/content/sources/contentful.mdx +32 -0
  200. package/docs/content/sources/custom.mdx +39 -0
  201. package/docs/content/sources/github-releases.mdx +31 -0
  202. package/docs/content/sources/index.mdx +91 -0
  203. package/docs/content/sources/meta.ts +16 -0
  204. package/docs/content/sources/notion.mdx +32 -0
  205. package/docs/content/sources/obsidian.mdx +37 -0
  206. package/docs/content/sources/payload.mdx +30 -0
  207. package/docs/content/sources/remote-mdx.mdx +40 -0
  208. package/docs/content/sources/sanity.mdx +29 -0
  209. package/docs/content/sources/strapi.mdx +31 -0
  210. package/docs/content/syntax.mdx +81 -2
  211. package/docs/content/variables.mdx +41 -0
  212. package/docs/content/versioning.mdx +1 -1
  213. package/docs/discoverability/agent-discovery.mdx +30 -3
  214. package/docs/discoverability/index.mdx +3 -2
  215. package/docs/discoverability/llms-txt.mdx +1 -1
  216. package/docs/discoverability/markdown.mdx +2 -2
  217. package/docs/discoverability/metadata.mdx +17 -0
  218. package/docs/discoverability/open-graph.mdx +5 -3
  219. package/docs/discoverability/rss.mdx +1 -1
  220. package/docs/references/api-pages.mdx +74 -0
  221. package/docs/references/asyncapi.mdx +2 -2
  222. package/docs/references/graphql.mdx +3 -3
  223. package/docs/references/meta.ts +1 -1
  224. package/docs/references/openapi.mdx +106 -5
  225. package/docs/references/scalar.mdx +3 -1
  226. package/package.json +36 -1
  227. package/skills/blume/SKILL.md +3 -0
  228. package/skills/blume-migrate/SKILL.md +5 -5
  229. package/skills/blume-migrate/references/docusaurus.md +3 -3
  230. package/skills/blume-migrate/references/fumadocs.md +1 -1
  231. package/skills/blume-migrate/references/mintlify.md +37 -19
  232. package/skills/blume-migrate/references/nextra.md +3 -3
  233. package/skills/blume-migrate/references/starlight.md +2 -1
  234. package/skills/blume-migrate/scripts/mintlify-codemod.mjs +67 -16
  235. package/skills/blume-write-skill/SKILL.md +82 -0
  236. package/src/ai/agent-readability.ts +4 -3
  237. package/src/ai/agent-surface.ts +9 -7
  238. package/src/ai/ai-catalog.ts +12 -5
  239. package/src/ai/api/handlers.ts +2 -2
  240. package/src/ai/api/spec.ts +2 -2
  241. package/src/ai/api-catalog.ts +6 -2
  242. package/src/ai/ask-context.ts +73 -20
  243. package/src/ai/ask-data.ts +6 -0
  244. package/src/ai/ask-tools.ts +142 -0
  245. package/src/ai/ask.ts +354 -76
  246. package/src/ai/changelog-markdown.ts +2 -2
  247. package/src/ai/component-markdown.ts +75 -0
  248. package/src/ai/cors.ts +2 -0
  249. package/src/ai/index.ts +12 -0
  250. package/src/ai/link-headers.ts +14 -5
  251. package/src/ai/llms.ts +24 -9
  252. package/src/ai/markdown.ts +2 -1
  253. package/src/ai/mcp/data.ts +6 -0
  254. package/src/ai/mcp/discovery.ts +2 -2
  255. package/src/ai/mcp/query.ts +2 -2
  256. package/src/ai/mcp/server.ts +2 -2
  257. package/src/ai/openapi-components.ts +31 -9
  258. package/src/ai/relative-links.ts +42 -26
  259. package/src/ai/serializers.ts +2 -1
  260. package/src/ai/site-skill.ts +274 -0
  261. package/src/ai/skills.ts +49 -3
  262. package/src/ai/tar.ts +139 -12
  263. package/src/ai/write-skill.ts +97 -0
  264. package/src/analytics/head.ts +4 -0
  265. package/src/analytics/index.ts +5 -0
  266. package/src/analytics/one-dollar-stats.ts +86 -0
  267. package/src/analytics/schema.ts +2 -0
  268. package/src/astro/generate.ts +133 -19
  269. package/src/astro/include-hmr.ts +31 -12
  270. package/src/astro/include-refresh.ts +19 -8
  271. package/src/astro/index.ts +1 -0
  272. package/src/astro/integration.ts +143 -14
  273. package/src/astro/module-types.ts +6 -0
  274. package/src/astro/runtime-modules.ts +28 -13
  275. package/src/astro/templates.ts +364 -67
  276. package/src/astro/variables.ts +31 -0
  277. package/src/audit/catalog.ts +19 -0
  278. package/src/audit/checks/contrast.ts +183 -0
  279. package/src/audit/checks/i18n.ts +2 -3
  280. package/src/audit/checks/indexability.ts +2 -3
  281. package/src/audit/checks/links.ts +13 -13
  282. package/src/audit/checks/sitemap.ts +2 -3
  283. package/src/audit/graph.ts +3 -1
  284. package/src/audit/redirects.ts +46 -15
  285. package/src/audit/run.ts +14 -9
  286. package/src/audit/snapshot.ts +5 -0
  287. package/src/audit/types.ts +2 -1
  288. package/src/blume-modules.d.ts +7 -0
  289. package/src/captcha/client.ts +152 -0
  290. package/src/captcha/hcaptcha.ts +36 -0
  291. package/src/captcha/index.ts +28 -0
  292. package/src/captcha/schema.ts +32 -0
  293. package/src/captcha/turnstile.ts +38 -0
  294. package/src/captcha/verify.ts +71 -0
  295. package/src/cli/args.ts +32 -0
  296. package/src/cli/command-meta.ts +5 -0
  297. package/src/cli/commands/audit.ts +4 -1
  298. package/src/cli/commands/doctor.ts +25 -17
  299. package/src/cli/commands/eval.ts +19 -11
  300. package/src/cli/commands/skill.ts +96 -0
  301. package/src/cli/commands/translate.ts +9 -12
  302. package/src/cli/commands/validate.ts +3 -1
  303. package/src/cli/dev-lock.ts +157 -37
  304. package/src/cli/index.ts +5 -0
  305. package/src/cli/report-format.ts +11 -6
  306. package/src/cli/required-secrets.ts +71 -29
  307. package/src/components/BlumePage.astro +2 -0
  308. package/src/components/colors.ts +19 -0
  309. package/src/components/content/ApiEndpoint.astro +18 -0
  310. package/src/components/content/ApiField.astro +79 -0
  311. package/src/components/content/ApiPlayground.astro +62 -0
  312. package/src/components/content/ApiRail.astro +19 -0
  313. package/src/components/content/Badge.astro +5 -3
  314. package/src/components/content/Color.astro +1 -0
  315. package/src/components/content/Component.astro +2 -2
  316. package/src/components/content/FileTree.astro +1 -0
  317. package/src/components/content/GithubInfo.astro +1 -0
  318. package/src/components/content/Panel.astro +1 -0
  319. package/src/components/content/ParamField.astro +25 -0
  320. package/src/components/content/RequestExample.astro +17 -0
  321. package/src/components/content/ResponseExample.astro +17 -0
  322. package/src/components/content/ResponseField.astro +32 -0
  323. package/src/components/content/Step.astro +1 -0
  324. package/src/components/content/Tab.astro +0 -1
  325. package/src/components/content/Tabs.astro +4 -0
  326. package/src/components/content/Tile.astro +1 -0
  327. package/src/components/content/Tooltip.astro +1 -0
  328. package/src/components/content/Tree.astro +1 -0
  329. package/src/components/content/TypeTable.astro +4 -1
  330. package/src/components/content/View.astro +21 -0
  331. package/src/components/content/ViewSwitcher.astro +168 -0
  332. package/src/components/content/api-field.ts +100 -0
  333. package/src/components/content/api-page.ts +224 -0
  334. package/src/components/content/badge-color.ts +5 -2
  335. package/src/components/content/base-href.ts +13 -36
  336. package/src/components/content/view-switcher.ts +71 -0
  337. package/src/components/index.ts +2 -0
  338. package/src/components/islands/Assistant.astro +10 -1
  339. package/src/components/islands/assistant.tsx +125 -18
  340. package/src/components/islands/base-path.ts +47 -10
  341. package/src/components/islands/code-question.ts +62 -0
  342. package/src/components/islands/hooks.ts +145 -40
  343. package/src/components/islands/support-link.ts +68 -0
  344. package/src/components/islands/webmcp.ts +4 -2
  345. package/src/components/layout/Analytics.astro +11 -2
  346. package/src/components/layout/Banner.astro +14 -10
  347. package/src/components/layout/Breadcrumbs.astro +11 -4
  348. package/src/components/layout/ConsentBanner.astro +62 -0
  349. package/src/components/layout/ConsentHead.astro +38 -0
  350. package/src/components/layout/Directory.astro +94 -0
  351. package/src/components/layout/DiscoveryLinks.astro +9 -5
  352. package/src/components/layout/DrawerSwitchers.astro +51 -0
  353. package/src/components/layout/Empty.astro +2 -2
  354. package/src/components/layout/Header.astro +124 -82
  355. package/src/components/layout/LanguageSwitcher.astro +50 -17
  356. package/src/components/layout/NarrationPlayer.astro +1085 -0
  357. package/src/components/layout/NavSelector.astro +35 -9
  358. package/src/components/layout/NavTabMenu.astro +3 -3
  359. package/src/components/layout/NavTree.astro +16 -6
  360. package/src/components/layout/PageActions.astro +4 -3
  361. package/src/components/layout/PageFeedback.astro +54 -49
  362. package/src/components/layout/PageLayout.astro +78 -9
  363. package/src/components/layout/Pagination.astro +3 -3
  364. package/src/components/layout/ReferenceLayout.astro +17 -6
  365. package/src/components/layout/RelatedPages.astro +42 -0
  366. package/src/components/layout/RootLayout.astro +313 -50
  367. package/src/components/layout/Search.astro +64 -30
  368. package/src/components/layout/SiteFooter.astro +124 -0
  369. package/src/components/layout/SiteMetatags.astro +16 -0
  370. package/src/components/layout/VersionBanner.astro +2 -2
  371. package/src/components/layout/analytics-client.ts +12 -0
  372. package/src/components/layout/consent/native.ts +79 -0
  373. package/src/components/layout/consent/types.ts +37 -0
  374. package/src/components/layout/footer-links.ts +53 -0
  375. package/src/components/layout/footer-socials.ts +87 -0
  376. package/src/components/layout/locale-redirect.ts +107 -0
  377. package/src/components/layout/nav-utils.ts +83 -0
  378. package/src/components/layout/page-feedback.ts +96 -0
  379. package/src/components/layout/search/analytics.ts +98 -0
  380. package/src/components/layout/search/flexsearch.ts +17 -5
  381. package/src/components/layout/search/orama-cloud.ts +26 -14
  382. package/src/components/layout/search/types.ts +20 -0
  383. package/src/components/layout/search/typesense.ts +5 -1
  384. package/src/components/layout/toc-active.ts +41 -0
  385. package/src/components/layout/toc-element.ts +8 -14
  386. package/src/components/openapi/ApiTagOperations.astro +2 -2
  387. package/src/components/openapi/AsyncApiOperation.astro +7 -4
  388. package/src/components/openapi/GraphqlChip.astro +2 -2
  389. package/src/components/openapi/Operation.astro +163 -13
  390. package/src/components/openapi/Playground.astro +4 -4
  391. package/src/components/openapi/RequestBody.astro +6 -4
  392. package/src/components/openapi/RequestPanel.astro +63 -8
  393. package/src/components/openapi/Responses.astro +6 -4
  394. package/src/components/openapi/SchemaTable.astro +7 -0
  395. package/src/components/openapi/async-snippets.ts +4 -4
  396. package/src/components/openapi/async.ts +38 -6
  397. package/src/components/openapi/code-samples.ts +97 -0
  398. package/src/components/openapi/helpers.ts +19 -9
  399. package/src/components/openapi/message-composer.ts +6 -1
  400. package/src/components/openapi/message.ts +17 -2
  401. package/src/components/openapi/operation-model.ts +81 -11
  402. package/src/components/openapi/panel.ts +4 -2
  403. package/src/components/openapi/param-style.ts +181 -0
  404. package/src/components/openapi/playground-client.ts +73 -16
  405. package/src/components/openapi/request.ts +190 -26
  406. package/src/components/openapi/schema-tree.ts +30 -24
  407. package/src/components/openapi/security.ts +6 -3
  408. package/src/components/openapi/snippets.ts +953 -13
  409. package/src/components/openapi/ws-client.ts +18 -2
  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/base-path.ts +62 -16
  421. package/src/core/builtin-tags.ts +5 -0
  422. package/src/core/client-address.ts +19 -0
  423. package/src/core/config-input.ts +203 -27
  424. package/src/core/data.ts +44 -4
  425. package/src/core/diagnostics.ts +5 -2
  426. package/src/core/directive-diagnostics.ts +99 -0
  427. package/src/core/footer.ts +26 -0
  428. package/src/core/frontmatter.ts +21 -18
  429. package/src/core/graph.ts +39 -36
  430. package/src/core/heading-markers.ts +5 -18
  431. package/src/core/i18n-ui.ts +82 -2
  432. package/src/core/i18n.ts +31 -3
  433. package/src/core/includes.ts +20 -3
  434. package/src/core/last-modified.ts +25 -3
  435. package/src/core/links.ts +16 -2
  436. package/src/core/locale-links.ts +5 -1
  437. package/src/core/localizable.ts +26 -0
  438. package/src/core/meta.ts +33 -19
  439. package/src/core/navigation.ts +71 -20
  440. package/src/core/page-modes.ts +56 -0
  441. package/src/core/project-graph.ts +138 -10
  442. package/src/core/redirect-patterns.ts +491 -0
  443. package/src/core/related.ts +131 -0
  444. package/src/core/schema.ts +342 -68
  445. package/src/core/sources/contentful-rich-text.ts +27 -20
  446. package/src/core/sources/filesystem.ts +20 -2
  447. package/src/core/sources/github-releases.ts +25 -6
  448. package/src/core/sources/lexical.ts +23 -18
  449. package/src/core/sources/lower.ts +201 -34
  450. package/src/core/sources/mdx-remote.ts +51 -16
  451. package/src/core/sources/normalize.ts +327 -91
  452. package/src/core/sources/notion.ts +52 -25
  453. package/src/core/sources/obsidian.ts +23 -6
  454. package/src/core/sources/portable-text.ts +38 -21
  455. package/src/core/sources/read.ts +8 -3
  456. package/src/core/sources/strapi-blocks.ts +20 -16
  457. package/src/core/sources/watch.ts +20 -7
  458. package/src/core/standard-schema.ts +10 -6
  459. package/src/core/types.ts +1 -1
  460. package/src/core/ui-packs/ar.ts +43 -0
  461. package/src/core/ui-packs/bg.ts +45 -0
  462. package/src/core/ui-packs/bn.ts +43 -0
  463. package/src/core/ui-packs/ca.ts +46 -0
  464. package/src/core/ui-packs/cs.ts +44 -0
  465. package/src/core/ui-packs/da.ts +45 -0
  466. package/src/core/ui-packs/de.ts +46 -0
  467. package/src/core/ui-packs/el.ts +45 -0
  468. package/src/core/ui-packs/es.ts +46 -0
  469. package/src/core/ui-packs/fa.ts +45 -0
  470. package/src/core/ui-packs/fi.ts +45 -0
  471. package/src/core/ui-packs/fr.ts +46 -0
  472. package/src/core/ui-packs/he.ts +43 -0
  473. package/src/core/ui-packs/hi.ts +44 -0
  474. package/src/core/ui-packs/hr.ts +45 -0
  475. package/src/core/ui-packs/hu.ts +44 -0
  476. package/src/core/ui-packs/id.ts +45 -0
  477. package/src/core/ui-packs/it.ts +45 -0
  478. package/src/core/ui-packs/ja.ts +45 -0
  479. package/src/core/ui-packs/ko.ts +44 -0
  480. package/src/core/ui-packs/nl.ts +46 -0
  481. package/src/core/ui-packs/no.ts +44 -0
  482. package/src/core/ui-packs/pl.ts +45 -0
  483. package/src/core/ui-packs/pt-br.ts +46 -0
  484. package/src/core/ui-packs/pt.ts +45 -0
  485. package/src/core/ui-packs/ro.ts +45 -0
  486. package/src/core/ui-packs/ru.ts +45 -0
  487. package/src/core/ui-packs/sk.ts +44 -0
  488. package/src/core/ui-packs/sr.ts +45 -0
  489. package/src/core/ui-packs/sv.ts +44 -0
  490. package/src/core/ui-packs/th.ts +43 -0
  491. package/src/core/ui-packs/tr.ts +45 -0
  492. package/src/core/ui-packs/uk.ts +45 -0
  493. package/src/core/ui-packs/vi.ts +45 -0
  494. package/src/core/ui-packs/zh-tw.ts +43 -0
  495. package/src/core/ui-packs/zh.ts +43 -0
  496. package/src/core/variables.ts +103 -0
  497. package/src/core/version-cut.ts +52 -15
  498. package/src/deploy/artifacts.ts +104 -14
  499. package/src/deploy/cloudflare-negotiation.ts +39 -17
  500. package/src/deploy/headers.ts +10 -5
  501. package/src/deploy/node-headers.ts +64 -10
  502. package/src/deploy/platforms/cloudflare.ts +44 -3
  503. package/src/deploy/platforms/netlify.ts +34 -1
  504. package/src/deploy/platforms/node.ts +1 -0
  505. package/src/deploy/platforms/static.ts +1 -0
  506. package/src/deploy/platforms/types.ts +8 -0
  507. package/src/deploy/platforms/vercel.ts +38 -2
  508. package/src/deploy/redirects.ts +139 -36
  509. package/src/deploy/robots.ts +2 -2
  510. package/src/deploy/rss.ts +4 -3
  511. package/src/deploy/sitemap.ts +7 -5
  512. package/src/deploy/vercel-negotiation.ts +36 -0
  513. package/src/markdown/api-rail.ts +259 -0
  514. package/src/markdown/base-links.ts +34 -36
  515. package/src/markdown/code-title.ts +35 -9
  516. package/src/markdown/directives.ts +242 -36
  517. package/src/markdown/features.ts +20 -2
  518. package/src/markdown/fence-meta.ts +2 -0
  519. package/src/markdown/include.ts +11 -1
  520. package/src/markdown/index.ts +35 -11
  521. package/src/markdown/math.ts +4 -4
  522. package/src/markdown/mdast.ts +5 -2
  523. package/src/markdown/relative-links.ts +12 -4
  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 +136 -11
  531. package/src/og/derive.ts +41 -31
  532. package/src/og/index.ts +1 -0
  533. package/src/openapi/asyncapi.ts +4 -2
  534. package/src/openapi/model.ts +77 -36
  535. package/src/openapi/overlay.ts +256 -0
  536. package/src/openapi/parse.ts +55 -2
  537. package/src/openapi/proxy.ts +63 -10
  538. package/src/openapi/references.ts +21 -5
  539. package/src/openapi/render-mdx.ts +46 -3
  540. package/src/openapi/scalar.ts +40 -6
  541. package/src/openapi/source.ts +1 -1
  542. package/src/ratelimit/cloudflare.ts +54 -0
  543. package/src/ratelimit/index.ts +30 -0
  544. package/src/ratelimit/memory.ts +45 -0
  545. package/src/ratelimit/runtime.ts +229 -0
  546. package/src/ratelimit/schema.ts +28 -0
  547. package/src/ratelimit/upstash.ts +30 -0
  548. package/src/ratelimit/wrangler.ts +70 -0
  549. package/src/reference/asyncapi.ts +2 -2
  550. package/src/reference/graphql.ts +5 -2
  551. package/src/reference/openapi.ts +21 -7
  552. package/src/reference/options.ts +51 -7
  553. package/src/reference/scalar.ts +33 -8
  554. package/src/registry/eject.ts +201 -28
  555. package/src/registry/registry.ts +7 -0
  556. package/src/search/adapters/version-scope.ts +30 -0
  557. package/src/search/documents.ts +93 -28
  558. package/src/search/orama-index.ts +28 -3
  559. package/src/search/pagefind-ranking.ts +39 -0
  560. package/src/search/popular.ts +2 -1
  561. package/src/search/sync/algolia.ts +15 -4
  562. package/src/search/sync/orama-cloud.ts +3 -0
  563. package/src/search/sync/typesense.ts +5 -0
  564. package/src/seo/jsonld.ts +7 -3
  565. package/src/seo/metatags.ts +67 -0
  566. package/src/theme/chrome-icons.ts +1 -0
  567. package/src/theme/color.ts +500 -0
  568. package/src/theme/entry.ts +56 -3
  569. package/src/theme/palette.ts +121 -45
  570. package/src/translate/meta.ts +68 -24
  571. package/src/translate/prompts.ts +1 -0
  572. package/src/translate/run.ts +3 -3
  573. package/src/translate/validate.ts +12 -3
  574. package/src/translate/work-list.ts +56 -17
  575. package/src/upgrade/upgrade.ts +4 -171
  576. package/dist/cli/chunk-3yce002v.js.map +0 -14
  577. package/dist/cli/chunk-5r8g91qn.js.map +0 -34
  578. package/dist/cli/chunk-6dtt0zfn.js.map +0 -10
  579. package/dist/cli/chunk-7mbqtmgb.js.map +0 -10
  580. package/dist/cli/chunk-ddndchfr.js.map +0 -14
  581. package/dist/cli/chunk-esh98wmb.js +0 -23
  582. package/dist/cli/chunk-esh98wmb.js.map +0 -10
  583. package/dist/cli/chunk-hqp2ajnh.js.map +0 -35
  584. package/dist/cli/chunk-hr8ne106.js.map +0 -13
  585. package/dist/cli/chunk-j85scx15.js.map +0 -10
  586. package/dist/cli/chunk-mqc662a6.js +0 -75
  587. package/dist/cli/chunk-mqc662a6.js.map +0 -11
  588. package/dist/cli/chunk-pbg5a4s3.js.map +0 -19
  589. package/dist/cli/chunk-qkb5a8sa.js.map +0 -10
  590. package/dist/cli/chunk-v6ya5kcb.js.map +0 -189
  591. package/dist/cli/chunk-wdrt2k2v.js.map +0 -10
  592. package/dist/cli/chunk-xh43dwgw.js.map +0 -36
  593. package/docs/content/sources.mdx +0 -338
  594. /package/dist/cli/{chunk-zp79m0ts.js.map → chunk-1n228xqb.js.map} +0 -0
  595. /package/dist/cli/{chunk-h2ez8dzb.js.map → chunk-5ffywsmb.js.map} +0 -0
  596. /package/dist/cli/{chunk-g698a744.js.map → chunk-5wj2qwn2.js.map} +0 -0
  597. /package/dist/cli/{chunk-sqw4ekg1.js.map → chunk-amrb6hgh.js.map} +0 -0
  598. /package/dist/cli/{chunk-h7k3nq3v.js.map → chunk-gp84za0h.js.map} +0 -0
  599. /package/dist/cli/{chunk-bfwp9vp6.js.map → chunk-p804f3pe.js.map} +0 -0
  600. /package/dist/cli/{chunk-00gs3wqs.js.map → chunk-ta27feev.js.map} +0 -0
  601. /package/dist/cli/{chunk-bbnwccaz.js.map → chunk-y4ykbark.js.map} +0 -0
@@ -19,13 +19,18 @@ type StringifyArgs = Parameters<typeof baseMatter.stringify>;
19
19
  /** Front matter data as gray-matter types it (`GrayMatterFile["data"]`). */
20
20
  type FrontMatterData = ReturnType<typeof baseMatter>["data"];
21
21
 
22
+ /** A YAML load result: a mapping (or list) Astro keeps as front matter data. */
23
+ const isYamlCollection = (
24
+ value: ReturnType<typeof load>
25
+ ): value is FrontMatterData => typeof value === "object" && value !== null;
26
+
22
27
  const yamlEngine = {
23
- // SAFETY: gray-matter's engine contract expects an object; a front matter
24
- // block is a YAML mapping, and js-yaml returns a scalar only for degenerate
25
- // input, which gray-matter treats the same way its bundled engine's output
26
- // is treated.
27
- parse: (input: string): object =>
28
- (load(input, { schema: YAML_SCHEMA }) ?? {}) as object,
28
+ // A block that loads to a scalar (`title` alone, or prose between two
29
+ // `---` lines) is no metadata: Astro reads it as `{}`, and so does Blume.
30
+ parse: (input: string): FrontMatterData => {
31
+ const value = load(input, { schema: YAML_SCHEMA });
32
+ return isYamlCollection(value) ? value : {};
33
+ },
29
34
  stringify: (data: FrontMatterData): string => dump(data),
30
35
  };
31
36
 
@@ -45,23 +50,21 @@ const withYamlEngine = <O extends { engines?: object } | undefined>(
45
50
 
46
51
  /**
47
52
  * True when a document's leading `---` line is a CommonMark thematic break,
48
- * not a front matter fence. Two shapes qualify (mirroring
49
- * `linesWithoutFrontMatter` in `sources/normalize.ts`):
50
- * - the next line is blank (or absent) — YAML metadata starts on the very
51
- * next line, so a gap means the body *opens* with a divider (e.g. a
52
- * Notion page whose first block is one);
53
- * - no closing `---` line follows — gray-matter would swallow the whole
54
- * document as one unclosed YAML block and hand it to js-yaml, which
55
- * crashes on ordinary Markdown (`> quote` → "a line break is expected").
53
+ * not a front matter fence: no closing `---` line follows. Astro's own front
54
+ * matter match requires that close too, while gray-matter would swallow the
55
+ * whole document as one unclosed YAML block and hand it to js-yaml, which
56
+ * crashes on ordinary Markdown (`> quote` → "a line break is expected").
57
+ *
58
+ * A blank line after the opening `---` is still front matter: Astro strips
59
+ * the block from the page either way, so reading it as a divider here would
60
+ * ignore its `draft`, `slug`, and `hidden` while its YAML leaked into search
61
+ * and llms.txt.
56
62
  */
57
63
  const opensWithThematicBreak = (input: string): boolean => {
58
- const [first = "", second] = input.split(/\r?\n/u, 2);
64
+ const [first = ""] = input.split(/\r?\n/u, 1);
59
65
  if (!/^-{3}\s*$/u.test(first)) {
60
66
  return false;
61
67
  }
62
- if (second === undefined || second.trim() === "") {
63
- return true;
64
- }
65
68
  // gray-matter closes the block at the next line-leading `---`; matching its
66
69
  // search exactly keeps this guard from firing on any document it parses.
67
70
  return !input.includes("\n---", 1);
package/src/core/graph.ts CHANGED
@@ -4,6 +4,7 @@ import {
4
4
  localizeRoute,
5
5
  resolveFallbackLocale,
6
6
  } from "./i18n.ts";
7
+ import { resolveLocalizable } from "./localizable.ts";
7
8
  import {
8
9
  validateNavIcons,
9
10
  validateNavStructure,
@@ -97,31 +98,6 @@ const localePagesFor = (
97
98
  return [...real, ...filled];
98
99
  };
99
100
 
100
- /**
101
- * Resolve a possibly-per-locale header label to the string a locale renders:
102
- * the active locale's entry, else the default locale's, else the map's first
103
- * entry (which is also what a single-locale site gets).
104
- */
105
- /** A label is either one string for every locale or a per-locale map. */
106
- const isSingleLabel = (label: LocalizableLabel): label is string =>
107
- typeof label === "string";
108
-
109
- const resolveLabel = (
110
- label: LocalizableLabel,
111
- locale: string,
112
- defaultLocale?: string
113
- ): string => {
114
- if (isSingleLabel(label)) {
115
- return label;
116
- }
117
- return (
118
- label[locale] ??
119
- (defaultLocale === undefined ? undefined : label[defaultLocale]) ??
120
- Object.values(label)[0] ??
121
- ""
122
- );
123
- };
124
-
125
101
  /**
126
102
  * An explicit sidebar with every item's `href` passed through `localize` (page
127
103
  * refs and `root`s resolve against each locale's own pages already).
@@ -154,11 +130,36 @@ const resolveTabLabels = (
154
130
  ...tab,
155
131
  items: tab.items?.map((item) => ({
156
132
  ...item,
157
- label: resolveLabel(item.label, locale, defaultLocale),
133
+ label: resolveLocalizable(item.label, locale, defaultLocale),
158
134
  })),
159
- label: resolveLabel(tab.label, locale, defaultLocale),
135
+ label: resolveLocalizable(tab.label, locale, defaultLocale),
160
136
  }));
161
137
 
138
+ /** A configured header or footer link with its label resolved for a locale. */
139
+ const withLabel = <T extends { label: LocalizableLabel }>(
140
+ link: T,
141
+ locale: string,
142
+ defaultLocale?: string
143
+ ): Omit<T, "label"> & { label: string } => ({
144
+ ...link,
145
+ label: resolveLocalizable(link.label, locale, defaultLocale),
146
+ });
147
+
148
+ /** The configured header links (plain, call to action, featured) in `locale`. */
149
+ const resolveHeaderLinks = (
150
+ navigation: BuildContentGraphOptions["navigation"],
151
+ locale: string,
152
+ defaultLocale?: string
153
+ ) => ({
154
+ actions: navigation.actions.map((link) =>
155
+ withLabel(link, locale, defaultLocale)
156
+ ),
157
+ cta: navigation.cta ? withLabel(navigation.cta, locale, defaultLocale) : null,
158
+ featured: navigation.featured.map((link) =>
159
+ withLabel(link, locale, defaultLocale)
160
+ ),
161
+ });
162
+
162
163
  /**
163
164
  * Mount a configured link authored as if the site were served at root (the
164
165
  * banner's): under `basePath`, except a route served outside the content tree
@@ -274,18 +275,22 @@ const buildLocaleNavigation = (
274
275
  ...item,
275
276
  href: localizeServed(item.href, false),
276
277
  });
277
- const { actions, cta, featured } = options.navigation;
278
+ const { actions, cta, featured } = resolveHeaderLinks(
279
+ options.navigation,
280
+ code,
281
+ i18n.defaultLocale
282
+ );
278
283
  const sidebarItems = options.navigation.sidebar.items;
279
284
 
280
285
  const navigation = buildNavigation(localePages, {
281
- actions: actions?.map(localizeHref),
286
+ actions: actions.map(localizeHref),
282
287
  basePath,
283
288
  cta: cta ? localizeHref(cta) : null,
284
289
  diagnostics,
285
290
  display: options.navigation.sidebar.display,
286
291
  extraRoutes: options.extraRoutes,
287
292
  fallbackMetaPrefix,
288
- featured: featured?.map(localizeHref),
293
+ featured: featured.map(localizeHref),
289
294
  folderMeta: options.folderMeta,
290
295
  // The localized tree root ("/" for the hidden default, "/fr" otherwise;
291
296
  // "/fr/v1.0" inside a snapshot): the tab pointing here spans the whole
@@ -415,13 +420,11 @@ const buildVersionNavigation = (
415
420
  }
416
421
  return {
417
422
  "": buildNavigation(versionPages, {
418
- actions: options.navigation.actions,
423
+ ...resolveHeaderLinks(options.navigation, ""),
419
424
  basePath: options.basePath ?? "",
420
- cta: options.navigation.cta,
421
425
  diagnostics,
422
426
  display: options.navigation.sidebar.display,
423
427
  extraRoutes: options.extraRoutes,
424
- featured: options.navigation.featured,
425
428
  folderMeta: options.folderMeta,
426
429
  localizedRoot: versionizeRoute("/", id),
427
430
  metaPrefix: id,
@@ -465,13 +468,13 @@ export const buildContentGraph = (
465
468
  ));
466
469
  } else {
467
470
  navigation = buildNavigation(currentPages, {
468
- actions: options.navigation.actions,
471
+ // No locale to prefer: a per-locale label map resolves to its first
472
+ // entry on a single-locale site.
473
+ ...resolveHeaderLinks(options.navigation, ""),
469
474
  basePath: options.basePath ?? "",
470
- cta: options.navigation.cta,
471
475
  diagnostics,
472
476
  display: options.navigation.sidebar.display,
473
477
  extraRoutes: options.extraRoutes,
474
- featured: options.navigation.featured,
475
478
  folderMeta: options.folderMeta,
476
479
  selectors: options.navigation.selectors,
477
480
  sharedFolderMeta: options.sharedFolderMeta,
@@ -55,17 +55,12 @@ export interface HeadingMarkers {
55
55
  * the heading count (mid-text brackets are ordinary prose); when a marker kind
56
56
  * repeats, the last one written wins.
57
57
  *
58
- * `isRefDefined` reports whether a link-reference definition exists for a
59
- * bracket's label (`toc`, `!toc`, `#id`): a defined bracket is a CommonMark
60
- * shortcut link, not a marker — the renderer sees a resolved `<a>` element
61
- * there and strips nothing at or before it, so the parse stops the same way.
62
- * The render-time hast plugin never passes it (resolved links are already
63
- * elements by the time it runs); the raw-source scanner does.
58
+ * Callers pass the heading's trailing text node after a Markdown parse, so a
59
+ * bracket that a link-reference definition turns into a CommonMark shortcut
60
+ * link (`[toc]` with a `[toc]: …` definition) is already an element there and
61
+ * never reaches this parse.
64
62
  */
65
- export const parseHeadingMarkers = (
66
- text: string,
67
- isRefDefined?: (label: string) => boolean
68
- ): HeadingMarkers => {
63
+ export const parseHeadingMarkers = (text: string): HeadingMarkers => {
69
64
  let remaining = text;
70
65
  let id: string | undefined;
71
66
  let toc: HeadingMarkers["toc"];
@@ -75,14 +70,6 @@ export const parseHeadingMarkers = (
75
70
  match = MARKER.exec(remaining)
76
71
  ) {
77
72
  const explicitId = match.groups.curlyId ?? match.groups.id;
78
- const label =
79
- explicitId === undefined
80
- ? `${match.groups.hide ?? ""}toc`
81
- : `#${explicitId}`;
82
- // A `{#id}` is never a shortcut link, whatever the definitions say.
83
- if (match.groups.curlyId === undefined && isRefDefined?.(label)) {
84
- break;
85
- }
86
73
  remaining = remaining.slice(0, match.index);
87
74
  if (explicitId === undefined) {
88
75
  // Stripping runs right-to-left, so keeping the first capture of each
@@ -15,6 +15,8 @@ const uiStringsObject = z.object({
15
15
  .object({
16
16
  addToCursor: z.string().default("Add to Cursor"),
17
17
  addToVscode: z.string().default("Add to VS Code"),
18
+ // A code block's button that opens the assistant with the code attached.
19
+ askAboutCode: z.string().default("Ask about this code"),
18
20
  connectMcp: z.string().default("Connect to MCP"),
19
21
  copied: z.string().default("Copied!"),
20
22
  copyClaudeCode: z.string().default("Copy Claude Code command"),
@@ -47,11 +49,32 @@ const uiStringsObject = z.object({
47
49
  copy: z.string().default("Copy conversation"),
48
50
  empty: z.string().default("Ask a question about the docs."),
49
51
  error: z.string().default("Sorry, something went wrong."),
52
+ // The question sent about an attached code block when the reader
53
+ // writes none.
54
+ explainCode: z.string().default("Explain this code."),
50
55
  label: z.string().default("Ask a question"),
56
+ // The header trigger's tooltip.
57
+ open: z.string().default("Open assistant"),
51
58
  placeholder: z.string().default("Ask a question…"),
59
+ // The answer when the rate limit (`rateLimit`) turns a question away.
60
+ rateLimited: z
61
+ .string()
62
+ .default(
63
+ "You've asked a lot of questions. Try again in a few minutes."
64
+ ),
65
+ // The attached code block's dismiss button.
66
+ removeCode: z.string().default("Remove code"),
52
67
  send: z.string().default("Send"),
68
+ // The link to your support channel (`ai.assistant.support`), and the
69
+ // subject of an email it starts.
70
+ support: z.string().default("Contact support"),
71
+ supportSubject: z.string().default("Question from the docs"),
53
72
  tip: z.string().default("Tip: You can open and close chat with"),
54
73
  title: z.string().default("Assistant"),
74
+ // The answer when the bot check (`ai.assistant.captcha`) fails.
75
+ verifyFailed: z
76
+ .string()
77
+ .default("We couldn't check that you're human. Try again."),
55
78
  you: z.string().default("You"),
56
79
  })
57
80
  .prefault({}),
@@ -70,6 +93,23 @@ const uiStringsObject = z.object({
70
93
  title: z.string().default("Changelog"),
71
94
  })
72
95
  .prefault({}),
96
+ // The built-in cookie consent banner (`consent: native()`) and the footer
97
+ // link that reopens a consent manager's preferences.
98
+ consent: z
99
+ .object({
100
+ accept: z.string().default("Accept"),
101
+ decline: z.string().default("Decline"),
102
+ // The banner's accessible name.
103
+ label: z.string().default("Cookie consent"),
104
+ message: z
105
+ .string()
106
+ .default(
107
+ "We'd like to use cookies to understand how these docs are used."
108
+ ),
109
+ policy: z.string().default("Privacy policy"),
110
+ settings: z.string().default("Cookie settings"),
111
+ })
112
+ .prefault({}),
73
113
  content: z
74
114
  .object({
75
115
  // `<Component>`'s source tab.
@@ -89,7 +129,12 @@ const uiStringsObject = z.object({
89
129
  prop: z.string().default("Prop"),
90
130
  // `<Tabs dropdown>`: the accessible name of the tab picker.
91
131
  selectTab: z.string().default("Select tab"),
92
- // `<Expandable>`'s toggle when it sets no `title`.
132
+ // `<View>`: the accessible name of the page's view picker.
133
+ selectView: z.string().default("Select view"),
134
+ // An expanded `expandable` code block's toggle.
135
+ showLess: z.string().default("Show less"),
136
+ // `<Expandable>`'s toggle when it sets no `title`, and a collapsed
137
+ // `expandable` code block's.
93
138
  showMore: z.string().default("Show more"),
94
139
  stars: z.string().default("Stars"),
95
140
  // An untitled tab's label; `{n}` is replaced with its position.
@@ -102,8 +147,11 @@ const uiStringsObject = z.object({
102
147
  .prefault({}),
103
148
  feedback: z
104
149
  .object({
150
+ // The written comment box after the rating (`feedback.comments`).
151
+ comment: z.string().default("Tell us more (optional)"),
105
152
  no: z.string().default("No"),
106
153
  question: z.string().default("Was this page helpful?"),
154
+ send: z.string().default("Send"),
107
155
  thanks: z.string().default("Thanks for your feedback!"),
108
156
  yes: z.string().default("Yes"),
109
157
  })
@@ -114,6 +162,36 @@ const uiStringsObject = z.object({
114
162
  untranslated: z.string().default("Not translated"),
115
163
  })
116
164
  .prefault({}),
165
+ narration: z
166
+ .object({
167
+ // Spoken, not shown: the cues that introduce a callout of each type, a
168
+ // step (`{n}` is its number), a tab (`{title}` is its label), and a
169
+ // collapsible section. End each with a full stop so it reads as its own
170
+ // sentence.
171
+ cueDanger: z.string().default("Danger."),
172
+ cueInfo: z.string().default("Info."),
173
+ cueNote: z.string().default("Note."),
174
+ cueSection: z.string().default("Expandable section."),
175
+ cueStep: z.string().default("Step {n}."),
176
+ cueSuccess: z.string().default("Success."),
177
+ cueTab: z.string().default("{title} tab."),
178
+ cueTip: z.string().default("Tip."),
179
+ cueWarning: z.string().default("Warning."),
180
+ error: z.string().default("The narration couldn't load."),
181
+ // Resumes following the sentence being read after the reader scrolls away.
182
+ follow: z.string().default("Follow along"),
183
+ label: z.string().default("Listen to this page"),
184
+ // The estimated listening time; `{n}` is whole minutes.
185
+ minutes: z.string().default("{n} min"),
186
+ next: z.string().default("Next sentence"),
187
+ pause: z.string().default("Pause"),
188
+ play: z.string().default("Play"),
189
+ previous: z.string().default("Previous sentence"),
190
+ progress: z.string().default("Narration progress"),
191
+ speed: z.string().default("Playback speed"),
192
+ stop: z.string().default("Stop listening"),
193
+ })
194
+ .prefault({}),
117
195
  nav: z
118
196
  .object({
119
197
  back: z.string().default("Back"),
@@ -128,7 +206,7 @@ const uiStringsObject = z.object({
128
206
  primary: z.string().default("Primary"),
129
207
  sections: z.string().default("Sections"),
130
208
  toggleNavigation: z.string().default("Toggle navigation"),
131
- toggleTheme: z.string().default("Toggle color theme"),
209
+ toggleTheme: z.string().default("Toggle theme"),
132
210
  })
133
211
  .prefault({}),
134
212
  notFound: z
@@ -154,6 +232,8 @@ const uiStringsObject = z.object({
154
232
  next: z.string().default("Next"),
155
233
  pagination: z.string().default("Pagination"),
156
234
  previous: z.string().default("Previous"),
235
+ // The heading over a page's `related` links.
236
+ related: z.string().default("Related pages"),
157
237
  skipToContent: z.string().default("Skip to content"),
158
238
  })
159
239
  .prefault({}),
package/src/core/i18n.ts CHANGED
@@ -219,11 +219,31 @@ export const localeTargetPath = (
219
219
  return `${folder}/${rel}`;
220
220
  };
221
221
 
222
+ /**
223
+ * The warning for a top-level folder named after the default locale
224
+ * (`docs/en/` when `defaultLocale` is `en`). Only non-default codes are
225
+ * locale folders — the default locale lives at the content root — so the
226
+ * folder is ordinary default-locale content, published one level down at
227
+ * `/en/…` (`/en/en/…` with the default prefix shown) and filled into every
228
+ * other locale at `/fr/en/…`.
229
+ */
230
+ const defaultLocaleFolderDiagnostic = (
231
+ folder: string,
232
+ i18n: ResolvedI18nConfig
233
+ ): Diagnostic => ({
234
+ code: "BLUME_I18N_DEFAULT_LOCALE_FOLDER",
235
+ message: `Folder "${folder}/" is named after the default locale, but "${i18n.defaultLocale}" pages live at the content root — its pages publish at ${localizeRoute(`/${folder}`, i18n.defaultLocale, i18n)}/… instead.`,
236
+ severity: "warning",
237
+ suggestion: `Move the contents of "${folder}/" up to the content root; only the other locales get a folder.`,
238
+ });
239
+
222
240
  /**
223
241
  * Warn about top-level content folders that look like a locale (a code Blume
224
242
  * recognizes) but aren't declared in `i18n.locales`. Without this they're
225
243
  * silently treated as default-locale content under a `/<code>/…` route, which
226
244
  * is almost never intended — usually a translation that wasn't registered.
245
+ * A folder named after the default locale gets the same treatment (see
246
+ * {@link defaultLocaleFolderDiagnostic}).
227
247
  */
228
248
  export const i18nDiagnostics = (
229
249
  pages: PageRecord[],
@@ -242,9 +262,17 @@ export const i18nDiagnostics = (
242
262
  // Inside a version snapshot the locale dir sits one level deeper
243
263
  // (`v1.0/fr/guide.md`), so a configured version segment is skipped first.
244
264
  const parts = page.source.ref.split("/");
245
- const first = (
246
- parts[0] && versionDirs.has(parts[0]) ? parts[1] : parts[0]
247
- )?.toLowerCase();
265
+ const folder = parts[0] && versionDirs.has(parts[0]) ? parts[1] : parts[0];
266
+ const first = folder?.toLowerCase();
267
+ if (
268
+ folder !== undefined &&
269
+ first === i18n.defaultLocale.toLowerCase() &&
270
+ !seen.has(first)
271
+ ) {
272
+ seen.add(first);
273
+ diagnostics.push(defaultLocaleFolderDiagnostic(folder, i18n));
274
+ continue;
275
+ }
248
276
  if (
249
277
  first &&
250
278
  !seen.has(first) &&
@@ -7,6 +7,7 @@ import type { FenceState } from "./code-fences.ts";
7
7
  import matter from "./frontmatter.ts";
8
8
  import { INLINE_CODE, MD_IMAGE, targetOffsetIn } from "./sources/normalize.ts";
9
9
  import type { Diagnostic } from "./types.ts";
10
+ import { substituteVariables } from "./variables.ts";
10
11
 
11
12
  /**
12
13
  * Content includes: `<include>./relative.mdx</include>` on a line of its own
@@ -98,6 +99,12 @@ export interface IncludeAttributes {
98
99
  lang?: string;
99
100
  /** Fence meta string in code-block mode (e.g. `title="lib.ts"`). */
100
101
  meta?: string;
102
+ /**
103
+ * Every other attribute with a value: the snippet's own variables.
104
+ * `<include plan="Pro">` makes `{{plan}}` in the included file read `Pro`,
105
+ * ahead of any site-wide variable of the same name.
106
+ */
107
+ props?: Record<string, string>;
101
108
  }
102
109
 
103
110
  /** A parsed include statement. */
@@ -108,8 +115,8 @@ export interface IncludeStatement {
108
115
 
109
116
  /**
110
117
  * Parse one trimmed line as an include statement, or `null` when it isn't
111
- * one. Only `lang` and `meta` are meaningful; unknown attributes parse and
112
- * are ignored so a future attribute degrades gracefully on older versions.
118
+ * one. `lang` and `meta` shape a code include; every other attribute with a
119
+ * value becomes a prop, a variable scoped to the included file.
113
120
  */
114
121
  export const parseIncludeStatement = (
115
122
  line: string
@@ -128,6 +135,9 @@ export const parseIncludeStatement = (
128
135
  if (name === "meta" && value) {
129
136
  attributes.meta = value;
130
137
  }
138
+ if (name && name !== "lang" && name !== "meta" && value !== undefined) {
139
+ attributes.props = { ...attributes.props, [name]: value };
140
+ }
131
141
  }
132
142
  return { attributes, target: match.groups.target ?? "" };
133
143
  };
@@ -519,8 +529,15 @@ const expandStatement = async (
519
529
  lines.shift();
520
530
  origins.shift();
521
531
  }
532
+ // Props replace their `{{name}}` references line by line, so every line
533
+ // keeps its origin; they reach the file's own nested includes too.
534
+ const { props } = statement.attributes;
522
535
  return {
523
- lines: rebaseImages(lines, dirname(path), dirname(filePath)),
536
+ lines: rebaseImages(
537
+ props ? lines.map((text) => substituteVariables(text, props)) : lines,
538
+ dirname(path),
539
+ dirname(filePath)
540
+ ),
524
541
  origins,
525
542
  };
526
543
  };
@@ -1,6 +1,7 @@
1
1
  import { execFileSync } from "node:child_process";
2
+ import { realpathSync } from "node:fs";
2
3
 
3
- import { relative } from "pathe";
4
+ import { normalize, relative } from "pathe";
4
5
 
5
6
  import type { Diagnostic } from "./types.ts";
6
7
 
@@ -58,6 +59,23 @@ export const parseGitLog = (output: string): Map<string, string> => {
58
59
  return times;
59
60
  };
60
61
 
62
+ /**
63
+ * `path` with every symlink resolved, the spelling git prints paths in: a
64
+ * project opened through a link (`/tmp/site` on macOS, where `/tmp` links to
65
+ * `/private/tmp`) gets `/private/tmp/site` from `rev-parse --show-toplevel`,
66
+ * so the project's own spelling would compare as outside the repository and
67
+ * every page would go undated. The native realpath also expands a Windows
68
+ * 8.3 short name (`RUNNER~1`), which git does too. A path that doesn't exist
69
+ * stays as written.
70
+ */
71
+ export const realPath = (path: string): string => {
72
+ try {
73
+ return normalize(realpathSync.native(path));
74
+ } catch {
75
+ return path;
76
+ }
77
+ };
78
+
61
79
  /**
62
80
  * The toplevel of the repository containing `root`, or null when git is
63
81
  * unavailable or the project isn't a repo. Callers use it to decide which
@@ -108,6 +126,10 @@ export const gitLastModifiedTimes = (
108
126
  if (gitRoot === null) {
109
127
  return new Map();
110
128
  }
129
+ // Git names paths by their real location, so the pathspecs and the paths
130
+ // read back against the log are compared in that spelling too (see
131
+ // `realPath`).
132
+ const top = realPath(gitRoot);
111
133
  try {
112
134
  const output = execFileSync(
113
135
  // oxlint-disable-next-line sonarjs/no-os-command-from-path -- git is a required dev-tool dependency resolved from PATH
@@ -121,14 +143,14 @@ export const gitLastModifiedTimes = (
121
143
  "--format=%x00%cI",
122
144
  "--name-only",
123
145
  "--",
124
- ...contentRoots,
146
+ ...contentRoots.map(realPath),
125
147
  ],
126
148
  { encoding: "utf-8", env: gitEnv(), maxBuffer: 256 * 1024 * 1024 }
127
149
  );
128
150
  const byRepoPath = parseGitLog(output);
129
151
  const result = new Map<string, string>();
130
152
  for (const sourcePath of sourcePaths) {
131
- const iso = byRepoPath.get(relative(gitRoot, sourcePath));
153
+ const iso = byRepoPath.get(relative(top, realPath(sourcePath)));
132
154
  if (iso) {
133
155
  result.set(sourcePath, iso);
134
156
  }
package/src/core/links.ts CHANGED
@@ -10,6 +10,7 @@ import {
10
10
  import type { LocaleRouting } from "./i18n.ts";
11
11
  import { localizeLinkPath } from "./locale-links.ts";
12
12
  import { gradeExternal, probeAll } from "./probe.ts";
13
+ import { isPatternPath, pathsUnderPattern } from "./redirect-patterns.ts";
13
14
  import type {
14
15
  ContentGraph,
15
16
  Diagnostic,
@@ -87,6 +88,8 @@ interface LinkContext {
87
88
  publicDir: string | null;
88
89
  /** Normalized `redirect.from` paths — valid targets that resolve at runtime. */
89
90
  redirects: Set<string>;
91
+ /** Pattern `redirect.from`s (`/beta/:slug*`), based like `redirects`. */
92
+ redirectPatterns: string[];
90
93
  routes: Set<string>;
91
94
  }
92
95
 
@@ -322,10 +325,16 @@ const checkPathLink = (
322
325
  if (ctx.extraRoutes.has(route)) {
323
326
  return null;
324
327
  }
325
- // A configured `redirect.from` resolves at runtime, so it's a valid target.
328
+ // A configured `redirect.from`, or a path a pattern `from` covers, resolves
329
+ // at runtime, so it's a valid target.
326
330
  // Its destination (and any anchor there) is validated on its own page, so we
327
331
  // don't follow the redirect to check the fragment here.
328
- if (ctx.redirects.has(route)) {
332
+ if (
333
+ ctx.redirects.has(route) ||
334
+ ctx.redirectPatterns.some(
335
+ (from) => pathsUnderPattern(from, [route]).length > 0
336
+ )
337
+ ) {
329
338
  return null;
330
339
  }
331
340
 
@@ -585,6 +594,11 @@ export const validateLinks = async (
585
594
  fileRoutes: buildFileRouteIndex(graph.pages, options.i18n ?? null),
586
595
  i18n: options.i18n ?? null,
587
596
  publicDir: options.publicDir,
597
+ redirectPatterns: (options.redirects ?? []).flatMap((redirect) =>
598
+ isPatternPath(redirect.from)
599
+ ? [withBasePath(basePath, redirect.from)]
600
+ : []
601
+ ),
588
602
  redirects: new Set(
589
603
  (options.redirects ?? []).map((redirect) =>
590
604
  toRoute(withBasePath(basePath, redirect.from))
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  isInternalPath,
3
+ mountBasePath,
3
4
  normalizePath,
4
5
  stripBasePath,
5
6
  withBasePath,
@@ -120,7 +121,10 @@ export const localizeHref = (
120
121
  if (localized === based) {
121
122
  return href;
122
123
  }
123
- return `${withBasePath(options.deployBase, localized)}${suffix}`;
124
+ // The base came off above, so it goes back on unconditionally: a localized
125
+ // path that starts with the base's own name (`/ja/…` under base `/ja`) is
126
+ // still served under it.
127
+ return `${mountBasePath(options.deployBase, localized)}${suffix}`;
124
128
  };
125
129
 
126
130
  /** Every `<a …>` opening tag; `\s` keeps `<abbr>`/`<astro-island>` out. */
@@ -0,0 +1,26 @@
1
+ import type { LocalizableLabel } from "./schema.ts";
2
+
3
+ /** A label is either one string for every locale or a per-locale map. */
4
+ const isSingleLabel = (label: LocalizableLabel): label is string =>
5
+ typeof label === "string";
6
+
7
+ /**
8
+ * Resolve a possibly-per-locale label to the string a locale renders: the
9
+ * active locale's entry, else the default locale's, else the map's first
10
+ * entry (which is also what a single-locale site gets).
11
+ */
12
+ export const resolveLocalizable = (
13
+ label: LocalizableLabel,
14
+ locale?: string,
15
+ defaultLocale?: string
16
+ ): string => {
17
+ if (isSingleLabel(label)) {
18
+ return label;
19
+ }
20
+ return (
21
+ (locale === undefined ? undefined : label[locale]) ??
22
+ (defaultLocale === undefined ? undefined : label[defaultLocale]) ??
23
+ Object.values(label)[0] ??
24
+ ""
25
+ );
26
+ };