mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
Apply the accepted pre-release package, service, type, directory, and role renames as one repository-wide change.
525 lines
20 KiB
TypeScript
525 lines
20 KiB
TypeScript
/**
|
|
* Canonical publication manifest for the documentation website.
|
|
*
|
|
* Markdown stays in its owning repository tier. This manifest maps each
|
|
* canonical source into matching route trees for both site locales; when a
|
|
* translation is absent, both routes intentionally project the available
|
|
* source instead of copying Markdown.
|
|
*/
|
|
|
|
/** Locale key used by the VitePress site. */
|
|
export type DocsLocale = 'root' | 'en'
|
|
|
|
/** Sidebar collection rendered for one locale and top-level module. */
|
|
export type DocsSidebar =
|
|
| 'zh-guide'
|
|
| 'zh-develop'
|
|
| 'zh-reference'
|
|
| 'en-guide'
|
|
| 'en-develop'
|
|
| 'en-reference'
|
|
|
|
/** A page projected into the VitePress source tree. */
|
|
export interface DocsPage {
|
|
/** VitePress locale whose route tree owns this projection. */
|
|
locale: DocsLocale
|
|
/** Language of the canonical source currently projected at this route. */
|
|
contentLocale: 'zh-CN' | 'en-US'
|
|
/** Repository-relative canonical Markdown source. */
|
|
source: string
|
|
/** VitePress route, including the `.md` suffix. */
|
|
route: string
|
|
/** Navigation label shown in the sidebar. */
|
|
label: string
|
|
/** Sidebar collection that owns the page, or null for a locale home page. */
|
|
sidebar: DocsSidebar | null
|
|
/** Section label within the sidebar. */
|
|
section: string
|
|
/** Stable order within the section. */
|
|
order: number
|
|
/** Heading levels included in this page's VitePress outline. */
|
|
outline?: number | readonly [number, number] | 'deep' | false
|
|
/** Additional repository paths that resolve to this page. */
|
|
sourceAliases?: string[]
|
|
}
|
|
|
|
interface MirroredPage {
|
|
source: string | Record<DocsLocale, string>
|
|
route: string
|
|
contentLocale: DocsPage['contentLocale'] | Record<DocsLocale, DocsPage['contentLocale']>
|
|
label: Record<DocsLocale, string>
|
|
sidebar: Record<DocsLocale, DocsSidebar | null>
|
|
section: Record<DocsLocale, string>
|
|
order: number
|
|
outline?: DocsPage['outline']
|
|
sourceAliases?: string[] | Partial<Record<DocsLocale, string[]>>
|
|
}
|
|
|
|
type PairedPage = Omit<MirroredPage, 'source' | 'contentLocale' | 'sourceAliases'> & {
|
|
/** English side of a sibling `foo.md` / `foo.zh.md` pair. */
|
|
source: string
|
|
/** Language-neutral repository aliases, such as the directory of an index page. */
|
|
sourceAliases?: string[]
|
|
}
|
|
|
|
function localized<T>(value: T | Record<DocsLocale, T>, locale: DocsLocale): T {
|
|
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
? (value as Record<DocsLocale, T>)[locale]
|
|
: value
|
|
}
|
|
|
|
function mirroredPages(pages: MirroredPage[]): DocsPage[] {
|
|
return pages.flatMap(page => (['root', 'en'] as const).map((locale) => {
|
|
const aliases = page.sourceAliases === undefined
|
|
? undefined
|
|
: Array.isArray(page.sourceAliases) ? page.sourceAliases : page.sourceAliases[locale]
|
|
return {
|
|
locale,
|
|
contentLocale: localized(page.contentLocale, locale),
|
|
source: localized(page.source, locale),
|
|
route: locale === 'root' ? page.route : `en/${page.route}`,
|
|
label: page.label[locale],
|
|
sidebar: page.sidebar[locale],
|
|
section: page.section[locale],
|
|
order: page.order,
|
|
...(page.outline === undefined ? {} : { outline: page.outline }),
|
|
...(aliases === undefined ? {} : { sourceAliases: aliases }),
|
|
}
|
|
}))
|
|
}
|
|
|
|
function pairedPages(pages: PairedPage[]): DocsPage[] {
|
|
return mirroredPages(pages.map((page) => {
|
|
const chineseSource = page.source.replace(/\.md$/, '.zh.md')
|
|
const sharedAliases = page.sourceAliases ?? []
|
|
return {
|
|
...page,
|
|
source: { root: chineseSource, en: page.source },
|
|
contentLocale: { root: 'zh-CN', en: 'en-US' },
|
|
sourceAliases: {
|
|
root: [...sharedAliases, page.source],
|
|
en: [...sharedAliases, chineseSource],
|
|
},
|
|
}
|
|
}))
|
|
}
|
|
|
|
const homeAndGuide = pairedPages([
|
|
{
|
|
source: 'docs/user/index.md',
|
|
route: 'index.md',
|
|
label: { root: 'DeepSeek Harness', en: 'DeepSeek Harness' },
|
|
sidebar: { root: null, en: null },
|
|
section: { root: '首页', en: 'Home' },
|
|
order: 0,
|
|
},
|
|
{
|
|
source: 'docs/user/guide/index.md',
|
|
route: 'guide/quickstart.md',
|
|
label: { root: '使用 Web UI', en: 'Use the Web UI' },
|
|
sidebar: { root: 'zh-guide', en: 'en-guide' },
|
|
section: { root: '入门', en: 'Guide' },
|
|
order: 1,
|
|
sourceAliases: ['docs/user/guide'],
|
|
},
|
|
{
|
|
source: 'docs/user/guide/providers.md',
|
|
route: 'guide/providers.md',
|
|
label: { root: '配置模型', en: 'Configure models' },
|
|
sidebar: { root: 'zh-guide', en: 'en-guide' },
|
|
section: { root: '入门', en: 'Guide' },
|
|
order: 2,
|
|
},
|
|
{
|
|
source: 'docs/user/guide/python-sdk.md',
|
|
route: 'guide/python-sdk.md',
|
|
label: { root: 'Python', en: 'Python' },
|
|
sidebar: { root: 'zh-guide', en: 'en-guide' },
|
|
section: { root: 'SDK', en: 'SDK' },
|
|
order: 1,
|
|
},
|
|
])
|
|
|
|
const develop = pairedPages([
|
|
{
|
|
source: 'docs/user/develop/basic/index.md',
|
|
route: 'develop/basic/index.md',
|
|
label: { root: '第一个 Harness 插件', en: 'Your first Harness plugin' },
|
|
sidebar: { root: 'zh-develop', en: 'en-develop' },
|
|
section: { root: '基础', en: 'Basics' },
|
|
order: 1,
|
|
sourceAliases: ['docs/user/develop/basic'],
|
|
},
|
|
{
|
|
source: 'docs/user/develop/basic/tool.md',
|
|
route: 'develop/basic/tool.md',
|
|
label: { root: '开发一个 Tool', en: 'Build a tool' },
|
|
sidebar: { root: 'zh-develop', en: 'en-develop' },
|
|
section: { root: '基础', en: 'Basics' },
|
|
order: 2,
|
|
},
|
|
{
|
|
source: 'docs/user/develop/basic/config.md',
|
|
route: 'develop/basic/config.md',
|
|
label: { root: '插件配置', en: 'Plugin configuration' },
|
|
sidebar: { root: 'zh-develop', en: 'en-develop' },
|
|
section: { root: '基础', en: 'Basics' },
|
|
order: 3,
|
|
},
|
|
{
|
|
source: 'docs/user/develop/basic/publish.md',
|
|
route: 'develop/basic/publish.md',
|
|
label: { root: '打包与安装插件', en: 'Package and install' },
|
|
sidebar: { root: 'zh-develop', en: 'en-develop' },
|
|
section: { root: '基础', en: 'Basics' },
|
|
order: 4,
|
|
},
|
|
{
|
|
source: 'docs/user/develop/framework/index.md',
|
|
route: 'develop/framework/index.md',
|
|
label: { root: '插件与生命周期', en: 'Plugin lifecycle' },
|
|
sidebar: { root: 'zh-develop', en: 'en-develop' },
|
|
section: { root: '框架能力', en: 'Framework' },
|
|
order: 1,
|
|
sourceAliases: ['docs/user/develop/framework'],
|
|
},
|
|
{
|
|
source: 'docs/user/develop/framework/service.md',
|
|
route: 'develop/framework/service.md',
|
|
label: { root: '服务与依赖', en: 'Services and dependencies' },
|
|
sidebar: { root: 'zh-develop', en: 'en-develop' },
|
|
section: { root: '框架能力', en: 'Framework' },
|
|
order: 2,
|
|
},
|
|
{
|
|
source: 'docs/user/develop/framework/events.md',
|
|
route: 'develop/framework/events.md',
|
|
label: { root: '事件系统', en: 'Event system' },
|
|
sidebar: { root: 'zh-develop', en: 'en-develop' },
|
|
section: { root: '框架能力', en: 'Framework' },
|
|
order: 3,
|
|
},
|
|
{
|
|
source: 'docs/user/develop/practice/index.md',
|
|
route: 'develop/practice/index.md',
|
|
label: { root: '能力的三层拆分', en: 'Capability layering' },
|
|
sidebar: { root: 'zh-develop', en: 'en-develop' },
|
|
section: { root: '实战', en: 'Practice' },
|
|
order: 1,
|
|
sourceAliases: ['docs/user/develop/practice'],
|
|
},
|
|
{
|
|
source: 'docs/user/develop/practice/llm-adapter.md',
|
|
route: 'develop/practice/llm-adapter.md',
|
|
label: { root: 'LLM 适配器', en: 'LLM adapter' },
|
|
sidebar: { root: 'zh-develop', en: 'en-develop' },
|
|
section: { root: '实战', en: 'Practice' },
|
|
order: 2,
|
|
},
|
|
])
|
|
|
|
const cordisTutorial = pairedPages(([
|
|
['index.md', '总览', 'Overview'],
|
|
['01-first-plugin.md', '1. 第一个插件', '1. Your first plugin'],
|
|
['02-lifecycle-and-effects.md', '2. 生命周期与副作用', '2. Lifecycle and effects'],
|
|
['03-services.md', '3. 服务', '3. Services'],
|
|
['04-events.md', '4. 事件', '4. Events'],
|
|
['05-config.md', '5. 配置', '5. Configuration'],
|
|
['06-composition-and-hmr.md', '6. 组合与热重载', '6. Composition and HMR'],
|
|
['07-into-the-harness.md', '7. 进入 Harness', '7. Into the harness'],
|
|
] as const).map(([file, rootLabel, enLabel], order): PairedPage => ({
|
|
source: `docs/cordis-tutorial/${file}`,
|
|
route: `develop/cordis-tutorial/${file}`,
|
|
label: { root: rootLabel, en: enLabel },
|
|
sidebar: { root: 'zh-develop', en: 'en-develop' },
|
|
section: { root: 'Cordis 框架教程', en: 'Cordis framework tutorial' },
|
|
order,
|
|
...(file === 'index.md' ? { sourceAliases: ['docs/cordis-tutorial'] } : {}),
|
|
})))
|
|
|
|
const cordisPrimerReference = pairedPages([
|
|
{
|
|
source: 'docs/cordis-primer.md',
|
|
route: 'reference/cordis-primer.md',
|
|
label: { root: 'Cordis 入门', en: 'Cordis primer' },
|
|
sidebar: { root: 'zh-reference', en: 'en-reference' },
|
|
section: { root: '概念', en: 'Concepts' },
|
|
order: 1,
|
|
},
|
|
])
|
|
|
|
/**
|
|
* Subsystem pages grouped by the concern they document, as `[Chinese section,
|
|
* English section, pages]`. One flat list of every subsystem pushed the rest of
|
|
* the reference sidebar below the fold.
|
|
*/
|
|
const subsystemGroups = [
|
|
['总览', 'Overview', [
|
|
['README.md', '子系统', 'Subsystems'],
|
|
]],
|
|
['内核与作用域', 'Core and scopes', [
|
|
['core.md', '核心', 'Core'],
|
|
['scope.md', '作用域', 'Scopes'],
|
|
['invariants.md', '运行时不变式', 'Runtime invariants'],
|
|
]],
|
|
['会话与持久化', 'Sessions and persistence', [
|
|
['session.md', '会话', 'Sessions'],
|
|
['session-query.md', '会话查询', 'Session query'],
|
|
['session-reference.md', '会话引用', 'Session references'],
|
|
['session-title.md', '会话标题', 'Session titles'],
|
|
['session-projection.md', '会话投影', 'Session projections'],
|
|
['persistence.md', '会话持久化', 'Session persistence'],
|
|
['spill.md', 'Spill 存储', 'Spill storage'],
|
|
['session-telemetry.md', '遥测', 'SessionTelemetryBackend'],
|
|
]],
|
|
['模型与上下文', 'Model and context', [
|
|
['llm-streaming.md', 'LLM 流式响应', 'LLM streaming'],
|
|
['token-meter.md', 'Token 计量', 'Token metering'],
|
|
['system-prompt.md', '系统提示词', 'System prompts'],
|
|
['compaction.md', '上下文压缩', 'Compaction'],
|
|
]],
|
|
['执行与工具', 'Execution and tools', [
|
|
['tools.md', '工具', 'Tools'],
|
|
['shell.md', 'Bash 执行', 'Bash execution'],
|
|
['subprocess.md', '子进程', 'Subprocesses'],
|
|
['terminal.md', 'PTY 会话', 'PTY sessions'],
|
|
['jobs.md', '后台任务', 'Background jobs'],
|
|
['filesystem.md', '文件系统', 'Filesystem'],
|
|
['lsp.md', 'LSP 导航', 'LSP navigation'],
|
|
['code-runtime.md', '代码运行时', 'Code runtime'],
|
|
['web.md', 'Web 访问', 'Web access'],
|
|
['skills.md', '技能', 'Skills'],
|
|
['workflow.md', '工作流', 'Workflows'],
|
|
['subagent.md', '子代理', 'Subagents'],
|
|
]],
|
|
['策略与交互', 'Policy and interaction', [
|
|
['approval.md', '审批', 'Approvals'],
|
|
['permission-presets.md', '权限预设', 'Permission presets'],
|
|
['sandbox.md', '沙箱', 'Sandboxing'],
|
|
['plan.md', '计划模式', 'Plan mode'],
|
|
['user-questions.md', '用户交互', 'User interaction'],
|
|
['commands.md', '命令', 'Human commands'],
|
|
['goal.md', '目标', 'Goals'],
|
|
['schedule.md', '定时提醒', 'Scheduled reminders'],
|
|
]],
|
|
['平台与接入', 'Platform and access', [
|
|
['web-server.md', 'HTTP 服务器', 'HTTP server'],
|
|
['typert.md', 'Typert', 'Typert'],
|
|
['client-modules.md', '客户端模块', 'Client modules'],
|
|
['storage.md', '存储', 'Storage'],
|
|
['workspace.md', '工作区', 'Workspaces'],
|
|
['settings.md', '用户设置', 'User settings'],
|
|
['credentials.md', '用户凭据', 'User credentials'],
|
|
]],
|
|
] as const
|
|
|
|
const subsystemsReference = subsystemGroups.flatMap(([rootSection, enSection, files]) => pairedPages(
|
|
files.map(([file, rootLabel, enLabel], order): PairedPage => ({
|
|
source: `docs/subsystems/${file}`,
|
|
route: file === 'README.md' ? 'reference/subsystems/index.md' : `reference/subsystems/${file}`,
|
|
label: { root: rootLabel, en: enLabel },
|
|
sidebar: { root: 'zh-reference', en: 'en-reference' },
|
|
section: { root: rootSection, en: enSection },
|
|
order,
|
|
// Subsystem pages carry long third-level sections a two-level outline reaches.
|
|
outline: [2, 3],
|
|
...(file === 'README.md' ? { sourceAliases: ['docs/subsystems'] } : {}),
|
|
})),
|
|
))
|
|
|
|
const reference = [
|
|
...pairedPages(([
|
|
['docs/architecture.md', 'reference/index.md', '架构', 'Architecture', 0],
|
|
] as const).map(([source, route, rootLabel, enLabel, order]): PairedPage => ({
|
|
source,
|
|
route,
|
|
label: { root: rootLabel, en: enLabel },
|
|
sidebar: { root: 'zh-reference', en: 'en-reference' },
|
|
section: { root: '概念', en: 'Concepts' },
|
|
order,
|
|
}))),
|
|
...pairedPages(([
|
|
['docs/capability-seams.md', 'reference/capability-seams.md', '能力服务', 'Capability services', 2],
|
|
['docs/agent-lifecycle.md', 'reference/agent-lifecycle.md', 'Agent 生命周期', 'Agent lifecycle', 3],
|
|
['docs/tool-execution-pipeline.md', 'reference/tool-execution-pipeline.md', 'Tool 执行', 'Tool execution', 4],
|
|
] as const).map(([source, route, rootLabel, enLabel, order]): PairedPage => ({
|
|
source,
|
|
route,
|
|
label: { root: rootLabel, en: enLabel },
|
|
sidebar: { root: 'zh-reference', en: 'en-reference' },
|
|
section: { root: '概念', en: 'Concepts' },
|
|
order,
|
|
}))),
|
|
...pairedPages(([
|
|
['docs/config-catalog.md', 'reference/config-catalog.md', '插件配置', 'Plugin configuration'],
|
|
['docs/tool-catalog.md', 'reference/tool-catalog.md', 'Tool Schema', 'Tool schemas'],
|
|
['docs/persistence-catalog.md', 'reference/persistence-catalog.md', '持久化事件', 'Persistence events', 'deep'],
|
|
] as const).map(([source, route, rootLabel, enLabel, outline], order): PairedPage => ({
|
|
source,
|
|
route,
|
|
label: { root: rootLabel, en: enLabel },
|
|
sidebar: { root: 'zh-reference', en: 'en-reference' },
|
|
section: { root: '生成参考', en: 'Generated reference' },
|
|
order,
|
|
...(outline === undefined ? {} : { outline }),
|
|
}))),
|
|
...pairedPages(([
|
|
['context.md', 'Context', 'Context'],
|
|
['events.md', 'Events', 'Events'],
|
|
['fiber.md', 'Fiber', 'Fiber'],
|
|
['registry.md', 'Plugin Registry', 'Plugin Registry'],
|
|
['service.md', 'Service', 'Service'],
|
|
] as const).map(([file, rootLabel, enLabel], order): PairedPage => ({
|
|
source: `docs/cordis-api/${file}`,
|
|
route: `reference/cordis-api/${file}`,
|
|
label: { root: rootLabel, en: enLabel },
|
|
sidebar: { root: 'zh-reference', en: 'en-reference' },
|
|
section: { root: 'Cordis API', en: 'Cordis Core API' },
|
|
order,
|
|
}))),
|
|
...mirroredPages(([
|
|
['inherited.md', '继承接口面', 'Inherited surface'],
|
|
] as const).map(([file, rootLabel, enLabel], order): MirroredPage => ({
|
|
source: `docs/cordis-api/${file}`,
|
|
route: `reference/cordis-api/${file}`,
|
|
contentLocale: 'en-US',
|
|
label: { root: rootLabel, en: enLabel },
|
|
sidebar: { root: 'zh-reference', en: 'en-reference' },
|
|
section: { root: 'Cordis API', en: 'Cordis Core API' },
|
|
order: order + 5,
|
|
}))),
|
|
...pairedPages(([
|
|
['adding-a-package.md', '新增 Package', 'Adding a package'],
|
|
['adding-a-tool.md', '新增 Tool', 'Adding a tool'],
|
|
['adding-an-llm-adapter.md', '新增 LLM Adapter', 'Adding an LLM adapter'],
|
|
['extension-cookbook.md', '扩展模式', 'Extension patterns'],
|
|
] as const).map(([file, rootLabel, enLabel], order): PairedPage => ({
|
|
source: `docs/cookbook/${file}`,
|
|
route: `reference/cookbook/${file}`,
|
|
label: { root: rootLabel, en: enLabel },
|
|
sidebar: { root: 'zh-reference', en: 'en-reference' },
|
|
section: { root: '开发手册', en: 'Cookbook' },
|
|
order,
|
|
}))),
|
|
...pairedPages([{
|
|
source: 'docs/cookbook/adding-a-conversation-node.md',
|
|
route: 'reference/cookbook/adding-a-conversation-node.md',
|
|
label: { root: '新增 Conversation Node', en: 'Adding a Conversation Node' },
|
|
sidebar: { root: 'zh-reference', en: 'en-reference' },
|
|
section: { root: '开发手册', en: 'Cookbook' },
|
|
order: 4,
|
|
}]),
|
|
]
|
|
|
|
/** A sidebar group, matched to pages by `label`. */
|
|
export interface DocsSection {
|
|
/** Group heading, equal to the `section` field of every page it holds. */
|
|
label: string
|
|
/** Render the group collapsed until it holds the page being read. */
|
|
collapsed?: boolean
|
|
}
|
|
|
|
/**
|
|
* Every sidebar group, in the order its locale renders it.
|
|
*
|
|
* The subsystem groups collapse because together they outnumber the rest of the
|
|
* reference sidebar; expanded, they push every other group below the fold.
|
|
*/
|
|
const sections: Record<DocsLocale, readonly DocsSection[]> = {
|
|
root: [
|
|
{ label: '入门' }, { label: 'SDK' },
|
|
{ label: '基础' }, { label: '框架能力' }, { label: '实战' }, { label: 'Cordis 框架教程' },
|
|
{ label: '概念' }, { label: '生成参考' }, { label: 'Cordis API' }, { label: '开发手册' },
|
|
{ label: '总览' },
|
|
{ label: '内核与作用域', collapsed: true },
|
|
{ label: '会话与持久化', collapsed: true },
|
|
{ label: '模型与上下文', collapsed: true },
|
|
{ label: '执行与工具', collapsed: true },
|
|
{ label: '策略与交互', collapsed: true },
|
|
{ label: '平台与接入', collapsed: true },
|
|
],
|
|
en: [
|
|
{ label: 'Guide' }, { label: 'SDK' },
|
|
{ label: 'Basics' }, { label: 'Framework' }, { label: 'Practice' }, { label: 'Cordis framework tutorial' },
|
|
{ label: 'Concepts' }, { label: 'Generated reference' }, { label: 'Cordis Core API' }, { label: 'Cookbook' },
|
|
{ label: 'Overview' },
|
|
{ label: 'Core and scopes', collapsed: true },
|
|
{ label: 'Sessions and persistence', collapsed: true },
|
|
{ label: 'Model and context', collapsed: true },
|
|
{ label: 'Execution and tools', collapsed: true },
|
|
{ label: 'Policy and interaction', collapsed: true },
|
|
{ label: 'Platform and access', collapsed: true },
|
|
],
|
|
}
|
|
|
|
/**
|
|
* Placement and collapse behavior of one sidebar group.
|
|
*
|
|
* @param locale - Route tree whose sidebar is being built.
|
|
* @param label - Section label carried by the pages in the group.
|
|
* @returns The declared group, plus its zero-based position in the locale.
|
|
* @throws When the locale declares no placement for the label. Ranking by list
|
|
* membership alone would sort an undeclared group silently ahead of every
|
|
* declared one.
|
|
*/
|
|
export function sectionSpec(locale: DocsLocale, label: string): DocsSection & { index: number } {
|
|
const declared = sections[locale]
|
|
const section = declared.find(candidate => candidate.label === label)
|
|
if (section === undefined) throw new Error(`Sidebar section "${label}" has no placement in the ${locale} locale.`)
|
|
return { ...section, index: declared.indexOf(section) }
|
|
}
|
|
|
|
/** Every canonical page published by the documentation website. */
|
|
export const docsPages: DocsPage[] = [
|
|
...homeAndGuide,
|
|
...develop,
|
|
...cordisTutorial,
|
|
...cordisPrimerReference,
|
|
...subsystemsReference,
|
|
...reference,
|
|
]
|
|
|
|
/**
|
|
* Pages of one sidebar collection, in the order the sidebar lists them.
|
|
*
|
|
* @param locale - Route tree whose sidebar is being built.
|
|
* @param collection - Sidebar collection to read.
|
|
* @returns The collection's pages, ordered by section placement then by `order`.
|
|
*/
|
|
export function orderedPages(locale: DocsLocale, collection: DocsSidebar): DocsPage[] {
|
|
return docsPages
|
|
.filter(page => page.locale === locale && page.sidebar === collection)
|
|
.sort((left, right) => (
|
|
sectionSpec(locale, left.section).index - sectionSpec(locale, right.section).index
|
|
|| left.order - right.order
|
|
))
|
|
}
|
|
|
|
/**
|
|
* Site-relative link for a published route.
|
|
*
|
|
* @param route - Manifest route, including its `.md` suffix.
|
|
* @returns The link VitePress serves the route at.
|
|
*/
|
|
export function routeLink(route: string): string {
|
|
return `/${route.replace(/(?:index)?\.md$/, '')}`
|
|
}
|
|
|
|
/**
|
|
* Where a top-level navigation item lands.
|
|
*
|
|
* The target is derived rather than written down: a collection whose first page
|
|
* is renamed or reordered would otherwise leave the navigation bar pointing at
|
|
* a route the manifest no longer publishes.
|
|
*
|
|
* @param locale - Route tree the navigation item belongs to.
|
|
* @param collection - Sidebar collection the item opens.
|
|
* @returns Site-relative link of the collection's first page.
|
|
* @throws When the collection publishes no page.
|
|
*/
|
|
export function landingLink(locale: DocsLocale, collection: DocsSidebar): string {
|
|
const first = orderedPages(locale, collection)[0]
|
|
if (first === undefined) throw new Error(`Sidebar collection "${collection}" publishes no page.`)
|
|
return routeLink(first.route)
|
|
}
|