docs.ts 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573
  1. /**
  2. * Canonical publication manifest for the documentation website.
  3. *
  4. * Markdown stays in its owning repository tier. This manifest maps each
  5. * canonical source into matching route trees for both site locales; when a
  6. * translation is absent, both routes intentionally project the available
  7. * source instead of copying Markdown.
  8. */
  9. /** Locale key used by the VitePress site. */
  10. export type DocsLocale = 'root' | 'en'
  11. /** Sidebar collection rendered for one locale and top-level module. */
  12. export type DocsSidebar =
  13. | 'zh-guide'
  14. | 'zh-develop'
  15. | 'zh-reference'
  16. | 'en-guide'
  17. | 'en-develop'
  18. | 'en-reference'
  19. /** A page projected into the VitePress source tree. */
  20. export interface DocsPage {
  21. /** VitePress locale whose route tree owns this projection. */
  22. locale: DocsLocale
  23. /** Language of the canonical source currently projected at this route. */
  24. contentLocale: 'zh-CN' | 'en-US'
  25. /** Repository-relative canonical Markdown source. */
  26. source: string
  27. /** VitePress route, including the `.md` suffix. */
  28. route: string
  29. /** Navigation label shown in the sidebar. */
  30. label: string
  31. /** Sidebar collection that owns the page, or null for a locale home page. */
  32. sidebar: DocsSidebar | null
  33. /** Section label within the sidebar. */
  34. section: string
  35. /** Stable order within the section. */
  36. order: number
  37. /** Heading levels included in this page's VitePress outline. */
  38. outline?: number | readonly [number, number] | 'deep' | false
  39. /** Additional repository paths that resolve to this page. */
  40. sourceAliases?: string[]
  41. }
  42. interface MirroredPage {
  43. source: string | Record<DocsLocale, string>
  44. route: string
  45. contentLocale: DocsPage['contentLocale'] | Record<DocsLocale, DocsPage['contentLocale']>
  46. label: Record<DocsLocale, string>
  47. sidebar: Record<DocsLocale, DocsSidebar | null>
  48. section: Record<DocsLocale, string>
  49. order: number
  50. outline?: DocsPage['outline']
  51. sourceAliases?: string[] | Partial<Record<DocsLocale, string[]>>
  52. }
  53. type PairedPage = Omit<MirroredPage, 'source' | 'contentLocale' | 'sourceAliases'> & {
  54. /** English side of a sibling `foo.md` / `foo.zh.md` pair. */
  55. source: string
  56. /** Language-neutral repository aliases, such as the directory of an index page. */
  57. sourceAliases?: string[]
  58. }
  59. function localized<T>(value: T | Record<DocsLocale, T>, locale: DocsLocale): T {
  60. return typeof value === 'object' && value !== null && !Array.isArray(value)
  61. ? (value as Record<DocsLocale, T>)[locale]
  62. : value
  63. }
  64. function mirroredPages(pages: MirroredPage[]): DocsPage[] {
  65. return pages.flatMap(page => (['root', 'en'] as const).map((locale) => {
  66. const aliases = page.sourceAliases === undefined
  67. ? undefined
  68. : Array.isArray(page.sourceAliases) ? page.sourceAliases : page.sourceAliases[locale]
  69. return {
  70. locale,
  71. contentLocale: localized(page.contentLocale, locale),
  72. source: localized(page.source, locale),
  73. route: locale === 'root' ? page.route : `en/${page.route}`,
  74. label: page.label[locale],
  75. sidebar: page.sidebar[locale],
  76. section: page.section[locale],
  77. order: page.order,
  78. ...(page.outline === undefined ? {} : { outline: page.outline }),
  79. ...(aliases === undefined ? {} : { sourceAliases: aliases }),
  80. }
  81. }))
  82. }
  83. function pairedPages(pages: PairedPage[]): DocsPage[] {
  84. return mirroredPages(pages.map((page) => {
  85. const chineseSource = page.source.replace(/\.md$/, '.zh.md')
  86. const sharedAliases = page.sourceAliases ?? []
  87. return {
  88. ...page,
  89. source: { root: chineseSource, en: page.source },
  90. contentLocale: { root: 'zh-CN', en: 'en-US' },
  91. sourceAliases: {
  92. root: [...sharedAliases, page.source],
  93. en: [...sharedAliases, chineseSource],
  94. },
  95. }
  96. }))
  97. }
  98. const homeAndGuide = pairedPages([
  99. {
  100. source: 'docs/user/index.md',
  101. route: 'index.md',
  102. label: { root: 'DeepSeek Harness', en: 'DeepSeek Harness' },
  103. sidebar: { root: null, en: null },
  104. section: { root: '首页', en: 'Home' },
  105. order: 0,
  106. },
  107. {
  108. source: 'docs/user/guide/index.md',
  109. route: 'guide/quickstart.md',
  110. label: { root: '使用 Web UI', en: 'Use the Web UI' },
  111. sidebar: { root: 'zh-guide', en: 'en-guide' },
  112. section: { root: '入门', en: 'Guide' },
  113. order: 1,
  114. sourceAliases: ['docs/user/guide'],
  115. },
  116. {
  117. source: 'docs/user/guide/providers.md',
  118. route: 'guide/providers.md',
  119. label: { root: '配置模型', en: 'Configure models' },
  120. sidebar: { root: 'zh-guide', en: 'en-guide' },
  121. section: { root: '入门', en: 'Guide' },
  122. order: 2,
  123. },
  124. {
  125. source: 'docs/user/guide/network-proxy.md',
  126. route: 'guide/network-proxy.md',
  127. label: { root: '网络代理', en: 'Network proxy' },
  128. sidebar: { root: 'zh-guide', en: 'en-guide' },
  129. section: { root: '入门', en: 'Guide' },
  130. order: 3,
  131. },
  132. {
  133. source: 'docs/user/guide/python-sdk.md',
  134. route: 'guide/python-sdk.md',
  135. label: { root: 'Python', en: 'Python' },
  136. sidebar: { root: 'zh-guide', en: 'en-guide' },
  137. section: { root: 'SDK', en: 'SDK' },
  138. order: 1,
  139. },
  140. {
  141. source: 'docs/user/guide/github-review.md',
  142. route: 'guide/github-review.md',
  143. label: { root: 'GitHub 评审会话', en: 'GitHub review sessions' },
  144. sidebar: { root: 'zh-guide', en: 'en-guide' },
  145. section: { root: '自动化', en: 'Automation' },
  146. order: 1,
  147. },
  148. {
  149. source: 'docs/user/guide/schedule.md',
  150. route: 'guide/schedule.md',
  151. label: { root: '会话内提醒', en: 'Session reminders' },
  152. sidebar: { root: 'zh-guide', en: 'en-guide' },
  153. section: { root: '自动化', en: 'Automation' },
  154. order: 2,
  155. },
  156. {
  157. source: 'docs/user/guide/mcp-memory.md',
  158. route: 'guide/mcp-memory.md',
  159. label: { root: '记忆 MCP', en: 'Memory MCP' },
  160. sidebar: { root: 'zh-guide', en: 'en-guide' },
  161. section: { root: '集成', en: 'Integrations' },
  162. order: 1,
  163. },
  164. ])
  165. const develop = pairedPages([
  166. {
  167. source: 'docs/user/develop/basic/index.md',
  168. route: 'develop/basic/index.md',
  169. label: { root: '第一个 Harness 插件', en: 'Your first Harness plugin' },
  170. sidebar: { root: 'zh-develop', en: 'en-develop' },
  171. section: { root: '基础', en: 'Basics' },
  172. order: 1,
  173. sourceAliases: ['docs/user/develop/basic'],
  174. },
  175. {
  176. source: 'docs/user/develop/basic/tool.md',
  177. route: 'develop/basic/tool.md',
  178. label: { root: '开发一个 Tool', en: 'Build a tool' },
  179. sidebar: { root: 'zh-develop', en: 'en-develop' },
  180. section: { root: '基础', en: 'Basics' },
  181. order: 2,
  182. },
  183. {
  184. source: 'docs/user/develop/basic/config.md',
  185. route: 'develop/basic/config.md',
  186. label: { root: '插件配置', en: 'Plugin configuration' },
  187. sidebar: { root: 'zh-develop', en: 'en-develop' },
  188. section: { root: '基础', en: 'Basics' },
  189. order: 3,
  190. },
  191. {
  192. source: 'docs/user/develop/basic/publish.md',
  193. route: 'develop/basic/publish.md',
  194. label: { root: '打包与安装插件', en: 'Package and install' },
  195. sidebar: { root: 'zh-develop', en: 'en-develop' },
  196. section: { root: '基础', en: 'Basics' },
  197. order: 4,
  198. },
  199. {
  200. source: 'docs/user/develop/framework/index.md',
  201. route: 'develop/framework/index.md',
  202. label: { root: '插件与生命周期', en: 'Plugin lifecycle' },
  203. sidebar: { root: 'zh-develop', en: 'en-develop' },
  204. section: { root: '框架能力', en: 'Framework' },
  205. order: 1,
  206. sourceAliases: ['docs/user/develop/framework'],
  207. },
  208. {
  209. source: 'docs/user/develop/framework/service.md',
  210. route: 'develop/framework/service.md',
  211. label: { root: '服务与依赖', en: 'Services and dependencies' },
  212. sidebar: { root: 'zh-develop', en: 'en-develop' },
  213. section: { root: '框架能力', en: 'Framework' },
  214. order: 2,
  215. },
  216. {
  217. source: 'docs/user/develop/framework/events.md',
  218. route: 'develop/framework/events.md',
  219. label: { root: '事件系统', en: 'Event system' },
  220. sidebar: { root: 'zh-develop', en: 'en-develop' },
  221. section: { root: '框架能力', en: 'Framework' },
  222. order: 3,
  223. },
  224. {
  225. source: 'docs/user/develop/practice/index.md',
  226. route: 'develop/practice/index.md',
  227. label: { root: '能力的三层拆分', en: 'Capability layering' },
  228. sidebar: { root: 'zh-develop', en: 'en-develop' },
  229. section: { root: '实战', en: 'Practice' },
  230. order: 1,
  231. sourceAliases: ['docs/user/develop/practice'],
  232. },
  233. {
  234. source: 'docs/user/develop/practice/llm-adapter.md',
  235. route: 'develop/practice/llm-adapter.md',
  236. label: { root: 'LLM 适配器', en: 'LLM adapter' },
  237. sidebar: { root: 'zh-develop', en: 'en-develop' },
  238. section: { root: '实战', en: 'Practice' },
  239. order: 2,
  240. },
  241. {
  242. source: 'docs/user/develop/practice/dynamic-cordis.md',
  243. route: 'develop/practice/dynamic-cordis.md',
  244. label: { root: '运行时 Cordis 工具', en: 'Runtime Cordis tools' },
  245. sidebar: { root: 'zh-develop', en: 'en-develop' },
  246. section: { root: '实战', en: 'Practice' },
  247. order: 3,
  248. },
  249. ])
  250. const cordisTutorial = pairedPages(([
  251. ['index.md', '总览', 'Overview'],
  252. ['01-first-plugin.md', '1. 第一个插件', '1. Your first plugin'],
  253. ['02-lifecycle-and-effects.md', '2. 生命周期与副作用', '2. Lifecycle and effects'],
  254. ['03-services.md', '3. 服务', '3. Services'],
  255. ['04-events.md', '4. 事件', '4. Events'],
  256. ['05-config.md', '5. 配置', '5. Configuration'],
  257. ['06-composition-and-hmr.md', '6. 组合与热重载', '6. Composition and HMR'],
  258. ['07-into-the-harness.md', '7. 进入 Harness', '7. Into the harness'],
  259. ] as const).map(([file, rootLabel, enLabel], order): PairedPage => ({
  260. source: `docs/cordis-tutorial/${file}`,
  261. route: `develop/cordis-tutorial/${file}`,
  262. label: { root: rootLabel, en: enLabel },
  263. sidebar: { root: 'zh-develop', en: 'en-develop' },
  264. section: { root: 'Cordis 框架教程', en: 'Cordis framework tutorial' },
  265. order,
  266. ...(file === 'index.md' ? { sourceAliases: ['docs/cordis-tutorial'] } : {}),
  267. })))
  268. const cordisPrimerReference = pairedPages([
  269. {
  270. source: 'docs/cordis-primer.md',
  271. route: 'reference/cordis-primer.md',
  272. label: { root: 'Cordis 入门', en: 'Cordis primer' },
  273. sidebar: { root: 'zh-reference', en: 'en-reference' },
  274. section: { root: '概念', en: 'Concepts' },
  275. order: 1,
  276. },
  277. ])
  278. /**
  279. * Subsystem pages grouped by the concern they document, as `[Chinese section,
  280. * English section, pages]`. One flat list of every subsystem pushed the rest of
  281. * the reference sidebar below the fold.
  282. */
  283. const subsystemGroups = [
  284. ['总览', 'Overview', [
  285. ['README.md', '子系统', 'Subsystems'],
  286. ]],
  287. ['内核与作用域', 'Core and scopes', [
  288. ['core.md', '核心', 'Core'],
  289. ['scope.md', '作用域', 'Scopes'],
  290. ['invariants.md', '运行时不变式', 'Runtime invariants'],
  291. ]],
  292. ['会话与持久化', 'Sessions and persistence', [
  293. ['session.md', '会话', 'Sessions'],
  294. ['session-query.md', '会话查询', 'Session query'],
  295. ['session-reference.md', '会话引用', 'Session references'],
  296. ['session-title.md', '会话标题', 'Session titles'],
  297. ['session-projection.md', '会话投影', 'Session projections'],
  298. ['persistence.md', '会话持久化', 'Session persistence'],
  299. ['spill.md', 'Spill 存储', 'Spill storage'],
  300. ['session-telemetry.md', '遥测', 'SessionTelemetryBackend'],
  301. ]],
  302. ['模型与上下文', 'Model and context', [
  303. ['llm-streaming.md', 'LLM 流式响应', 'LLM streaming'],
  304. ['token-meter.md', 'Token 计量', 'Token metering'],
  305. ['system-prompt.md', '系统提示词', 'System prompts'],
  306. ['compaction.md', '上下文压缩', 'Compaction'],
  307. ]],
  308. ['执行与工具', 'Execution and tools', [
  309. ['tools.md', '工具', 'Tools'],
  310. ['shell.md', 'Bash 执行', 'Bash execution'],
  311. ['subprocess.md', '子进程', 'Subprocesses'],
  312. ['terminal.md', 'PTY 会话', 'PTY sessions'],
  313. ['jobs.md', '后台任务', 'Background jobs'],
  314. ['filesystem.md', '文件系统', 'Filesystem'],
  315. ['lsp.md', 'LSP 导航', 'LSP navigation'],
  316. ['code-runtime.md', '代码运行时', 'Code runtime'],
  317. ['web.md', 'Web 访问', 'Web access'],
  318. ['skills.md', '技能', 'Skills'],
  319. ['workflow.md', '工作流', 'Workflows'],
  320. ['subagent.md', '子代理', 'Subagents'],
  321. ]],
  322. ['策略与交互', 'Policy and interaction', [
  323. ['approval.md', '审批', 'Approvals'],
  324. ['permission-presets.md', '权限预设', 'Permission presets'],
  325. ['sandbox.md', '沙箱', 'Sandboxing'],
  326. ['plan.md', '计划模式', 'Plan mode'],
  327. ['user-questions.md', '用户交互', 'User interaction'],
  328. ['commands.md', '命令', 'Human commands'],
  329. ['goal.md', '目标', 'Goals'],
  330. ['schedule.md', '定时提醒', 'Scheduled reminders'],
  331. ]],
  332. ['平台与接入', 'Platform and access', [
  333. ['web-server.md', 'HTTP 服务器', 'HTTP server'],
  334. ['web-client.md', 'Web Client 架构', 'Web Client architecture'],
  335. ['client-modules.md', '客户端模块', 'Client modules'],
  336. ['slots.md', '客户端 Slots', 'Client slots'],
  337. ['conversation.md', 'Conversation 组装', 'Conversation assembly'],
  338. ['typert.md', 'Typert', 'Typert'],
  339. ['storage.md', '存储', 'Storage'],
  340. ['workspace.md', '工作区', 'Workspaces'],
  341. ['settings.md', '用户设置', 'User settings'],
  342. ['credentials.md', '用户凭据', 'User credentials'],
  343. ]],
  344. ] as const
  345. const subsystemsReference = subsystemGroups.flatMap(([rootSection, enSection, files]) => pairedPages(
  346. files.map(([file, rootLabel, enLabel], order): PairedPage => ({
  347. source: `docs/subsystems/${file}`,
  348. route: file === 'README.md' ? 'reference/subsystems/index.md' : `reference/subsystems/${file}`,
  349. label: { root: rootLabel, en: enLabel },
  350. sidebar: { root: 'zh-reference', en: 'en-reference' },
  351. section: { root: rootSection, en: enSection },
  352. order,
  353. // Subsystem pages carry long third-level sections a two-level outline reaches.
  354. outline: [2, 3],
  355. ...(file === 'README.md' ? { sourceAliases: ['docs/subsystems'] } : {}),
  356. })),
  357. ))
  358. const reference = [
  359. // `docs/deepseek-llm-api-wire-extensions.md` is a repository-only provider protocol reference.
  360. // Projected links intentionally resolve to its GitHub source instead of a public site route.
  361. ...pairedPages(([
  362. ['docs/architecture.md', 'reference/index.md', '架构', 'Architecture', 0],
  363. ] as const).map(([source, route, rootLabel, enLabel, order]): PairedPage => ({
  364. source,
  365. route,
  366. label: { root: rootLabel, en: enLabel },
  367. sidebar: { root: 'zh-reference', en: 'en-reference' },
  368. section: { root: '概念', en: 'Concepts' },
  369. order,
  370. }))),
  371. ...pairedPages(([
  372. ['docs/capability-seams.md', 'reference/capability-seams.md', '能力服务', 'Capability services', 2],
  373. ['docs/agent-lifecycle.md', 'reference/agent-lifecycle.md', 'Agent 生命周期', 'Agent lifecycle', 3],
  374. ['docs/tool-execution-pipeline.md', 'reference/tool-execution-pipeline.md', 'Tool 执行', 'Tool execution', 4],
  375. ['docs/api-gateway.md', 'reference/api-gateway.md', 'API Gateway', 'API Gateway', 5],
  376. ] as const).map(([source, route, rootLabel, enLabel, order]): PairedPage => ({
  377. source,
  378. route,
  379. label: { root: rootLabel, en: enLabel },
  380. sidebar: { root: 'zh-reference', en: 'en-reference' },
  381. section: { root: '概念', en: 'Concepts' },
  382. order,
  383. }))),
  384. ...pairedPages(([
  385. ['docs/config-catalog.md', 'reference/config-catalog.md', '插件配置', 'Plugin configuration'],
  386. ['docs/tool-catalog.md', 'reference/tool-catalog.md', 'Tool Schema', 'Tool schemas'],
  387. ['docs/persistence-catalog.md', 'reference/persistence-catalog.md', '持久化事件', 'Persistence events', 'deep'],
  388. ] as const).map(([source, route, rootLabel, enLabel, outline], order): PairedPage => ({
  389. source,
  390. route,
  391. label: { root: rootLabel, en: enLabel },
  392. sidebar: { root: 'zh-reference', en: 'en-reference' },
  393. section: { root: '生成参考', en: 'Generated reference' },
  394. order,
  395. ...(outline === undefined ? {} : { outline }),
  396. }))),
  397. ...pairedPages(([
  398. ['context.md', 'Context', 'Context'],
  399. ['events.md', 'Events', 'Events'],
  400. ['fiber.md', 'Fiber', 'Fiber'],
  401. ['registry.md', 'Plugin Registry', 'Plugin Registry'],
  402. ['service.md', 'Service', 'Service'],
  403. ] as const).map(([file, rootLabel, enLabel], order): PairedPage => ({
  404. source: `docs/cordis-api/${file}`,
  405. route: `reference/cordis-api/${file}`,
  406. label: { root: rootLabel, en: enLabel },
  407. sidebar: { root: 'zh-reference', en: 'en-reference' },
  408. section: { root: 'Cordis API', en: 'Cordis Core API' },
  409. order,
  410. }))),
  411. ...mirroredPages(([
  412. ['inherited.md', '继承接口面', 'Inherited surface'],
  413. ] as const).map(([file, rootLabel, enLabel], order): MirroredPage => ({
  414. source: `docs/cordis-api/${file}`,
  415. route: `reference/cordis-api/${file}`,
  416. contentLocale: 'en-US',
  417. label: { root: rootLabel, en: enLabel },
  418. sidebar: { root: 'zh-reference', en: 'en-reference' },
  419. section: { root: 'Cordis API', en: 'Cordis Core API' },
  420. order: order + 5,
  421. }))),
  422. ...pairedPages(([
  423. ['adding-a-package.md', '新增 Package', 'Adding a package'],
  424. ['adding-a-tool.md', '新增 Tool', 'Adding a tool'],
  425. ['adding-an-llm-adapter.md', '新增 LLM Adapter', 'Adding an LLM adapter'],
  426. ['adding-a-settings-card.md', '新增设置卡片', 'Adding a settings card'],
  427. ['extension-cookbook.md', '扩展模式', 'Extension patterns'],
  428. ] as const).map(([file, rootLabel, enLabel], order): PairedPage => ({
  429. source: `docs/cookbook/${file}`,
  430. route: `reference/cookbook/${file}`,
  431. label: { root: rootLabel, en: enLabel },
  432. sidebar: { root: 'zh-reference', en: 'en-reference' },
  433. section: { root: '开发手册', en: 'Cookbook' },
  434. order,
  435. }))),
  436. ]
  437. /**
  438. * Sidebar collections of each locale, in the order the site's navigation
  439. * presents them. The navigation bar and the llms.txt index both read this
  440. * sequence, so a new collection lands in both surfaces together.
  441. */
  442. export const localeCollections = {
  443. root: ['zh-guide', 'zh-develop', 'zh-reference'],
  444. en: ['en-guide', 'en-develop', 'en-reference'],
  445. } as const satisfies Record<DocsLocale, readonly DocsSidebar[]>
  446. /** A sidebar group, matched to pages by `label`. */
  447. export interface DocsSection {
  448. /** Group heading, equal to the `section` field of every page it holds. */
  449. label: string
  450. /** Render the group collapsed until it holds the page being read. */
  451. collapsed?: boolean
  452. }
  453. /**
  454. * Every sidebar group, in the order its locale renders it.
  455. *
  456. * The subsystem groups collapse because together they outnumber the rest of the
  457. * reference sidebar; expanded, they push every other group below the fold.
  458. */
  459. const sections: Record<DocsLocale, readonly DocsSection[]> = {
  460. root: [
  461. { label: '入门' }, { label: 'SDK' }, { label: '自动化' }, { label: '集成' },
  462. { label: '基础' }, { label: '框架能力' }, { label: '实战' }, { label: 'Cordis 框架教程' },
  463. { label: '概念' }, { label: '生成参考' }, { label: 'Cordis API' }, { label: '开发手册' },
  464. { label: '总览' },
  465. { label: '内核与作用域', collapsed: true },
  466. { label: '会话与持久化', collapsed: true },
  467. { label: '模型与上下文', collapsed: true },
  468. { label: '执行与工具', collapsed: true },
  469. { label: '策略与交互', collapsed: true },
  470. { label: '平台与接入', collapsed: true },
  471. ],
  472. en: [
  473. { label: 'Guide' }, { label: 'SDK' }, { label: 'Automation' }, { label: 'Integrations' },
  474. { label: 'Basics' }, { label: 'Framework' }, { label: 'Practice' }, { label: 'Cordis framework tutorial' },
  475. { label: 'Concepts' }, { label: 'Generated reference' }, { label: 'Cordis Core API' }, { label: 'Cookbook' },
  476. { label: 'Overview' },
  477. { label: 'Core and scopes', collapsed: true },
  478. { label: 'Sessions and persistence', collapsed: true },
  479. { label: 'Model and context', collapsed: true },
  480. { label: 'Execution and tools', collapsed: true },
  481. { label: 'Policy and interaction', collapsed: true },
  482. { label: 'Platform and access', collapsed: true },
  483. ],
  484. }
  485. /**
  486. * Placement and collapse behavior of one sidebar group.
  487. *
  488. * @param locale - Route tree whose sidebar is being built.
  489. * @param label - Section label carried by the pages in the group.
  490. * @returns The declared group, plus its zero-based position in the locale.
  491. * @throws When the locale declares no placement for the label. Ranking by list
  492. * membership alone would sort an undeclared group silently ahead of every
  493. * declared one.
  494. */
  495. export function sectionSpec(locale: DocsLocale, label: string): DocsSection & { index: number } {
  496. const declared = sections[locale]
  497. const section = declared.find(candidate => candidate.label === label)
  498. if (section === undefined) throw new Error(`Sidebar section "${label}" has no placement in the ${locale} locale.`)
  499. return { ...section, index: declared.indexOf(section) }
  500. }
  501. /** Every canonical page published by the documentation website. */
  502. export const docsPages: DocsPage[] = [
  503. ...homeAndGuide,
  504. ...develop,
  505. ...cordisTutorial,
  506. ...cordisPrimerReference,
  507. ...subsystemsReference,
  508. ...reference,
  509. ]
  510. /**
  511. * Pages of one sidebar collection, in the order the sidebar lists them.
  512. *
  513. * @param locale - Route tree whose sidebar is being built.
  514. * @param collection - Sidebar collection to read.
  515. * @returns The collection's pages, ordered by section placement then by `order`.
  516. */
  517. export function orderedPages(locale: DocsLocale, collection: DocsSidebar): DocsPage[] {
  518. return docsPages
  519. .filter(page => page.locale === locale && page.sidebar === collection)
  520. .sort((left, right) => (
  521. sectionSpec(locale, left.section).index - sectionSpec(locale, right.section).index
  522. || left.order - right.order
  523. ))
  524. }
  525. /**
  526. * Site-relative link for a published route.
  527. *
  528. * @param route - Manifest route, including its `.md` suffix.
  529. * @returns The link VitePress serves the route at.
  530. */
  531. export function routeLink(route: string): string {
  532. return `/${route.replace(/(?:index)?\.md$/, '')}`
  533. }
  534. /**
  535. * Where a top-level navigation item lands.
  536. *
  537. * The target is derived rather than written down: a collection whose first page
  538. * is renamed or reordered would otherwise leave the navigation bar pointing at
  539. * a route the manifest no longer publishes.
  540. *
  541. * @param locale - Route tree the navigation item belongs to.
  542. * @param collection - Sidebar collection the item opens.
  543. * @returns Site-relative link of the collection's first page.
  544. * @throws When the collection publishes no page.
  545. */
  546. export function landingLink(locale: DocsLocale, collection: DocsSidebar): string {
  547. const first = orderedPages(locale, collection)[0]
  548. if (first === undefined) throw new Error(`Sidebar collection "${collection}" publishes no page.`)
  549. return routeLink(first.route)
  550. }