blume 2.0.1 → 2.0.3

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 (492) hide show
  1. package/CHANGELOG.md +147 -0
  2. package/dist/cli/{chunk-qwsrynx5.js → chunk-1d7ve1dm.js} +38 -19
  3. package/dist/cli/{chunk-qwsrynx5.js.map → chunk-1d7ve1dm.js.map} +4 -4
  4. package/dist/cli/{chunk-s6jhgk0q.js → chunk-2eytanqx.js} +2 -2
  5. package/dist/cli/{chunk-kdp5q7ke.js → chunk-2hsdwb9n.js} +18 -19
  6. package/dist/cli/{chunk-kdp5q7ke.js.map → chunk-2hsdwb9n.js.map} +2 -2
  7. package/dist/cli/{chunk-hdpx1tax.js → chunk-2z928egk.js} +5 -5
  8. package/dist/cli/{chunk-jwyddg7y.js → chunk-35d4wj9f.js} +30 -24
  9. package/dist/cli/chunk-35d4wj9f.js.map +15 -0
  10. package/dist/cli/{chunk-ah61y8py.js → chunk-364znk6q.js} +2 -2
  11. package/dist/cli/{chunk-jts8mvcz.js → chunk-3em5wd2y.js} +25 -6
  12. package/dist/cli/{chunk-jts8mvcz.js.map → chunk-3em5wd2y.js.map} +3 -3
  13. package/dist/cli/{chunk-s1p84fyh.js → chunk-5f86nr5m.js} +63 -22
  14. package/dist/cli/chunk-5f86nr5m.js.map +11 -0
  15. package/dist/cli/{chunk-2hn4b8z7.js → chunk-5m5nmvyq.js} +170 -76
  16. package/dist/cli/chunk-5m5nmvyq.js.map +13 -0
  17. package/dist/cli/{chunk-epjnccmv.js → chunk-5xvm6tfj.js} +18 -15
  18. package/dist/cli/chunk-5xvm6tfj.js.map +10 -0
  19. package/dist/cli/{chunk-fz5wtpmh.js → chunk-6dsbexzp.js} +18 -15
  20. package/dist/cli/chunk-6dsbexzp.js.map +10 -0
  21. package/dist/cli/{chunk-vtk4a6dg.js → chunk-8ktnccpt.js} +1 -1
  22. package/dist/cli/{chunk-6hsn950k.js → chunk-9t7a85s3.js} +178 -48
  23. package/dist/cli/chunk-9t7a85s3.js.map +10 -0
  24. package/dist/cli/{chunk-fxypxtvm.js → chunk-a58773jm.js} +2 -2
  25. package/dist/cli/{chunk-mb2919y2.js → chunk-acanzt5p.js} +17 -6
  26. package/dist/cli/chunk-acanzt5p.js.map +10 -0
  27. package/dist/cli/{chunk-27g6wdth.js → chunk-akbpwfxc.js} +90 -26
  28. package/dist/cli/chunk-akbpwfxc.js.map +10 -0
  29. package/dist/cli/{chunk-wm7js3j9.js → chunk-b07cmahc.js} +2 -2
  30. package/dist/cli/{chunk-wgm7m9qk.js → chunk-c8chx29p.js} +179 -51
  31. package/dist/cli/chunk-c8chx29p.js.map +36 -0
  32. package/dist/cli/{chunk-qs4q5p4e.js → chunk-crgn1q09.js} +32 -14
  33. package/dist/cli/chunk-crgn1q09.js.map +10 -0
  34. package/dist/cli/chunk-e04dxsz1.js +39 -0
  35. package/dist/cli/chunk-e04dxsz1.js.map +10 -0
  36. package/dist/cli/{chunk-yt5n7ppj.js → chunk-ey84smr6.js} +17 -7
  37. package/dist/cli/chunk-ey84smr6.js.map +10 -0
  38. package/dist/cli/{chunk-f2z5v128.js → chunk-g4hq16wv.js} +14 -15
  39. package/dist/cli/{chunk-f2z5v128.js.map → chunk-g4hq16wv.js.map} +2 -2
  40. package/dist/cli/{chunk-fa25z98p.js → chunk-ga0pf4aj.js} +9 -8
  41. package/dist/cli/chunk-ga0pf4aj.js.map +11 -0
  42. package/dist/cli/{chunk-kpf8rrjc.js → chunk-hm3vjy5s.js} +109 -58
  43. package/dist/cli/chunk-hm3vjy5s.js.map +19 -0
  44. package/dist/cli/{chunk-fs23ddbb.js → chunk-j85vccga.js} +625 -750
  45. package/dist/cli/chunk-j85vccga.js.map +36 -0
  46. package/dist/cli/{chunk-ch6g3ar0.js → chunk-p3v96n38.js} +6 -6
  47. package/dist/cli/{chunk-ch6g3ar0.js.map → chunk-p3v96n38.js.map} +3 -3
  48. package/dist/cli/{chunk-q5163e60.js → chunk-p73c0m7w.js} +21 -19
  49. package/dist/cli/chunk-p73c0m7w.js.map +11 -0
  50. package/dist/cli/{chunk-dh8cwk36.js → chunk-pehfxfta.js} +24 -9
  51. package/dist/cli/chunk-pehfxfta.js.map +10 -0
  52. package/dist/cli/{chunk-qkqwkpte.js → chunk-pv29h0wf.js} +3688 -1313
  53. package/dist/cli/chunk-pv29h0wf.js.map +190 -0
  54. package/dist/cli/{chunk-6vm74dry.js → chunk-q58y5e6a.js} +10 -10
  55. package/dist/cli/{chunk-6vm74dry.js.map → chunk-q58y5e6a.js.map} +3 -3
  56. package/dist/cli/{chunk-zxcczpyx.js → chunk-r20tn01b.js} +1 -1
  57. package/dist/cli/{chunk-m3vmjgmq.js → chunk-r9rcc4w7.js} +16 -8
  58. package/dist/cli/chunk-r9rcc4w7.js.map +10 -0
  59. package/dist/cli/{chunk-5shv93fd.js → chunk-tkacnehg.js} +2 -2
  60. package/dist/cli/{chunk-zxh4d9vy.js → chunk-tzmab476.js} +4 -4
  61. package/dist/cli/{chunk-yw7dm696.js → chunk-vg9r4eb9.js} +24 -11
  62. package/dist/cli/chunk-vg9r4eb9.js.map +14 -0
  63. package/dist/cli/{chunk-79jhk4py.js → chunk-wrr3j9w9.js} +260 -132
  64. package/dist/cli/chunk-wrr3j9w9.js.map +35 -0
  65. package/dist/cli/{chunk-6crbhc3x.js → chunk-yfyb25rh.js} +54 -27
  66. package/dist/cli/chunk-yfyb25rh.js.map +14 -0
  67. package/dist/cli/index.js +162 -35
  68. package/dist/cli/index.js.map +4 -4
  69. package/dist/types/ai/agent-surface.d.ts +32 -0
  70. package/dist/types/ai/api-catalog.d.ts +7 -1
  71. package/dist/types/ai/ask-context.d.ts +7 -0
  72. package/dist/types/ai/component-markdown.d.ts +4 -4
  73. package/dist/types/ai/link-headers.d.ts +8 -1
  74. package/dist/types/ai/openapi-components.d.ts +5 -2
  75. package/dist/types/ai/relative-links.d.ts +11 -4
  76. package/dist/types/ai/skills.d.ts +4 -1
  77. package/dist/types/ai/static-expression.d.ts +28 -0
  78. package/dist/types/ai/tar.d.ts +1 -3
  79. package/dist/types/analytics/databuddy.d.ts +43 -0
  80. package/dist/types/analytics/index.d.ts +4 -0
  81. package/dist/types/analytics/one-dollar-stats.d.ts +48 -0
  82. package/dist/types/analytics/schema.d.ts +28 -0
  83. package/dist/types/astro/integration.d.ts +3 -2
  84. package/dist/types/cli/env.d.ts +5 -0
  85. package/dist/types/cli/init/scaffold.d.ts +19 -3
  86. package/dist/types/cli/init/starter-spec.d.ts +11 -0
  87. package/dist/types/core/base-path.d.ts +24 -1
  88. package/dist/types/core/config-input.d.ts +13 -11
  89. package/dist/types/core/config.d.ts +2 -2
  90. package/dist/types/core/directive-diagnostics.d.ts +12 -0
  91. package/dist/types/core/graph.d.ts +2 -0
  92. package/dist/types/core/heading-markers.d.ts +5 -7
  93. package/dist/types/core/i18n-ui.d.ts +31 -0
  94. package/dist/types/core/i18n.d.ts +9 -1
  95. package/dist/types/core/last-modified.d.ts +10 -0
  96. package/dist/types/core/links.d.ts +3 -1
  97. package/dist/types/core/load-module.d.ts +10 -0
  98. package/dist/types/core/locale-links.d.ts +12 -2
  99. package/dist/types/core/meta.d.ts +13 -1
  100. package/dist/types/core/nav-diagnostics.d.ts +10 -0
  101. package/dist/types/core/navigation.d.ts +38 -0
  102. package/dist/types/core/ordering-prefix.d.ts +4 -0
  103. package/dist/types/core/safe-links.d.ts +3 -1
  104. package/dist/types/core/schema.d.ts +59 -13
  105. package/dist/types/core/sources/github-releases.d.ts +5 -0
  106. package/dist/types/core/sources/lower.d.ts +38 -13
  107. package/dist/types/core/sources/normalize.d.ts +23 -2
  108. package/dist/types/core/sources/remote.d.ts +11 -1
  109. package/dist/types/core/sources/resolve.d.ts +12 -0
  110. package/dist/types/core/sources/types.d.ts +31 -0
  111. package/dist/types/core/sources/watch.d.ts +12 -5
  112. package/dist/types/core/standard-schema.d.ts +5 -0
  113. package/dist/types/core/types.d.ts +9 -0
  114. package/dist/types/deploy/adapters/node.d.ts +5 -2
  115. package/dist/types/deploy/adapters/types.d.ts +7 -0
  116. package/dist/types/deploy/artifacts.d.ts +6 -4
  117. package/dist/types/deploy/cloudflare-negotiation.d.ts +3 -2
  118. package/dist/types/deploy/headers.d.ts +36 -7
  119. package/dist/types/deploy/node-headers.d.ts +43 -8
  120. package/dist/types/deploy/platforms/netlify.d.ts +27 -2
  121. package/dist/types/deploy/platforms/node.d.ts +6 -5
  122. package/dist/types/deploy/platforms/types.d.ts +15 -0
  123. package/dist/types/deploy/platforms/vercel.d.ts +3 -2
  124. package/dist/types/deploy/redirects.d.ts +31 -15
  125. package/dist/types/deploy/vercel-negotiation.d.ts +3 -2
  126. package/dist/types/markdown/directives.d.ts +62 -0
  127. package/dist/types/markdown/features.d.ts +21 -0
  128. package/dist/types/markdown/mdast.d.ts +63 -0
  129. package/dist/types/openapi/asyncapi.d.ts +4 -2
  130. package/dist/types/openapi/model.d.ts +20 -6
  131. package/dist/types/search/sync/algolia.d.ts +3 -1
  132. package/docs/01-quickstart.mdx +3 -2
  133. package/docs/02-deployment.mdx +20 -9
  134. package/docs/08-faq.mdx +10 -3
  135. package/docs/advanced/changelog.mdx +1 -1
  136. package/docs/advanced/custom-pages.mdx +7 -5
  137. package/docs/cli/audit.mdx +19 -3
  138. package/docs/cli/doctor.mdx +2 -2
  139. package/docs/cli/evals.mdx +4 -4
  140. package/docs/cli/index.mdx +4 -1
  141. package/docs/cli/translate.mdx +4 -4
  142. package/docs/cli/version.mdx +1 -1
  143. package/docs/configuration/analytics.mdx +42 -2
  144. package/docs/configuration/assistant.mdx +1 -1
  145. package/docs/configuration/customization.mdx +6 -3
  146. package/docs/configuration/index.mdx +5 -3
  147. package/docs/configuration/search.mdx +2 -2
  148. package/docs/content/components.mdx +2 -2
  149. package/docs/content/frontmatter.mdx +5 -1
  150. package/docs/content/i18n.mdx +3 -1
  151. package/docs/content/index.mdx +8 -4
  152. package/docs/content/islands.mdx +1 -1
  153. package/docs/content/meta.mdx +7 -3
  154. package/docs/content/navigation.mdx +29 -4
  155. package/docs/content/sources.mdx +18 -16
  156. package/docs/content/syntax.mdx +14 -0
  157. package/docs/content/versioning.mdx +2 -1
  158. package/docs/discoverability/agent-discovery.mdx +22 -8
  159. package/docs/discoverability/index.mdx +4 -3
  160. package/docs/discoverability/llms-txt.mdx +2 -5
  161. package/docs/discoverability/markdown.mdx +6 -4
  162. package/docs/discoverability/mcp.mdx +4 -0
  163. package/docs/discoverability/metadata.mdx +3 -2
  164. package/docs/discoverability/open-graph.mdx +8 -4
  165. package/docs/discoverability/rss.mdx +4 -2
  166. package/docs/references/asyncapi.mdx +2 -2
  167. package/docs/references/graphql.mdx +2 -2
  168. package/docs/references/openapi.mdx +6 -4
  169. package/package.json +1 -1
  170. package/skills/blume-migrate/SKILL.md +6 -6
  171. package/skills/blume-migrate/references/docusaurus.md +6 -6
  172. package/skills/blume-migrate/references/fumadocs.md +1 -1
  173. package/skills/blume-migrate/references/mintlify.md +3 -3
  174. package/skills/blume-migrate/references/monorepo.md +1 -1
  175. package/skills/blume-migrate/references/nextra.md +2 -2
  176. package/skills/blume-migrate/references/starlight.md +5 -5
  177. package/skills/blume-migrate/scripts/mintlify-codemod.mjs +9 -4
  178. package/skills/blume-update-docs/references/audit-checklist.md +1 -1
  179. package/src/ai/agent-readability.ts +2 -2
  180. package/src/ai/agent-surface.ts +56 -0
  181. package/src/ai/ai-catalog.ts +6 -2
  182. package/src/ai/api/handlers.ts +2 -2
  183. package/src/ai/api/spec.ts +17 -4
  184. package/src/ai/api-catalog.ts +13 -3
  185. package/src/ai/ask-context.ts +14 -2
  186. package/src/ai/ask-data.ts +26 -12
  187. package/src/ai/changelog-markdown.ts +2 -2
  188. package/src/ai/component-markdown.ts +43 -37
  189. package/src/ai/link-headers.ts +22 -8
  190. package/src/ai/llms.ts +22 -10
  191. package/src/ai/markdown.ts +23 -11
  192. package/src/ai/mcp/discovery.ts +2 -2
  193. package/src/ai/mcp/query.ts +2 -2
  194. package/src/ai/mcp/server.ts +80 -9
  195. package/src/ai/openapi-components.ts +22 -6
  196. package/src/ai/relative-links.ts +99 -15
  197. package/src/ai/serializers.ts +2 -1
  198. package/src/ai/skills.ts +14 -1
  199. package/src/ai/static-expression.ts +416 -0
  200. package/src/ai/tar.ts +139 -12
  201. package/src/ai/visibility.ts +45 -14
  202. package/src/analytics/databuddy.ts +67 -0
  203. package/src/analytics/head.ts +8 -0
  204. package/src/analytics/index.ts +7 -0
  205. package/src/analytics/one-dollar-stats.ts +86 -0
  206. package/src/analytics/posthog.ts +21 -3
  207. package/src/analytics/schema.ts +4 -0
  208. package/src/astro/generate.ts +72 -28
  209. package/src/astro/include-hmr.ts +31 -12
  210. package/src/astro/include-refresh.ts +19 -8
  211. package/src/astro/integration.ts +108 -14
  212. package/src/astro/runtime-modules.ts +28 -13
  213. package/src/astro/templates.ts +139 -57
  214. package/src/audit/catalog.ts +2 -2
  215. package/src/audit/checks/assets.ts +25 -5
  216. package/src/audit/checks/content.ts +20 -2
  217. package/src/audit/checks/i18n.ts +46 -8
  218. package/src/audit/checks/indexability.ts +39 -19
  219. package/src/audit/checks/links.ts +22 -1
  220. package/src/audit/checks/llms.ts +6 -3
  221. package/src/audit/checks/network.ts +3 -1
  222. package/src/audit/checks/og-image.ts +10 -0
  223. package/src/audit/checks/robots.ts +6 -1
  224. package/src/audit/checks/sitemap.ts +56 -31
  225. package/src/audit/checks/social.ts +21 -2
  226. package/src/audit/crawl.ts +88 -15
  227. package/src/audit/graph.ts +3 -1
  228. package/src/audit/report.ts +54 -17
  229. package/src/audit/run.ts +13 -9
  230. package/src/audit/snapshot.ts +5 -0
  231. package/src/audit/types.ts +18 -0
  232. package/src/audit/url.ts +37 -6
  233. package/src/cli/args.ts +32 -0
  234. package/src/cli/build-failure.ts +50 -0
  235. package/src/cli/commands/audit.ts +11 -1
  236. package/src/cli/commands/build.ts +19 -5
  237. package/src/cli/commands/check.ts +2 -0
  238. package/src/cli/commands/doctor.ts +3 -1
  239. package/src/cli/commands/eval.ts +20 -18
  240. package/src/cli/commands/init.ts +15 -5
  241. package/src/cli/commands/preview.ts +15 -0
  242. package/src/cli/commands/sync.ts +2 -0
  243. package/src/cli/commands/translate.ts +18 -18
  244. package/src/cli/commands/upgrade.ts +11 -0
  245. package/src/cli/commands/validate.ts +3 -1
  246. package/src/cli/dev-lock.ts +157 -37
  247. package/src/cli/eject-scripts.ts +32 -7
  248. package/src/cli/env.ts +12 -1
  249. package/src/cli/init/scaffold.ts +117 -11
  250. package/src/cli/init/starter-spec.ts +235 -0
  251. package/src/cli/report-format.ts +11 -6
  252. package/src/components/colors.ts +19 -0
  253. package/src/components/content/AccordionItem.astro +26 -22
  254. package/src/components/content/Badge.astro +7 -12
  255. package/src/components/content/Card.astro +2 -2
  256. package/src/components/content/Component.astro +11 -5
  257. package/src/components/content/Expandable.astro +5 -1
  258. package/src/components/content/Frame.astro +2 -7
  259. package/src/components/content/GithubInfo.astro +12 -2
  260. package/src/components/content/Prompt.astro +2 -7
  261. package/src/components/content/Tab.astro +0 -1
  262. package/src/components/content/Tabs.astro +58 -5
  263. package/src/components/content/Tile.astro +1 -1
  264. package/src/components/content/Tooltip.astro +69 -7
  265. package/src/components/content/Tree.astro +7 -2
  266. package/src/components/content/TypeTable.astro +10 -5
  267. package/src/components/content/Update.astro +8 -2
  268. package/src/components/content/auto-type-table.ts +4 -1
  269. package/src/components/content/badge-color.ts +20 -0
  270. package/src/components/content/base-href.ts +14 -22
  271. package/src/components/content/inline-markdown.ts +27 -7
  272. package/src/components/copy-feedback.ts +35 -8
  273. package/src/components/islands/assistant.tsx +37 -6
  274. package/src/components/islands/base-path.ts +47 -10
  275. package/src/components/islands/hooks.ts +42 -21
  276. package/src/components/islands/webmcp.ts +16 -10
  277. package/src/components/layout/Banner.astro +23 -4
  278. package/src/components/layout/Breadcrumbs.astro +5 -2
  279. package/src/components/layout/DiscoveryLinks.astro +9 -5
  280. package/src/components/layout/Header.astro +23 -10
  281. package/src/components/layout/LanguageSwitcher.astro +2 -2
  282. package/src/components/layout/Logo.astro +5 -0
  283. package/src/components/layout/NavSelector.astro +8 -3
  284. package/src/components/layout/NavTabMenu.astro +133 -0
  285. package/src/components/layout/NavTree.astro +31 -12
  286. package/src/components/layout/NavTreeCache.astro +5 -2
  287. package/src/components/layout/NavTreeScript.astro +45 -5
  288. package/src/components/layout/PageActions.astro +4 -3
  289. package/src/components/layout/PageLayout.astro +28 -13
  290. package/src/components/layout/Pagination.astro +3 -3
  291. package/src/components/layout/ReferenceLayout.astro +4 -1
  292. package/src/components/layout/RootLayout.astro +25 -12
  293. package/src/components/layout/Search.astro +38 -14
  294. package/src/components/layout/VersionBanner.astro +2 -2
  295. package/src/components/layout/analytics-client.ts +14 -0
  296. package/src/components/layout/toc-active.ts +41 -0
  297. package/src/components/layout/toc-element.ts +8 -14
  298. package/src/components/openapi/ApiTagOperations.astro +2 -2
  299. package/src/components/openapi/AsyncApiOperation.astro +7 -4
  300. package/src/components/openapi/GraphqlChip.astro +2 -2
  301. package/src/components/openapi/GraphqlType.astro +11 -3
  302. package/src/components/openapi/MessageComposer.astro +1 -1
  303. package/src/components/openapi/Operation.astro +23 -5
  304. package/src/components/openapi/PanelTabs.astro +4 -1
  305. package/src/components/openapi/Playground.astro +8 -2
  306. package/src/components/openapi/RequestPanel.astro +13 -6
  307. package/src/components/openapi/SchemaProperty.astro +11 -33
  308. package/src/components/openapi/SchemaTable.astro +34 -52
  309. package/src/components/openapi/async.ts +38 -6
  310. package/src/components/openapi/helpers.ts +100 -12
  311. package/src/components/openapi/message-composer.ts +14 -1
  312. package/src/components/openapi/message-model.ts +12 -2
  313. package/src/components/openapi/message.ts +21 -3
  314. package/src/components/openapi/operation-model.ts +119 -17
  315. package/src/components/openapi/panel.ts +31 -5
  316. package/src/components/openapi/param-style.ts +181 -0
  317. package/src/components/openapi/playground-client.ts +110 -23
  318. package/src/components/openapi/playground-schema.ts +25 -6
  319. package/src/components/openapi/request.ts +192 -26
  320. package/src/components/openapi/schema-tree.ts +209 -0
  321. package/src/components/openapi/snippets.ts +113 -19
  322. package/src/components/openapi/validate-json.ts +1 -1
  323. package/src/components/openapi/ws-client.ts +18 -2
  324. package/src/core/base-path.ts +69 -8
  325. package/src/core/config-input.ts +13 -11
  326. package/src/core/config.ts +18 -4
  327. package/src/core/diagnostics.ts +219 -30
  328. package/src/core/directive-diagnostics.ts +99 -0
  329. package/src/core/frontmatter.ts +21 -18
  330. package/src/core/graph.ts +120 -11
  331. package/src/core/heading-markers.ts +5 -18
  332. package/src/core/i18n-ui.ts +39 -2
  333. package/src/core/i18n.ts +44 -5
  334. package/src/core/last-modified.ts +25 -3
  335. package/src/core/links.ts +7 -4
  336. package/src/core/load-module.ts +20 -0
  337. package/src/core/locale-links.ts +22 -18
  338. package/src/core/manifest.ts +3 -2
  339. package/src/core/meta.ts +89 -12
  340. package/src/core/nav-diagnostics.ts +56 -1
  341. package/src/core/navigation.ts +272 -83
  342. package/src/core/ordering-prefix.ts +27 -0
  343. package/src/core/project-graph.ts +50 -6
  344. package/src/core/safe-href.ts +53 -1
  345. package/src/core/safe-links.ts +11 -2
  346. package/src/core/schema.ts +94 -18
  347. package/src/core/sources/assets.ts +83 -41
  348. package/src/core/sources/contentful-rich-text.ts +28 -20
  349. package/src/core/sources/contentful.ts +25 -12
  350. package/src/core/sources/filesystem.ts +25 -3
  351. package/src/core/sources/github-releases.ts +131 -11
  352. package/src/core/sources/lexical.ts +30 -20
  353. package/src/core/sources/lower.ts +276 -56
  354. package/src/core/sources/mdx-remote.ts +52 -16
  355. package/src/core/sources/normalize.ts +456 -119
  356. package/src/core/sources/notion.ts +77 -34
  357. package/src/core/sources/obsidian.ts +40 -9
  358. package/src/core/sources/payload.ts +1 -0
  359. package/src/core/sources/portable-text.ts +85 -49
  360. package/src/core/sources/remote.ts +18 -2
  361. package/src/core/sources/resolve.ts +52 -33
  362. package/src/core/sources/sanity.ts +1 -0
  363. package/src/core/sources/strapi-blocks.ts +28 -19
  364. package/src/core/sources/strapi.ts +1 -0
  365. package/src/core/sources/types.ts +31 -0
  366. package/src/core/sources/watch.ts +20 -7
  367. package/src/core/standard-schema.ts +10 -6
  368. package/src/core/types.ts +9 -0
  369. package/src/core/ui-packs/ar.ts +13 -0
  370. package/src/core/ui-packs/bg.ts +13 -0
  371. package/src/core/ui-packs/bn.ts +13 -0
  372. package/src/core/ui-packs/ca.ts +13 -0
  373. package/src/core/ui-packs/cs.ts +13 -0
  374. package/src/core/ui-packs/da.ts +13 -0
  375. package/src/core/ui-packs/de.ts +13 -0
  376. package/src/core/ui-packs/el.ts +13 -0
  377. package/src/core/ui-packs/es.ts +13 -0
  378. package/src/core/ui-packs/fa.ts +13 -0
  379. package/src/core/ui-packs/fi.ts +13 -0
  380. package/src/core/ui-packs/fr.ts +13 -0
  381. package/src/core/ui-packs/he.ts +13 -0
  382. package/src/core/ui-packs/hi.ts +13 -0
  383. package/src/core/ui-packs/hr.ts +13 -0
  384. package/src/core/ui-packs/hu.ts +13 -0
  385. package/src/core/ui-packs/id.ts +13 -0
  386. package/src/core/ui-packs/it.ts +13 -0
  387. package/src/core/ui-packs/ja.ts +13 -0
  388. package/src/core/ui-packs/ko.ts +13 -0
  389. package/src/core/ui-packs/nl.ts +13 -0
  390. package/src/core/ui-packs/no.ts +13 -0
  391. package/src/core/ui-packs/pl.ts +13 -0
  392. package/src/core/ui-packs/pt-br.ts +13 -0
  393. package/src/core/ui-packs/pt.ts +13 -0
  394. package/src/core/ui-packs/ro.ts +13 -0
  395. package/src/core/ui-packs/ru.ts +13 -0
  396. package/src/core/ui-packs/sk.ts +13 -0
  397. package/src/core/ui-packs/sr.ts +13 -0
  398. package/src/core/ui-packs/sv.ts +13 -0
  399. package/src/core/ui-packs/th.ts +13 -0
  400. package/src/core/ui-packs/tr.ts +13 -0
  401. package/src/core/ui-packs/uk.ts +13 -0
  402. package/src/core/ui-packs/vi.ts +13 -0
  403. package/src/core/ui-packs/zh-tw.ts +13 -0
  404. package/src/core/ui-packs/zh.ts +13 -0
  405. package/src/core/version-cut.ts +69 -17
  406. package/src/core/versions.ts +4 -1
  407. package/src/deploy/adapters/node.ts +5 -2
  408. package/src/deploy/adapters/registry.ts +2 -1
  409. package/src/deploy/adapters/types.ts +13 -1
  410. package/src/deploy/artifacts.ts +52 -11
  411. package/src/deploy/cloudflare-negotiation.ts +27 -16
  412. package/src/deploy/headers.ts +75 -55
  413. package/src/deploy/node-headers.ts +148 -27
  414. package/src/deploy/platforms/cloudflare.ts +13 -3
  415. package/src/deploy/platforms/netlify.ts +83 -5
  416. package/src/deploy/platforms/node.ts +8 -5
  417. package/src/deploy/platforms/static.ts +2 -0
  418. package/src/deploy/platforms/types.ts +15 -0
  419. package/src/deploy/platforms/vercel.ts +10 -3
  420. package/src/deploy/redirects.ts +68 -21
  421. package/src/deploy/robots.ts +2 -2
  422. package/src/deploy/rss.ts +4 -3
  423. package/src/deploy/sitemap.ts +7 -5
  424. package/src/deploy/vercel-negotiation.ts +35 -3
  425. package/src/eval/agents.ts +10 -2
  426. package/src/eval/run.ts +25 -0
  427. package/src/markdown/base-links.ts +74 -29
  428. package/src/markdown/directives.ts +242 -36
  429. package/src/markdown/features.ts +17 -0
  430. package/src/markdown/index.ts +13 -9
  431. package/src/markdown/mdast.ts +5 -2
  432. package/src/markdown/relative-links.ts +15 -27
  433. package/src/markdown/route-snapshot.ts +37 -0
  434. package/src/og/card.ts +149 -9
  435. package/src/og/derive.ts +156 -5
  436. package/src/og/index.ts +1 -0
  437. package/src/openapi/asyncapi.ts +4 -2
  438. package/src/openapi/graphql-build.ts +28 -2
  439. package/src/openapi/model.ts +99 -27
  440. package/src/openapi/proxy.ts +63 -10
  441. package/src/openapi/render-mdx.ts +51 -3
  442. package/src/registry/eject.ts +297 -43
  443. package/src/search/adapters/version-scope.ts +30 -0
  444. package/src/search/documents.ts +89 -27
  445. package/src/search/popular.ts +2 -1
  446. package/src/search/sync/algolia.ts +36 -2
  447. package/src/seo/jsonld.ts +7 -3
  448. package/src/sources/registry.ts +5 -0
  449. package/src/theme/entry.ts +11 -4
  450. package/src/translate/agents.ts +6 -1
  451. package/src/translate/ledger.ts +26 -3
  452. package/src/translate/meta.ts +76 -24
  453. package/src/translate/run.ts +14 -8
  454. package/src/translate/validate.ts +14 -2
  455. package/src/translate/work-list.ts +91 -19
  456. package/src/upgrade/upgrade.ts +36 -4
  457. package/dist/cli/chunk-27g6wdth.js.map +0 -10
  458. package/dist/cli/chunk-2hn4b8z7.js.map +0 -12
  459. package/dist/cli/chunk-6crbhc3x.js.map +0 -14
  460. package/dist/cli/chunk-6hsn950k.js.map +0 -10
  461. package/dist/cli/chunk-79jhk4py.js.map +0 -35
  462. package/dist/cli/chunk-82bbrxdn.js +0 -51
  463. package/dist/cli/chunk-82bbrxdn.js.map +0 -10
  464. package/dist/cli/chunk-abh8yjkn.js +0 -31
  465. package/dist/cli/chunk-abh8yjkn.js.map +0 -10
  466. package/dist/cli/chunk-ce574jw2.js +0 -23
  467. package/dist/cli/chunk-ce574jw2.js.map +0 -10
  468. package/dist/cli/chunk-dh8cwk36.js.map +0 -10
  469. package/dist/cli/chunk-epjnccmv.js.map +0 -10
  470. package/dist/cli/chunk-fa25z98p.js.map +0 -11
  471. package/dist/cli/chunk-fs23ddbb.js.map +0 -35
  472. package/dist/cli/chunk-fz5wtpmh.js.map +0 -10
  473. package/dist/cli/chunk-jwyddg7y.js.map +0 -15
  474. package/dist/cli/chunk-kpf8rrjc.js.map +0 -19
  475. package/dist/cli/chunk-m3vmjgmq.js.map +0 -10
  476. package/dist/cli/chunk-mb2919y2.js.map +0 -10
  477. package/dist/cli/chunk-q5163e60.js.map +0 -11
  478. package/dist/cli/chunk-qkqwkpte.js.map +0 -182
  479. package/dist/cli/chunk-qs4q5p4e.js.map +0 -10
  480. package/dist/cli/chunk-s1p84fyh.js.map +0 -10
  481. package/dist/cli/chunk-wgm7m9qk.js.map +0 -36
  482. package/dist/cli/chunk-yt5n7ppj.js.map +0 -10
  483. package/dist/cli/chunk-yw7dm696.js.map +0 -14
  484. /package/dist/cli/{chunk-s6jhgk0q.js.map → chunk-2eytanqx.js.map} +0 -0
  485. /package/dist/cli/{chunk-hdpx1tax.js.map → chunk-2z928egk.js.map} +0 -0
  486. /package/dist/cli/{chunk-ah61y8py.js.map → chunk-364znk6q.js.map} +0 -0
  487. /package/dist/cli/{chunk-vtk4a6dg.js.map → chunk-8ktnccpt.js.map} +0 -0
  488. /package/dist/cli/{chunk-fxypxtvm.js.map → chunk-a58773jm.js.map} +0 -0
  489. /package/dist/cli/{chunk-wm7js3j9.js.map → chunk-b07cmahc.js.map} +0 -0
  490. /package/dist/cli/{chunk-zxcczpyx.js.map → chunk-r20tn01b.js.map} +0 -0
  491. /package/dist/cli/{chunk-5shv93fd.js.map → chunk-tkacnehg.js.map} +0 -0
  492. /package/dist/cli/{chunk-zxh4d9vy.js.map → chunk-tzmab476.js.map} +0 -0
@@ -69,8 +69,10 @@ const DOCS_PATHS = new Map(
69
69
  BLUME_TRANSLATE_MISSING: "/docs/cli/translate",
70
70
  BLUME_TRANSLATE_STALE: "/docs/cli/translate",
71
71
  BLUME_UNKNOWN_COMPONENT: "/docs/configuration/customization",
72
+ BLUME_UNKNOWN_DIRECTIVE: "/docs/content/syntax",
72
73
  BLUME_UNKNOWN_ICON: DOCS_CONTENT_NAVIGATION,
73
74
  BLUME_UNKNOWN_OPTION: "/docs/cli",
75
+ BLUME_UNLOADABLE_FILE_NAME: "/docs/content",
74
76
  BLUME_WIKILINK_AMBIGUOUS: DOCS_CONTENT_SOURCES,
75
77
  BLUME_WIKILINK_UNRESOLVED: DOCS_CONTENT_SOURCES,
76
78
  BLUME_YARN_PNP: "/docs/quickstart",
@@ -98,50 +100,237 @@ const isKeySegment = (segment: string | number): segment is string =>
98
100
  typeof segment === "string";
99
101
 
100
102
  /**
101
- * Best-effort source position for a Zod issue path (e.g. `["seo", "title"]`) in
102
- * the raw config / frontmatter text. Narrows key-by-key — finding each string
103
- * segment as a `key:`/`key =` at or after the previous match — so a nested key
104
- * lands under its parent. Array indices are skipped. Returns 1-based line/column,
105
- * or undefined when nothing matches.
103
+ * Where a located value's children are searched. `object` and `array` are the
104
+ * inside of a `{…}`/`[…]` literal, searched entry by entry so a key or an index
105
+ * only matches a direct child. `block` has no brackets — the whole file at the
106
+ * top, or a YAML key's indented lines — and is searched at any depth.
106
107
  */
107
- const stepSegment = (
108
+ interface Scope {
109
+ end: number;
110
+ /** A literal's direct entries (keys or elements), in source order. */
111
+ entries: number[];
112
+ kind: "array" | "block" | "object";
113
+ start: number;
114
+ }
115
+
116
+ /** A located key or element: where it starts, and where its value starts. */
117
+ interface Step {
118
+ index: number;
119
+ next: number;
120
+ }
121
+
122
+ const QUOTES = new Set(['"', "'", "`"]);
123
+ const OPENERS = new Set(["(", "[", "{"]);
124
+ const CLOSERS = new Set([")", "]", "}"]);
125
+ const WHITESPACE = /\s/u;
126
+
127
+ /**
128
+ * The index just past a comment starting at `index`, or `index` when none does.
129
+ * A `//` after a `:` is a URL scheme in YAML (`href: https://…`), not a comment.
130
+ */
131
+ const skipComment = (source: string, index: number): number => {
132
+ if (source.startsWith("//", index) && source.charAt(index - 1) !== ":") {
133
+ const end = source.indexOf("\n", index);
134
+ return end === -1 ? source.length : end;
135
+ }
136
+ if (source.startsWith("/*", index)) {
137
+ const end = source.indexOf("*/", index + 2);
138
+ return end === -1 ? source.length : end + 2;
139
+ }
140
+ return index;
141
+ };
142
+
143
+ /**
144
+ * The index just past a string literal starting at `index`, or `index` when
145
+ * none does. A `'`/`"` that doesn't close on its own line is an apostrophe in
146
+ * plain YAML text, not a string.
147
+ */
148
+ const skipString = (source: string, index: number): number => {
149
+ const quote = source.charAt(index);
150
+ if (!QUOTES.has(quote)) {
151
+ return index;
152
+ }
153
+ let cursor = index + 1;
154
+ while (cursor < source.length) {
155
+ const char = source.charAt(cursor);
156
+ if (char === quote) {
157
+ return cursor + 1;
158
+ }
159
+ if (char === "\n" && quote !== "`") {
160
+ return index;
161
+ }
162
+ cursor += char === "\\" ? 2 : 1;
163
+ }
164
+ return index;
165
+ };
166
+
167
+ /** Where a literal closes, and where each of its direct entries starts. */
168
+ interface LiteralScan {
169
+ end: number;
170
+ entries: number[];
171
+ }
172
+
173
+ /**
174
+ * Walk the `{…}`/`[…]` literal opened at `open`: where each direct entry starts
175
+ * (a key, or an array element) and where the literal closes. Nested brackets,
176
+ * strings, and comments are stepped over, so only the literal's own commas
177
+ * separate its entries.
178
+ */
179
+ const scanLiteral = (source: string, open: number): LiteralScan => {
180
+ const entries: number[] = [];
181
+ let depth = 0;
182
+ let pending = true;
183
+ let index = open + 1;
184
+ while (index < source.length) {
185
+ const afterComment = skipComment(source, index);
186
+ const char = source.charAt(index);
187
+ if (afterComment !== index || WHITESPACE.test(char)) {
188
+ index = Math.max(afterComment, index + 1);
189
+ continue;
190
+ }
191
+ if (depth === 0 && CLOSERS.has(char)) {
192
+ return { end: index, entries };
193
+ }
194
+ if (depth === 0 && pending) {
195
+ entries.push(index);
196
+ pending = false;
197
+ }
198
+ const afterString = skipString(source, index);
199
+ if (afterString !== index) {
200
+ index = afterString;
201
+ continue;
202
+ }
203
+ if (OPENERS.has(char)) {
204
+ depth += 1;
205
+ } else if (CLOSERS.has(char)) {
206
+ depth -= 1;
207
+ } else if (char === "," && depth === 0) {
208
+ pending = true;
209
+ }
210
+ index += 1;
211
+ }
212
+ return { end: source.length, entries };
213
+ };
214
+
215
+ /**
216
+ * The lines indented deeper than the key at `anchor` — a YAML mapping's
217
+ * children — up to the first line that isn't, or `limit`.
218
+ */
219
+ const indentedBlock = (
108
220
  source: string,
109
- segment: string | number,
110
- cursor: number
111
- ) => {
112
- // A non-string path segment (array index) is skipped without moving on.
113
- if (!isKeySegment(segment)) {
114
- return { index: -1, next: cursor, stop: false };
221
+ anchor: number,
222
+ limit: number
223
+ ): Scope => {
224
+ const column = anchor - (source.lastIndexOf("\n", anchor - 1) + 1);
225
+ const lineEnd = source.indexOf("\n", anchor);
226
+ const start = lineEnd === -1 ? limit : Math.min(lineEnd + 1, limit);
227
+ let end = start;
228
+ while (end < limit) {
229
+ const next = source.indexOf("\n", end);
230
+ const after = next === -1 || next >= limit ? limit : next + 1;
231
+ const line = source.slice(end, after);
232
+ if (line.trim() !== "" && line.length - line.trimStart().length <= column) {
233
+ break;
234
+ }
235
+ end = after;
115
236
  }
116
- // The negative lookbehind keeps a segment like `title` from matching the
117
- // tail of an unrelated key such as `subtitle:`.
118
- const matcher = new RegExp(
119
- `(?<![\\w$])${escapeRegExp(segment)}\\s*[:=]`,
120
- "gu"
121
- );
122
- matcher.lastIndex = cursor;
123
- const match = matcher.exec(source);
124
- if (!match) {
125
- return { index: -1, next: cursor, stop: true };
237
+ return { end, entries: [], kind: "block", start };
238
+ };
239
+
240
+ /** A value's lead-in before its literal: spaces, or an adapter call's `vercel(`. */
241
+ const VALUE_LEAD = /[\s\w$.(]*/uy;
242
+
243
+ /**
244
+ * The scope holding the children of the key or element at `anchor`, whose
245
+ * value starts at `from`. A `{…}`/`[…]` literal — written directly, or as the
246
+ * first argument of a call like `vercel({…})` — is walked entry by entry;
247
+ * anything else falls back to the lines indented under `anchor` (YAML's
248
+ * nesting), within the enclosing scope's `limit`.
249
+ */
250
+ const valueScope = (source: string, step: Step, limit: number): Scope => {
251
+ VALUE_LEAD.lastIndex = step.next;
252
+ VALUE_LEAD.exec(source);
253
+ const open = VALUE_LEAD.lastIndex;
254
+ const char = source.charAt(open);
255
+ if (open < limit && (char === "{" || char === "[")) {
256
+ const literal = scanLiteral(source, open);
257
+ return {
258
+ end: literal.end,
259
+ entries: literal.entries,
260
+ kind: char === "{" ? "object" : "array",
261
+ start: open,
262
+ };
126
263
  }
127
- return { index: match.index, next: matcher.lastIndex, stop: false };
264
+ return indentedBlock(source, step.index, limit);
128
265
  };
129
266
 
267
+ /**
268
+ * Find `segment` as a `key:`/`key =` (bare or quoted) in `scope`: among an
269
+ * object literal's own entries, or anywhere in a block. The negative lookbehind
270
+ * keeps `title` from matching the tail of an unrelated key such as `subtitle:`.
271
+ */
272
+ const findKey = (
273
+ source: string,
274
+ scope: Scope,
275
+ segment: string
276
+ ): Step | undefined => {
277
+ const key = escapeRegExp(segment);
278
+ const pattern = `(?<![\\w$])(?:${key}|"${key}"|'${key}')\\s*[:=]`;
279
+ if (scope.kind === "block") {
280
+ const matcher = new RegExp(pattern, "gu");
281
+ matcher.lastIndex = scope.start;
282
+ const match = matcher.exec(source);
283
+ return match && match.index < scope.end
284
+ ? { index: match.index, next: matcher.lastIndex }
285
+ : undefined;
286
+ }
287
+ // Sticky: the key must start exactly where the entry does.
288
+ const matcher = new RegExp(pattern, "uy");
289
+ for (const entry of scope.kind === "object" ? scope.entries : []) {
290
+ matcher.lastIndex = entry;
291
+ if (matcher.test(source)) {
292
+ return { index: entry, next: matcher.lastIndex };
293
+ }
294
+ }
295
+ return undefined;
296
+ };
297
+
298
+ /** The element at `position` of an array literal; a block can't be indexed. */
299
+ const findElement = (scope: Scope, position: number): Step | undefined => {
300
+ const index = scope.kind === "array" ? scope.entries[position] : undefined;
301
+ return index === undefined ? undefined : { index, next: index };
302
+ };
303
+
304
+ /**
305
+ * Best-effort source position for a Zod issue path (e.g. `["seo", "title"]`,
306
+ * `["redirects", 2, "status"]`) in the raw config / frontmatter text. Narrows
307
+ * segment by segment: a key is found among its parent's own entries (or, where
308
+ * there are no brackets, in the parent's indented lines), an index picks that
309
+ * element of an array literal. A segment that isn't there — a missing key —
310
+ * stops the walk at its parent, so the position never escapes into a sibling.
311
+ * Returns 1-based line/column, or undefined when not even the first segment
312
+ * matches.
313
+ */
130
314
  const locatePath = (
131
315
  source: string,
132
316
  path: readonly (string | number)[]
133
317
  ): { column: number; line: number } | undefined => {
134
- let cursor = 0;
318
+ let scope: Scope = {
319
+ end: source.length,
320
+ entries: [],
321
+ kind: "block",
322
+ start: 0,
323
+ };
135
324
  let found = -1;
136
325
  for (const segment of path) {
137
- const step = stepSegment(source, segment, cursor);
138
- if (step.stop) {
326
+ const step = isKeySegment(segment)
327
+ ? findKey(source, scope, segment)
328
+ : findElement(scope, segment);
329
+ if (!step) {
139
330
  break;
140
331
  }
141
- cursor = step.next;
142
- if (step.index >= 0) {
143
- found = step.index;
144
- }
332
+ found = step.index;
333
+ scope = valueScope(source, step, scope.end);
145
334
  }
146
335
  if (found < 0) {
147
336
  return;
@@ -0,0 +1,99 @@
1
+ import type { Nodes } from "mdast";
2
+ import { mdxToMdast } from "satteri";
3
+
4
+ import {
5
+ CALLOUT_ALIASES,
6
+ CALLOUT_TYPES,
7
+ calloutTypeFor,
8
+ } from "../markdown/directives.ts";
9
+ import { MDX_BODY_FEATURES } from "../markdown/features.ts";
10
+ import { strippedLineOffset } from "./sources/normalize.ts";
11
+ import type { SourceEntry } from "./sources/types.ts";
12
+ import type { Diagnostic } from "./types.ts";
13
+
14
+ // A line that may open a container directive: a colon fence and a name, after
15
+ // any quote or list indentation. A page is parsed only when one of these
16
+ // names something other than a callout, so pages without one cost a scan.
17
+ const CONTAINER_OPENING = /^[\t >]*:{3,}(?<name>[a-z][\w-]*)/gimu;
18
+
19
+ const codeList = (names: Iterable<string>): string =>
20
+ [...names].map((name) => `\`${name}\``).join(", ");
21
+
22
+ const CALLOUT_NAMES = `${codeList(CALLOUT_TYPES)} (or the aliases ${codeList(Object.keys(CALLOUT_ALIASES))})`;
23
+
24
+ /** A container directive that isn't a callout, and its line in `text`. */
25
+ interface UnknownContainer {
26
+ line: number;
27
+ name: string;
28
+ }
29
+
30
+ /**
31
+ * Every container directive in an `.mdx` body whose name isn't a callout, in
32
+ * source order. The body is parsed as the renderer reads it, so a fence in a
33
+ * code block or a `::: name` that isn't a directive at all never counts. A
34
+ * body MDX can't parse fails to render anyway, with its own error.
35
+ */
36
+ const unknownContainers = (text: string): UnknownContainer[] => {
37
+ const named = [...text.matchAll(CONTAINER_OPENING)].some(
38
+ (match) => calloutTypeFor(match.groups?.name ?? "") === null
39
+ );
40
+ if (!named) {
41
+ return [];
42
+ }
43
+ let tree: Nodes;
44
+ try {
45
+ tree = mdxToMdast(text, { features: MDX_BODY_FEATURES });
46
+ } catch {
47
+ return [];
48
+ }
49
+ const found: UnknownContainer[] = [];
50
+ const walk = (node: Nodes): void => {
51
+ if (
52
+ node.type === "containerDirective" &&
53
+ calloutTypeFor(node.name) === null
54
+ ) {
55
+ found.push({ line: node.position?.start.line ?? 1, name: node.name });
56
+ }
57
+ if ("children" in node) {
58
+ for (const child of node.children) {
59
+ walk(child);
60
+ }
61
+ }
62
+ };
63
+ walk(tree);
64
+ return found;
65
+ };
66
+
67
+ /**
68
+ * Warn about each `:::name` container in an `.mdx` entry that isn't a callout.
69
+ * The page keeps its content — the body renders between the literal `:::`
70
+ * lines (see `markdown/directives.ts`) — but a typo like `:::warnig`, or a
71
+ * `:::details` carried over from another docs tool, should read as the
72
+ * mistake it is rather than as a finished page. Lines point into the file
73
+ * the author wrote, a partial's own file for a container an `<include>`
74
+ * brought in.
75
+ */
76
+ export const unknownDirectiveDiagnostics = (
77
+ entry: SourceEntry,
78
+ sourceName: string
79
+ ): Diagnostic[] => {
80
+ if (entry.body.format !== "mdx") {
81
+ return [];
82
+ }
83
+ const page = entry.sourcePath ?? `${sourceName}:${entry.ref}`;
84
+ const offset =
85
+ entry.bodyLineOffset ?? strippedLineOffset(entry.raw, entry.body.text);
86
+ return unknownContainers(entry.expanded?.text ?? entry.body.text).map(
87
+ ({ line, name }) => {
88
+ const origin = entry.expanded?.origins[line - 1];
89
+ return {
90
+ code: "BLUME_UNKNOWN_DIRECTIVE",
91
+ file: origin?.file ?? page,
92
+ line: origin?.line ?? line + offset,
93
+ message: `\`:::${name}\` isn't a callout type, so the page shows its \`:::\` lines as written around its content.`,
94
+ severity: "warning",
95
+ suggestion: `Use a callout type — ${CALLOUT_NAMES} — or remove the \`:::\` lines to keep the content as plain prose.`,
96
+ };
97
+ }
98
+ );
99
+ };
@@ -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
@@ -1,17 +1,22 @@
1
- import { withBasePath } from "./base-path.ts";
1
+ import { mountBasePath, withBasePath } from "./base-path.ts";
2
2
  import {
3
3
  localizeInternalPath,
4
4
  localizeRoute,
5
5
  resolveFallbackLocale,
6
6
  } from "./i18n.ts";
7
- import { validateNavIcons, validateNavStructure } from "./nav-diagnostics.ts";
8
- import { buildNavigation } from "./navigation.ts";
7
+ import {
8
+ validateNavIcons,
9
+ validateNavStructure,
10
+ validateSidebarItems,
11
+ } from "./nav-diagnostics.ts";
12
+ import { buildNavigation, isPageRef, pagesByRef } from "./navigation.ts";
9
13
  import type {
10
14
  FolderMeta,
11
15
  LocalizableLabel,
12
16
  ResolvedConfig,
13
17
  ResolvedI18nConfig,
14
18
  ResolvedVersionsConfig,
19
+ SidebarItemConfig,
15
20
  } from "./schema.ts";
16
21
  import type {
17
22
  ContentGraph,
@@ -23,6 +28,8 @@ import type {
23
28
  import { versionizeRoute } from "./versions.ts";
24
29
 
25
30
  interface BuildContentGraphOptions {
31
+ /** The banner's link target as configured (`banner.link.href`), if any. */
32
+ bannerHref?: string;
26
33
  /** Site-wide route mount point (`""` or `/seg`); invisible to the nav tree. */
27
34
  basePath?: string;
28
35
  /** The header brand link as configured (`logo.href`, default `/`). */
@@ -83,7 +90,7 @@ const localePagesFor = (
83
90
  ...source,
84
91
  fallback: true,
85
92
  locale: code,
86
- route: withBasePath(basePath, localizeRoute(key, code, i18n)),
93
+ route: mountBasePath(basePath, localizeRoute(key, code, i18n)),
87
94
  });
88
95
  }
89
96
  }
@@ -115,6 +122,28 @@ const resolveLabel = (
115
122
  );
116
123
  };
117
124
 
125
+ /**
126
+ * An explicit sidebar with every item's `href` passed through `localize` (page
127
+ * refs and `root`s resolve against each locale's own pages already).
128
+ */
129
+ const localizeSidebarHrefs = (
130
+ items: SidebarItemConfig[],
131
+ localize: (href: string) => string
132
+ ): SidebarItemConfig[] =>
133
+ items.map((item) => {
134
+ if (isPageRef(item)) {
135
+ return item;
136
+ }
137
+ const localized = { ...item };
138
+ if (item.href !== undefined) {
139
+ localized.href = localize(item.href);
140
+ }
141
+ if (item.items) {
142
+ localized.items = localizeSidebarHrefs(item.items, localize);
143
+ }
144
+ return localized;
145
+ });
146
+
118
147
  /** Resolve every localizable label in the configured tabs for one locale. */
119
148
  const resolveTabLabels = (
120
149
  tabs: BuildContentGraphOptions["navigation"]["tabs"],
@@ -130,6 +159,18 @@ const resolveTabLabels = (
130
159
  label: resolveLabel(tab.label, locale, defaultLocale),
131
160
  }));
132
161
 
162
+ /**
163
+ * Mount a configured link authored as if the site were served at root (the
164
+ * banner's): under `basePath`, except a route served outside the content tree
165
+ * — a custom page, the generated changelog index — which answers only at its
166
+ * own path.
167
+ */
168
+ const mountConfiguredLink = (
169
+ path: string,
170
+ basePath: string,
171
+ extraRoutes: ReadonlySet<string>
172
+ ): string => (extraRoutes.has(path) ? path : withBasePath(basePath, path));
173
+
133
174
  /**
134
175
  * Build one locale's navigation tree from its own pages and folder meta.
135
176
  * `version` is the archived version id when building a snapshot's tree
@@ -217,8 +258,15 @@ const buildLocaleNavigation = (
217
258
  // next to the originals and `guides/meta.ts` applies to every locale —
218
259
  // prefixing would look up keys that can never exist. Inside a snapshot the
219
260
  // version dir is hoisted in front (`v1.0/fr`), matching `discoverFolderMeta`.
220
- const localeDir =
221
- i18n.parser === "dir" && code !== i18n.defaultLocale ? code : "";
261
+ const localeDirOf = (locale: string): string =>
262
+ i18n.parser === "dir" && locale !== i18n.defaultLocale ? locale : "";
263
+ const localeDir = localeDirOf(code);
264
+ // A locale padded from the fallback locale mirrors that locale's folder meta
265
+ // for any folder it has no meta of its own for.
266
+ const fallbackMetaPrefix =
267
+ fallback && code !== fallback
268
+ ? [version, localeDirOf(fallback)].filter(Boolean).join("/")
269
+ : undefined;
222
270
  // Internal featured and header hrefs are localized like tab paths — a pinned
223
271
  // `/changelog` link rendered on `/fr/…` pages must stay inside the reader's
224
272
  // locale, not kick them back to the default one.
@@ -227,6 +275,7 @@ const buildLocaleNavigation = (
227
275
  href: localizeServed(item.href, false),
228
276
  });
229
277
  const { actions, cta, featured } = options.navigation;
278
+ const sidebarItems = options.navigation.sidebar.items;
230
279
 
231
280
  const navigation = buildNavigation(localePages, {
232
281
  actions: actions?.map(localizeHref),
@@ -235,6 +284,7 @@ const buildLocaleNavigation = (
235
284
  diagnostics,
236
285
  display: options.navigation.sidebar.display,
237
286
  extraRoutes: options.extraRoutes,
287
+ fallbackMetaPrefix,
238
288
  featured: featured?.map(localizeHref),
239
289
  folderMeta: options.folderMeta,
240
290
  // The localized tree root ("/" for the hidden default, "/fr" otherwise;
@@ -250,13 +300,43 @@ const buildLocaleNavigation = (
250
300
  sharedMetaPrefix: version,
251
301
  // A configured explicit sidebar describes the current docs; a frozen
252
302
  // snapshot's structure comes from the snapshot itself, so archived trees
253
- // always build from the filesystem.
254
- sidebar: version ? undefined : options.navigation.sidebar.items,
303
+ // always build from the filesystem. Its `href` links are localized like
304
+ // featured links.
305
+ sidebar:
306
+ version || !sidebarItems
307
+ ? undefined
308
+ : localizeSidebarHrefs(sidebarItems, (href) =>
309
+ localizeServed(href, false)
310
+ ),
311
+ // Tab paths are written against the current docs' root, which a
312
+ // snapshot's versionized `localizedRoot` no longer is.
313
+ tabRoot: localizeRoute("/", code, i18n),
255
314
  tabs,
256
315
  });
257
- return options.brandHref === undefined
258
- ? navigation
259
- : { ...navigation, brandHref: localizeServed(options.brandHref, false) };
316
+ // `serves` checked the localized brand link against based routes, and
317
+ // unlike tab and header links it isn't rebased later, so a localized one
318
+ // takes the base here (`/fr` is served at `/docs/fr`).
319
+ const localizedBrand = (href: string): string => {
320
+ const localized = localizeServed(href, false);
321
+ return localized === href ? localized : withBasePath(basePath, localized);
322
+ };
323
+ const branded =
324
+ options.brandHref === undefined
325
+ ? navigation
326
+ : { ...navigation, brandHref: localizedBrand(options.brandHref) };
327
+ // The banner link is authored like a featured href — in the default
328
+ // locale's path space, as if mounted at root — so it is localized the same
329
+ // way before it is mounted.
330
+ return options.bannerHref === undefined
331
+ ? branded
332
+ : {
333
+ ...branded,
334
+ bannerHref: mountConfiguredLink(
335
+ localizeServed(options.bannerHref, false),
336
+ basePath,
337
+ extraRoutes
338
+ ),
339
+ };
260
340
  };
261
341
 
262
342
  /**
@@ -348,6 +428,8 @@ const buildVersionNavigation = (
348
428
  selectors: options.navigation.selectors,
349
429
  sharedFolderMeta: options.sharedFolderMeta,
350
430
  sharedMetaPrefix: id,
431
+ // Tab paths are written against the current docs' root.
432
+ tabRoot: "/",
351
433
  // A configured explicit sidebar describes the current docs; a snapshot's
352
434
  // structure comes from the snapshot itself.
353
435
  tabs: resolveTabLabels(options.navigation.tabs, ""),
@@ -398,6 +480,16 @@ export const buildContentGraph = (
398
480
  // entry on a single-locale site.
399
481
  tabs: resolveTabLabels(options.navigation.tabs, ""),
400
482
  });
483
+ if (options.bannerHref !== undefined) {
484
+ navigation = {
485
+ ...navigation,
486
+ bannerHref: mountConfiguredLink(
487
+ options.bannerHref,
488
+ options.basePath ?? "",
489
+ options.extraRoutes ?? new Set<string>()
490
+ ),
491
+ };
492
+ }
401
493
  }
402
494
 
403
495
  const navigationByVersion: Record<string, Record<string, Navigation>> = {};
@@ -417,6 +509,23 @@ export const buildContentGraph = (
417
509
  ...validateNavIcons(navigation),
418
510
  ...validateNavStructure(navigation, currentPages)
419
511
  );
512
+ // Checked once against every locale's pages, not per locale tree: a page
513
+ // missing from one locale's sidebar is an untranslated page, not a typo.
514
+ const sidebarItems = options.navigation.sidebar.items;
515
+ if (sidebarItems) {
516
+ const byRef = pagesByRef(
517
+ currentPages,
518
+ options.basePath ?? "",
519
+ Boolean(i18n)
520
+ );
521
+ const extraRoutes = options.extraRoutes ?? new Set<string>();
522
+ diagnostics.push(
523
+ ...validateSidebarItems(
524
+ sidebarItems,
525
+ (route) => byRef.has(route) || extraRoutes.has(route)
526
+ )
527
+ );
528
+ }
420
529
 
421
530
  return {
422
531
  diagnostics,