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
@@ -1,6 +1,7 @@
1
1
  import { extname } from "pathe";
2
2
 
3
3
  import { stripBasePath, withBasePath } from "./base-path.ts";
4
+ import { orderingPrefix, stripOrderingPrefix } from "./ordering-prefix.ts";
4
5
  import type {
5
6
  FolderMeta,
6
7
  SidebarDisplay,
@@ -17,7 +18,6 @@ import type {
17
18
  PageRecord,
18
19
  } from "./types.ts";
19
20
 
20
- const NUMERIC_PREFIX = /^(?<order>\d+)[-_.]/u;
21
21
  const GROUP_FOLDER = /^\((?<label>.+)\)$/u;
22
22
  const WORD_SPLIT = /[-_]/u;
23
23
 
@@ -94,27 +94,50 @@ const WORD_FORMS = new Map([
94
94
  ["yaml", "YAML"],
95
95
  ]);
96
96
 
97
+ /**
98
+ * Capitalize one word of a slug-like name for display, spelling the acronyms
99
+ * and brand casings in {@link WORD_FORMS} their own way (`api` → "API").
100
+ * Shared with page titles derived from file names, so a page and a folder
101
+ * named alike read alike.
102
+ */
103
+ export const titleWord = (word: string): string =>
104
+ WORD_FORMS.get(word) ?? word.charAt(0).toUpperCase() + word.slice(1);
105
+
97
106
  const humanize = (segment: string): string =>
98
- segment
99
- .replace(NUMERIC_PREFIX, "")
107
+ stripOrderingPrefix(segment)
100
108
  .split(WORD_SPLIT)
101
109
  .filter(Boolean)
102
- .map(
103
- (word) =>
104
- WORD_FORMS.get(word) ?? word.charAt(0).toUpperCase() + word.slice(1)
105
- )
110
+ .map(titleWord)
106
111
  .join(" ");
107
112
 
108
113
  const numericOrder = (segment: string): number => {
109
- const value = segment.match(NUMERIC_PREFIX)?.groups?.order;
114
+ const value = orderingPrefix(segment);
110
115
  return value ? Math.trunc(Number(value)) : Number.POSITIVE_INFINITY;
111
116
  };
112
117
 
118
+ /**
119
+ * The name inside a group folder's parentheses — `(guides)`, or `01-(guides)`
120
+ * with its ordering prefix outside them — else undefined. A prefix written
121
+ * inside (`(01-guides)`) stays in the name, where it sorts and drops like any
122
+ * folder's.
123
+ */
124
+ const groupName = (raw: string): string | undefined =>
125
+ (raw.match(GROUP_FOLDER) ?? stripOrderingPrefix(raw).match(GROUP_FOLDER))
126
+ ?.groups?.label;
127
+
128
+ /**
129
+ * A folder's sidebar order: its ordering prefix, or — for a group folder
130
+ * without one outside the parentheses — the prefix inside them, so
131
+ * `01-(guides)` and `(01-guides)` both sort like `01-guides`.
132
+ */
133
+ const folderOrder = (raw: string): number =>
134
+ numericOrder(
135
+ orderingPrefix(raw) === undefined ? (groupName(raw) ?? raw) : raw
136
+ );
137
+
113
138
  /** The nav key of a raw path segment: group label or numeric-stripped name. */
114
- const segmentKey = (raw: string): string => {
115
- const group = raw.match(GROUP_FOLDER)?.groups?.label;
116
- return (group ?? raw).replace(NUMERIC_PREFIX, "");
117
- };
139
+ const segmentKey = (raw: string): string =>
140
+ stripOrderingPrefix(groupName(raw) ?? raw);
118
141
 
119
142
  /**
120
143
  * Whether a filename stem is a directory index, ignoring an ordering prefix:
@@ -122,7 +145,7 @@ const segmentKey = (raw: string): string => {
122
145
  * exactly like `index` and must be treated as one here too.
123
146
  */
124
147
  const isIndexStem = (stem: string): boolean =>
125
- stem.replace(NUMERIC_PREFIX, "") === "index";
148
+ stripOrderingPrefix(stem) === "index";
126
149
 
127
150
  /** A filename's stem: the name with its extension stripped. */
128
151
  const stemOf = (filename: string): string =>
@@ -158,6 +181,8 @@ interface MutablePage {
158
181
  * so they're excluded from the duplicate-order diagnostic.
159
182
  */
160
183
  orderIsAuthored: boolean;
184
+ /** Whether `order` is the node's position in its folder meta `pages` list. */
185
+ ranked?: boolean;
161
186
  }
162
187
 
163
188
  interface MutableGroup {
@@ -166,6 +191,12 @@ interface MutableGroup {
166
191
  path: string;
167
192
  /** The group's URL path (folder route prefix); set as pages are inserted. */
168
193
  routePath?: string;
194
+ /**
195
+ * The group's URL path read off its folder names, from a page whose
196
+ * frontmatter `slug` moved it off the folder's path. Fills `routePath` only
197
+ * when no other page under the folder supplies one.
198
+ */
199
+ folderPath?: string;
169
200
  /** The folder's index page route, when it has one; the group row's link. */
170
201
  route?: string;
171
202
  label: string;
@@ -173,6 +204,8 @@ interface MutableGroup {
173
204
  collapsed?: boolean;
174
205
  display?: SidebarDisplay;
175
206
  order: number;
207
+ /** Whether `order` is the node's position in its folder meta `pages` list. */
208
+ ranked?: boolean;
176
209
  children: MutableNode[];
177
210
  index: Map<string, MutableGroup>;
178
211
  }
@@ -206,8 +239,8 @@ const ensureGroup = (
206
239
  const group = createGroup(
207
240
  segmentKey(rawSegment),
208
241
  path,
209
- humanize(rawSegment.match(GROUP_FOLDER)?.groups?.label ?? rawSegment),
210
- numericOrder(rawSegment)
242
+ humanize(groupName(rawSegment) ?? rawSegment),
243
+ folderOrder(rawSegment)
211
244
  );
212
245
  parent.index.set(rawSegment, group);
213
246
  parent.children.push(group);
@@ -240,7 +273,8 @@ const pageOrder = (page: PageRecord, filename: string): PageOrder => {
240
273
  }
241
274
  }
242
275
  // An undated changelog entry's numeric filename prefix is usually a date
243
- // (`2024-01-05-release.md`) rather than a rank, so it is derived too.
276
+ // (`20240105-release.md`) rather than a rank, so it is derived too. An ISO
277
+ // date (`2024-01-05-release.md`) is no ordering prefix at all.
244
278
  const order = numericOrder(filename);
245
279
  return {
246
280
  order,
@@ -260,6 +294,17 @@ const metaKey = (path: string, metaPrefix: string): string => {
260
294
  return path ? `${metaPrefix}/${path}` : metaPrefix;
261
295
  };
262
296
 
297
+ /**
298
+ * What an i18n locale's tree mirrors from the fallback locale for a folder it
299
+ * has no meta of its own for: the fallback locale's folder meta, and the
300
+ * `sidebar.display` of a fallback-filled index page. Empty for the fallback
301
+ * locale itself and for single-locale sites.
302
+ */
303
+ interface FallbackFolderMeta {
304
+ indexDisplay: Map<string, SidebarDisplay>;
305
+ meta: (path: string) => FolderMeta | undefined;
306
+ }
307
+
263
308
  /**
264
309
  * Apply folder meta (title/order/icon/collapsed/display and explicit page
265
310
  * order), plus the index-frontmatter display sugar collected per folder path.
@@ -271,41 +316,40 @@ const applyFolderMeta = (
271
316
  metaPrefix: string,
272
317
  sharedMetaPrefix: string,
273
318
  indexDisplay: Map<string, SidebarDisplay>,
274
- indexRoute: Map<string, string>
319
+ indexRoute: Map<string, string>,
320
+ fallback: FallbackFolderMeta
275
321
  ): void => {
276
322
  // A folder with an index page links its group row to it — the same shape
277
323
  // as an explicit-config group's `root`, and the only sidebar link to the
278
324
  // section's own page once the index row is hidden. Index-less folders keep
279
325
  // no link: their row would 404.
280
326
  group.route = indexRoute.get(group.path);
327
+ group.routePath ??= group.folderPath;
281
328
  // Locale-specific meta wins; a shared `meta.$.*` (keyed by the locale-stripped
282
329
  // group path — version-prefixed inside a snapshot) applies to every locale
283
330
  // otherwise.
284
- const meta =
331
+ const localeMeta =
285
332
  folderMeta.get(metaKey(group.path, metaPrefix)) ??
286
333
  sharedMeta.get(metaKey(group.path, sharedMetaPrefix));
334
+ // A locale with neither mirrors the fallback locale's group, the way its
335
+ // tree mirrors that locale's untranslated pages: the fallback's folder
336
+ // meta, and — ahead of it, as in the fallback's own tree — the display its
337
+ // fallback-filled index page sets. A locale's own meta owns its group, so
338
+ // the fallback's index frontmatter never overrides it.
339
+ const mirrored = localeMeta === undefined;
340
+ const meta = localeMeta ?? fallback.meta(group.path);
287
341
  // The group's own render mode, resolved index frontmatter first, then folder
288
342
  // meta; `toNavNode` falls back to the global mode. Applies to this group
289
343
  // only — nested subgroups resolve their own value through the same chain.
290
- group.display = indexDisplay.get(group.path) ?? meta?.display;
344
+ group.display =
345
+ indexDisplay.get(group.path) ??
346
+ (mirrored ? fallback.indexDisplay.get(group.path) : undefined) ??
347
+ meta?.display;
291
348
  if (meta) {
292
349
  group.label = meta.title ?? group.label;
293
350
  group.icon = meta.icon ?? group.icon;
294
351
  group.order = meta.order ?? group.order;
295
352
  group.collapsed = meta.collapsed ?? group.collapsed;
296
-
297
- if (meta.pages) {
298
- const rank = new Map(meta.pages.map((key, i) => [key, i]));
299
- for (const child of group.children) {
300
- const position = rank.get(child.key);
301
- if (position !== undefined) {
302
- child.order = position;
303
- if (child.kind === "page") {
304
- child.orderIsAuthored = true;
305
- }
306
- }
307
- }
308
- }
309
353
  }
310
354
 
311
355
  for (const child of group.children) {
@@ -317,10 +361,29 @@ const applyFolderMeta = (
317
361
  metaPrefix,
318
362
  sharedMetaPrefix,
319
363
  indexDisplay,
320
- indexRoute
364
+ indexRoute,
365
+ fallback
321
366
  );
322
367
  }
323
368
  }
369
+
370
+ // Ranked after the children resolved their own meta: the `pages` list is
371
+ // this folder's word on its children's order, so it wins over a listed
372
+ // subfolder's own `order` just as it wins over a listed page's
373
+ // `sidebar.order`.
374
+ if (meta?.pages) {
375
+ const rank = new Map(meta.pages.map((key, i) => [key, i]));
376
+ for (const child of group.children) {
377
+ const position = rank.get(child.key);
378
+ if (position !== undefined) {
379
+ child.order = position;
380
+ child.ranked = true;
381
+ if (child.kind === "page") {
382
+ child.orderIsAuthored = true;
383
+ }
384
+ }
385
+ }
386
+ }
324
387
  };
325
388
 
326
389
  /**
@@ -457,22 +520,27 @@ const isAuthoredOrder = (node: MutableNode): boolean =>
457
520
  * excluded: that's the common, intentional case of "just sort alphabetically."
458
521
  * So is a derived, non-authored order (e.g. two changelog entries published
459
522
  * on the same day) — not an authoring mistake.
523
+ *
524
+ * A folder meta `pages` rank is a list position, not a number the author
525
+ * picked, so it only ties with another rank: a listed page at position 1
526
+ * beside an unlisted `01-setup.md` is not a conflict anyone wrote.
460
527
  */
461
528
  const duplicateOrderDiagnostics = (nodes: MutableNode[]): Diagnostic[] => {
462
- const byOrder = new Map<number, MutableNode[]>();
529
+ const byOrder = new Map<string, { order: number; tied: MutableNode[] }>();
463
530
  for (const node of nodes) {
464
531
  if (!Number.isFinite(node.order) || !isAuthoredOrder(node)) {
465
532
  continue;
466
533
  }
467
- const tied = byOrder.get(node.order);
468
- if (tied) {
469
- tied.push(node);
534
+ const key = `${node.ranked === true}:${node.order}`;
535
+ const entry = byOrder.get(key);
536
+ if (entry) {
537
+ entry.tied.push(node);
470
538
  } else {
471
- byOrder.set(node.order, [node]);
539
+ byOrder.set(key, { order: node.order, tied: [node] });
472
540
  }
473
541
  }
474
542
  const diagnostics: Diagnostic[] = [];
475
- for (const [order, tied] of byOrder) {
543
+ for (const { order, tied } of byOrder.values()) {
476
544
  if (tied.length > 1) {
477
545
  const names = tied.map((node) => `"${node.label}"`);
478
546
  const list =
@@ -596,13 +664,23 @@ const buildFileSystemSidebar = (
596
664
  sharedMetaPrefix: string,
597
665
  display: SidebarDisplay,
598
666
  tabPaths: Set<string>,
599
- diagnostics: Diagnostic[] = []
667
+ diagnostics: Diagnostic[] = [],
668
+ fallbackMetaPrefix?: string
600
669
  ): NavNode[] => {
601
670
  const root = createGroup("", "", "", 0);
602
671
  // Folder path -> `sidebar.display` from that folder's index page frontmatter.
603
672
  // Collected before the hidden filter (like the title check): hiding the index
604
673
  // row from the panel shouldn't stop it configuring its group.
605
674
  const indexDisplay = new Map<string, SidebarDisplay>();
675
+ // What a group with no meta of this locale's mirrors from the fallback
676
+ // locale (see `applyFolderMeta`).
677
+ const fallback: FallbackFolderMeta = {
678
+ indexDisplay: new Map(),
679
+ meta: (path) =>
680
+ fallbackMetaPrefix === undefined
681
+ ? undefined
682
+ : folderMeta.get(metaKey(path, fallbackMetaPrefix)),
683
+ };
606
684
  // Folder path -> that folder's index page route, for the group row's link.
607
685
  // Also collected before the hidden filter: hiding the index row is how a
608
686
  // site drops the duplicate label under a linked header, so the link must
@@ -631,12 +709,16 @@ const buildFileSystemSidebar = (
631
709
  if (diagnostic) {
632
710
  diagnostics.push(diagnostic);
633
711
  }
634
- // Fallback-filled pages are exempt (like the title check above): their
635
- // frontmatter belongs to the fallback locale, and letting it through
636
- // would override this locale's own authored `meta.ts` display — then
637
- // silently flip again the day the index gets translated.
638
- if (page.meta.sidebar.display && !page.fallback) {
639
- indexDisplay.set(dirs.join("/"), page.meta.sidebar.display);
712
+ // A fallback-filled index's display is kept apart (like the title check
713
+ // above exempts it): its frontmatter belongs to the fallback locale, so
714
+ // it only speaks for a group this locale has no meta for — letting it
715
+ // override this locale's own authored `meta.ts` display would silently
716
+ // flip again the day the index gets translated.
717
+ if (page.meta.sidebar.display) {
718
+ (page.fallback ? fallback.indexDisplay : indexDisplay).set(
719
+ dirs.join("/"),
720
+ page.meta.sidebar.display
721
+ );
640
722
  }
641
723
  if (dirs.length > 0) {
642
724
  indexRoute.set(dirs.join("/"), page.route);
@@ -658,17 +740,39 @@ const buildFileSystemSidebar = (
658
740
  const folderParts = isIndexStem(stem)
659
741
  ? routeSegments
660
742
  : routeSegments.slice(0, -1);
661
- const routeDirCount = dirs.filter((dir) => !GROUP_FOLDER.test(dir)).length;
743
+ const routeDirCount = dirs.filter(
744
+ (dir) => groupName(dir) === undefined
745
+ ).length;
662
746
  const offset = Math.max(0, folderParts.length - routeDirCount);
747
+ // A frontmatter `slug` publishes the page away from its folder, so its
748
+ // route says nothing about where the folder lives (`guides/a.md` with
749
+ // `slug: install` is `/install`). Its groups read their path off the folder
750
+ // names instead, behind the route's leading base/locale/version segments
751
+ // (whatever precedes the slug's own `versionKey`), and only where no
752
+ // other page under the folder supplies one.
753
+ const slugged = page.meta.slug !== undefined;
754
+ const leading = routeSegments.slice(
755
+ 0,
756
+ Math.max(
757
+ 0,
758
+ routeSegments.length - page.versionKey.split("/").filter(Boolean).length
759
+ )
760
+ );
663
761
 
664
762
  let parent = root;
665
763
  let consumed = offset;
764
+ const folders: string[] = [];
666
765
  for (const dir of dirs) {
667
766
  parent = ensureGroup(parent, dir);
668
- if (!GROUP_FOLDER.test(dir)) {
767
+ if (groupName(dir) === undefined) {
669
768
  consumed += 1;
769
+ folders.push(stripOrderingPrefix(dir));
770
+ }
771
+ if (slugged) {
772
+ parent.folderPath ??= `/${[...leading, ...folders].join("/")}`;
773
+ } else {
774
+ parent.routePath ??= `/${folderParts.slice(0, consumed).join("/")}`;
670
775
  }
671
- parent.routePath ??= `/${folderParts.slice(0, consumed).join("/")}`;
672
776
  }
673
777
 
674
778
  const { order, orderIsAuthored } = pageOrder(page, filename);
@@ -695,7 +799,8 @@ const buildFileSystemSidebar = (
695
799
  metaPrefix,
696
800
  sharedMetaPrefix,
697
801
  indexDisplay,
698
- indexRoute
802
+ indexRoute,
803
+ fallback
699
804
  );
700
805
  sortNodes(root.children, diagnostics);
701
806
  hoistPages(root.children, display, true);
@@ -703,7 +808,11 @@ const buildFileSystemSidebar = (
703
808
  return root.children.map((child) => toNavNode(child, display));
704
809
  };
705
810
 
706
- const normalizeRef = (ref: string): string => {
811
+ /**
812
+ * An explicit-sidebar ref (`"guides/"`, `"/guides/index"`, `"index"`) in the
813
+ * slashless route form pages are keyed by (`/guides`, `/`).
814
+ */
815
+ export const normalizeRef = (ref: string): string => {
707
816
  if (ref === "index") {
708
817
  return "/";
709
818
  }
@@ -734,9 +843,36 @@ const routeForRef = (
734
843
  };
735
844
 
736
845
  /** An explicit-config sidebar item written as a bare page-ref string. */
737
- const isPageRef = (item: SidebarItemConfig): item is string =>
846
+ export const isPageRef = (item: SidebarItemConfig): item is string =>
738
847
  typeof item === "string";
739
848
 
849
+ /**
850
+ * Pages keyed the way explicit-sidebar refs look them up: by the
851
+ * locale-agnostic `translationKey` under i18n (`byLogical`), so one authored
852
+ * sidebar maps onto every locale's pages, else by route. Refs are authored as
853
+ * if mounted at root while `page.route` carries the base, so each page is also
854
+ * aliased under its base-less route (the `translationKey` is base-less
855
+ * already).
856
+ */
857
+ export const pagesByRef = (
858
+ pages: PageRecord[],
859
+ basePath: string,
860
+ byLogical: boolean
861
+ ): Map<string, PageRecord> => {
862
+ const byRoute = new Map(
863
+ pages.map((page) => [byLogical ? page.translationKey : page.route, page])
864
+ );
865
+ if (basePath && !byLogical) {
866
+ for (const page of pages) {
867
+ const bare = stripBasePath(basePath, page.route);
868
+ if (!byRoute.has(bare)) {
869
+ byRoute.set(bare, page);
870
+ }
871
+ }
872
+ }
873
+ return byRoute;
874
+ };
875
+
740
876
  /**
741
877
  * Convert one non-group explicit-config sidebar item (string ref, `root`, or
742
878
  * `href`) to a nav node, or null to skip. Group items (`item.items`) are handled
@@ -822,6 +958,35 @@ const buildConfigSidebar = (
822
958
  return nodes;
823
959
  };
824
960
 
961
+ /**
962
+ * The two roots a tab's target resolves between, both based: `tabs`, the root
963
+ * of the space tab paths are written in (the current docs' localized root,
964
+ * `/fr`), and `tree`, the root this tree's pages live under — the same route,
965
+ * except in an archived version tree (`/fr/v1.0`).
966
+ */
967
+ interface TabRoots {
968
+ tabs: string;
969
+ tree: string;
970
+ }
971
+
972
+ /**
973
+ * `path` relative to `root` (`/fr/guides` under `/fr` is `/guides`, the root
974
+ * itself is `/`), or undefined when it isn't under it.
975
+ */
976
+ const pathUnder = (path: string, root: string): string | undefined => {
977
+ if (root === "/") {
978
+ return path;
979
+ }
980
+ if (path === root) {
981
+ return "/";
982
+ }
983
+ return path.startsWith(`${root}/`) ? path.slice(root.length) : undefined;
984
+ };
985
+
986
+ /** Mount a root-relative remainder (`/guides`, never `/`) under `root`. */
987
+ const joinRoute = (root: string, rest: string): string =>
988
+ root === "/" ? rest : `${root}${rest}`;
989
+
825
990
  /**
826
991
  * Resolve a tab's clickable target. A tab's `path` scopes its sidebar section
827
992
  * but need not be a real route — a section with no index page would 404 if the
@@ -832,30 +997,40 @@ const buildConfigSidebar = (
832
997
  *
833
998
  * Routes outside the content tree are served at their own path, not under
834
999
  * `basePath` (`pages/changelog.astro` answers `/changelog` on a `/docs`-based
835
- * site), while tab paths arrive based (`/docs/changelog`). So when the based
836
- * path is neither an outside route nor a tree node, the base-less path is
837
- * checked against the outside routes before the section's first page wins.
1000
+ * site) or a locale prefix, while tab paths arrive based and localized
1001
+ * (`/docs/fr/changelog`). So when the tab path is neither an outside route nor
1002
+ * a tree node, its base-less and then its root-relative form (`/changelog`)
1003
+ * are checked against the outside routes before the section's first page
1004
+ * wins.
1005
+ *
1006
+ * In an archived version tree the section is looked up under the versioned
1007
+ * root (`/v1.0/guides`), where that tree's pages live — tab paths stay in
1008
+ * current-docs space, where nothing in the tree could match.
838
1009
  */
839
1010
  const resolveTabHref = (
840
1011
  sidebar: NavNode[],
841
1012
  path: string,
842
1013
  extraRoutes: ReadonlySet<string>,
843
- basePath: string
1014
+ basePath: string,
1015
+ roots: TabRoots
844
1016
  ): string => {
845
1017
  if (extraRoutes.has(path)) {
846
1018
  return path;
847
1019
  }
1020
+ const rest = pathUnder(path, roots.tabs);
1021
+ const section =
1022
+ rest === undefined || rest === "/" ? path : joinRoute(roots.tree, rest);
848
1023
  let first: string | undefined;
849
1024
  const walk = (nodes: NavNode[]): boolean => {
850
1025
  for (const node of nodes) {
851
1026
  const { route } = node;
852
- if (route === path) {
1027
+ if (route === section) {
853
1028
  return true;
854
1029
  }
855
1030
  if (
856
1031
  first === undefined &&
857
1032
  route !== undefined &&
858
- route.startsWith(`${path}/`)
1033
+ route.startsWith(`${section}/`)
859
1034
  ) {
860
1035
  first = route;
861
1036
  }
@@ -866,10 +1041,16 @@ const resolveTabHref = (
866
1041
  return false;
867
1042
  };
868
1043
  if (walk(sidebar)) {
869
- return path;
1044
+ return section;
870
1045
  }
871
1046
  const bare = stripBasePath(basePath, path);
872
- return extraRoutes.has(bare) ? bare : (first ?? path);
1047
+ if (extraRoutes.has(bare)) {
1048
+ return bare;
1049
+ }
1050
+ if (rest !== undefined && extraRoutes.has(rest)) {
1051
+ return rest;
1052
+ }
1053
+ return first ?? path;
873
1054
  };
874
1055
 
875
1056
  /**
@@ -884,11 +1065,13 @@ const withTabHrefs = (
884
1065
  tabs: NavTab[],
885
1066
  sidebar: NavNode[],
886
1067
  extraRoutes: ReadonlySet<string>,
887
- basePath: string
1068
+ basePath: string,
1069
+ roots: TabRoots
888
1070
  ): NavTab[] =>
889
1071
  tabs.map((tab) => {
890
1072
  const href =
891
- tab.href ?? resolveTabHref(sidebar, tab.path, extraRoutes, basePath);
1073
+ tab.href ??
1074
+ resolveTabHref(sidebar, tab.path, extraRoutes, basePath, roots);
892
1075
  return href === tab.path ? tab : { ...tab, href };
893
1076
  });
894
1077
 
@@ -978,6 +1161,13 @@ export const buildNavigation = (
978
1161
  * group path, e.g. `fr`, `v1.0`, or `v1.0/fr`.
979
1162
  */
980
1163
  metaPrefix?: string;
1164
+ /**
1165
+ * Folder-meta lookup prefix of the i18n fallback locale, whose untranslated
1166
+ * pages pad this tree (`""` for the default locale of the current
1167
+ * version). A group with no meta of this locale's mirrors the fallback
1168
+ * locale's. Unset for the fallback locale itself, and without i18n.
1169
+ */
1170
+ fallbackMetaPrefix?: string;
981
1171
  /**
982
1172
  * Prefix for shared `meta.$.*` lookups — the version dir inside a
983
1173
  * snapshot (`v1.0`), since shared meta is locale-agnostic but still
@@ -999,6 +1189,14 @@ export const buildNavigation = (
999
1189
  * scoping.
1000
1190
  */
1001
1191
  localizedRoot?: string;
1192
+ /**
1193
+ * The root of the space tab paths are written in, before `basePath`: the
1194
+ * current docs' localized root (`"/"`, `/fr`). Defaults to
1195
+ * `localizedRoot`; an archived version tree passes it because its own
1196
+ * root is versionized (`/fr/v1.0`) while tab paths are not, so a tab's
1197
+ * section can be found under the version.
1198
+ */
1199
+ tabRoot?: string;
1002
1200
  /**
1003
1201
  * Sink for diagnostics produced while building the tree (duplicate sidebar
1004
1202
  * `order` values, index-page title/folder-meta-title mismatches). Pushed
@@ -1027,24 +1225,7 @@ export const buildNavigation = (
1027
1225
  basePath,
1028
1226
  options
1029
1227
  );
1030
- const byRoute = new Map(
1031
- pages.map((page) => [
1032
- options.refByLogical ? page.translationKey : page.route,
1033
- page,
1034
- ])
1035
- );
1036
- // Explicit-sidebar refs (`"foo/index"`) are authored as if mounted at root,
1037
- // but `page.route` carries the base — alias each page under its base-less
1038
- // route so a bare ref still resolves. (The i18n `refByLogical` map is already
1039
- // keyed by the base-less `translationKey`, so it needs no alias.)
1040
- if (basePath && !options.refByLogical) {
1041
- for (const page of pages) {
1042
- const bare = stripBasePath(basePath, page.route);
1043
- if (!byRoute.has(bare)) {
1044
- byRoute.set(bare, page);
1045
- }
1046
- }
1047
- }
1228
+ const byRoute = pagesByRef(pages, basePath, Boolean(options.refByLogical));
1048
1229
 
1049
1230
  // A tab pointing at the tree root spans the whole sidebar rather than one
1050
1231
  // section, so it must not feed tab-section hoisting. `tabs` carries final
@@ -1057,6 +1238,13 @@ export const buildNavigation = (
1057
1238
  // paths stay in current-docs space, so root-tab checks use `isRootTab`
1058
1239
  // containment, not equality.
1059
1240
  const rootTabPath = withBasePath(basePath, options.localizedRoot ?? "/");
1241
+ const tabRoots: TabRoots = {
1242
+ tabs: withBasePath(
1243
+ basePath,
1244
+ options.tabRoot ?? options.localizedRoot ?? "/"
1245
+ ),
1246
+ tree: rootTabPath,
1247
+ };
1060
1248
 
1061
1249
  // Emitted here, before the sidebar-mode branch: an explicit config sidebar
1062
1250
  // ignores a stray `sidebar.display` just like the filesystem sidebar does
@@ -1080,7 +1268,7 @@ export const buildNavigation = (
1080
1268
  root: rootTabPath,
1081
1269
  selectors,
1082
1270
  sidebar,
1083
- tabs: withTabHrefs(tabs, sidebar, extraRoutes, basePath),
1271
+ tabs: withTabHrefs(tabs, sidebar, extraRoutes, basePath, tabRoots),
1084
1272
  };
1085
1273
  }
1086
1274
 
@@ -1094,7 +1282,8 @@ export const buildNavigation = (
1094
1282
  new Set(
1095
1283
  tabs.flatMap((tab) => (isRootTab(tab, rootTabPath) ? [] : [tab.path]))
1096
1284
  ),
1097
- diagnostics
1285
+ diagnostics,
1286
+ options.fallbackMetaPrefix
1098
1287
  );
1099
1288
  return {
1100
1289
  actions,
@@ -1103,6 +1292,6 @@ export const buildNavigation = (
1103
1292
  root: rootTabPath,
1104
1293
  selectors,
1105
1294
  sidebar,
1106
- tabs: withTabHrefs(tabs, sidebar, extraRoutes, basePath),
1295
+ tabs: withTabHrefs(tabs, sidebar, extraRoutes, basePath, tabRoots),
1107
1296
  };
1108
1297
  };
@@ -0,0 +1,27 @@
1
+ /**
2
+ * A file or folder name's ordering prefix: digits, then a `-`, `_`, or `.`
3
+ * separator (`01-intro`, `2_setup`). It sorts the generated sidebar and is
4
+ * dropped from the route.
5
+ */
6
+ const ORDERING_PREFIX = /^(?<order>\d+)[-_.]/u;
7
+
8
+ /**
9
+ * Digit-led names that are names, not orders, and stay whole: a version
10
+ * (`1.2.0`, `2.0`), which a `1.` prefix would split into `2.0`, and an ISO date
11
+ * (`2024-01-05`, `2024-01-05-first-post`), whose year would sort the sidebar
12
+ * and leave only `01-05…` of the route.
13
+ */
14
+ const NOT_AN_ORDER = /^(?:\d+\.\d|\d{4}-\d{2}-\d{2}(?:[-_.]|$))/u;
15
+
16
+ /** The digits of `name`'s ordering prefix (`01` of `01-intro`), if it has one. */
17
+ export const orderingPrefix = (name: string): string | undefined =>
18
+ NOT_AN_ORDER.test(name)
19
+ ? undefined
20
+ : name.match(ORDERING_PREFIX)?.groups?.order;
21
+
22
+ /** `name` without its ordering prefix (`01-intro` -> `intro`). */
23
+ export const stripOrderingPrefix = (name: string): string => {
24
+ const order = orderingPrefix(name);
25
+ // The prefix is the digits plus their one-character separator.
26
+ return order === undefined ? name : name.slice(order.length + 1);
27
+ };