From 96945c6dbffc3bd8a96f3a228a4650712a6f4f84 Mon Sep 17 00:00:00 2001 From: ooooo <3164076421@qq.com> Date: Mon, 14 Sep 2026 21:44:27 +0800 Subject: [PATCH 1/3] fix(docs): fix relative repo links in built pages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # 🐛 Bug Fix - Rewrite relative Markdown links to existing repository files outside docs/ before VitePress applies HTML link normalization. - Use GitHub blob/main URLs for files and tree/main URLs for directories, preserving query strings and fragments. - Keep documentation links and missing or out-of-repository targets unchanged. ## Validation - Run pre-commit run --all-files --show-diff-on-failure successfully. - Build the VitePress site and verify the generated source links in both locales. - Confirm the train.py file and rollout directory URLs are accessible on GitHub. Fixes #313 --- docs/.vitepress/config.mts | 39 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index 3e5ef1c0..e95a47b1 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -1,6 +1,37 @@ import taskLists from 'markdown-it-task-lists' +import { statSync } from 'node:fs' +import { dirname, isAbsolute, relative, resolve, sep } from 'node:path' +import { fileURLToPath } from 'node:url' import { defineConfig } from 'vitepress' +const repoRoot = fileURLToPath(new URL('../../', import.meta.url)) +const sourceRepo = 'https://github.com/redai-studio/Relax' +const sourceRef = 'main' + +function toSourceUrl(href: string, documentPath?: string): string { + if (!documentPath) return href + if (!href.startsWith('../') && !href.startsWith('./')) return href + + const [, pathname, suffix] = href.match(/^([^?#]*)(.*)$/)! + let targetPath: string + try { + targetPath = resolve(dirname(documentPath), decodeURIComponent(pathname)) + } catch { + return href + } + + const repoPath = relative(repoRoot, targetPath) + const pathParts = repoPath.split(sep) + if (isAbsolute(repoPath) || pathParts[0] === '..' || pathParts[0] === 'docs') return href + + const targetStats = statSync(targetPath, { throwIfNoEntry: false }) + if (!targetStats) return href + + const kind = targetStats.isDirectory() ? 'tree' : 'blob' + const urlPath = pathParts.map(encodeURIComponent).join('/') + return `${sourceRepo}/${kind}/${sourceRef}/${urlPath}${suffix}` +} + // https://vitepress.dev/reference/site-config export default defineConfig({ vite: { @@ -215,6 +246,14 @@ export default defineConfig({ math: true, config(md) { md.use(taskLists) + + const renderLink = md.renderer.rules.link_open! + md.renderer.rules.link_open = (tokens, index, options, env, self) => { + const token = tokens[index] + const href = token.attrGet('href') + if (href) token.attrSet('href', toSourceUrl(href, env.path)) + return renderLink(tokens, index, options, env, self) + } } }, From 18f85026d26726297f98957162b1c7b72e3c253f Mon Sep 17 00:00:00 2001 From: ooooo <3164076421@qq.com> Date: Thu, 17 Sep 2026 22:53:24 +0800 Subject: [PATCH 2/3] fix(docs): resolve source links in a plugin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # 🐛 Bug Fix - Resolve repository links against the original Markdown path when VitePress rewrites page routes, falling back to the rendered path when needed. - Warn about missing or inaccessible targets inside the repository and outside the documentation directory without failing the documentation build. # ♻️ Refactor - Extract source-link conversion into a small Markdown plugin configured with the repository URL, branch, and absolute VitePress source directory. - Keep access to VitePress's internal resolved configuration in the site config and document the repository-parent directory convention. - Preserve file and directory URLs, query strings, fragments, external-link attributes, and normal documentation link rendering. ## Validation - Pass pre-commit run --all-files --show-diff-on-failure. - Build the documentation with VitePress 1.6.4 and verify 98 source links across English and Chinese pages. - Exercise rewritten paths, missing JSON targets, filesystem errors, and warning exclusions with the actual VitePress Markdown renderer. Refs #313 --- docs/.vitepress/config.mts | 49 ++++----------------- docs/.vitepress/plugins/source-links.ts | 58 +++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 40 deletions(-) create mode 100644 docs/.vitepress/plugins/source-links.ts diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index e95a47b1..e2a54793 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -1,36 +1,6 @@ import taskLists from 'markdown-it-task-lists' -import { statSync } from 'node:fs' -import { dirname, isAbsolute, relative, resolve, sep } from 'node:path' -import { fileURLToPath } from 'node:url' -import { defineConfig } from 'vitepress' - -const repoRoot = fileURLToPath(new URL('../../', import.meta.url)) -const sourceRepo = 'https://github.com/redai-studio/Relax' -const sourceRef = 'main' - -function toSourceUrl(href: string, documentPath?: string): string { - if (!documentPath) return href - if (!href.startsWith('../') && !href.startsWith('./')) return href - - const [, pathname, suffix] = href.match(/^([^?#]*)(.*)$/)! - let targetPath: string - try { - targetPath = resolve(dirname(documentPath), decodeURIComponent(pathname)) - } catch { - return href - } - - const repoPath = relative(repoRoot, targetPath) - const pathParts = repoPath.split(sep) - if (isAbsolute(repoPath) || pathParts[0] === '..' || pathParts[0] === 'docs') return href - - const targetStats = statSync(targetPath, { throwIfNoEntry: false }) - if (!targetStats) return href - - const kind = targetStats.isDirectory() ? 'tree' : 'blob' - const urlPath = pathParts.map(encodeURIComponent).join('/') - return `${sourceRepo}/${kind}/${sourceRef}/${urlPath}${suffix}` -} +import { defineConfig, type SiteConfig } from 'vitepress' +import sourceLinks from './plugins/source-links' // https://vitepress.dev/reference/site-config export default defineConfig({ @@ -245,15 +215,14 @@ export default defineConfig({ markdown: { math: true, config(md) { + // VitePress exposes its resolved config through this internal global. + const { srcDir } = (globalThis as typeof globalThis & { VITEPRESS_CONFIG: SiteConfig }).VITEPRESS_CONFIG md.use(taskLists) - - const renderLink = md.renderer.rules.link_open! - md.renderer.rules.link_open = (tokens, index, options, env, self) => { - const token = tokens[index] - const href = token.attrGet('href') - if (href) token.attrSet('href', toSourceUrl(href, env.path)) - return renderLink(tokens, index, options, env, self) - } + md.use(sourceLinks, { + repo: 'https://github.com/redai-studio/Relax', + branch: 'main', + srcDir + }) } }, diff --git a/docs/.vitepress/plugins/source-links.ts b/docs/.vitepress/plugins/source-links.ts new file mode 100644 index 00000000..80d07ac1 --- /dev/null +++ b/docs/.vitepress/plugins/source-links.ts @@ -0,0 +1,58 @@ +import { statSync } from 'node:fs' +import { dirname, isAbsolute, relative, resolve, sep } from 'node:path' +import type { MarkdownRenderer } from 'vitepress' + +interface SourceLinksOptions { + repo: string + branch: string + /** Absolute path to the VitePress Markdown source directory. */ + srcDir: string +} + +/** Link repository files to GitHub; the VitePress source directory sits directly inside the repo. */ +export default function sourceLinks(md: MarkdownRenderer, options: SourceLinksOptions): void { + const { repo, branch, srcDir } = options + const docsRoot = resolve(srcDir) + const repoRoot = dirname(docsRoot) + + function toSourceUrl(href: string, documentPath?: string): string { + if (!documentPath || !/^\.\.?\//.test(href)) return href + + const [, pathname, suffix] = href.match(/^([^?#]*)(.*)$/)! + let targetPath: string + try { + targetPath = resolve(dirname(documentPath), decodeURIComponent(pathname)) + } catch { + return href + } + + const repoPath = relative(repoRoot, targetPath) + const pathParts = repoPath.split(sep) + if (isAbsolute(repoPath) || pathParts[0] === '..') return href + if (targetPath === docsRoot || targetPath.startsWith(docsRoot + sep)) return href + + try { + const targetStats = statSync(targetPath, { throwIfNoEntry: false }) + if (!targetStats) { + console.warn(`[source-links] ${documentPath}: "${href}" points to missing target ${targetPath}`) + return href + } + + const kind = targetStats.isDirectory() ? 'tree' : 'blob' + const urlPath = pathParts.map(encodeURIComponent).join('/') + return `${repo.replace(/\/$/, '')}/${kind}/${encodeURIComponent(branch)}/${urlPath}${suffix}` + } catch (error) { + console.warn(`[source-links] ${documentPath}: cannot resolve "${href}": ${error}`) + return href + } + } + + // Rewrite before VitePress normalizes local links into HTML page URLs. + const renderLink = md.renderer.rules.link_open! + md.renderer.rules.link_open = (tokens, index, options, env, self) => { + const token = tokens[index] + const href = token.attrGet('href') + if (href) token.attrSet('href', toSourceUrl(href, env?.realPath ?? env?.path)) + return renderLink(tokens, index, options, env, self) + } +} From 015dbeddf3c4cbd621342ea0405f5a6a36612209 Mon Sep 17 00:00:00 2001 From: ooooo <3164076421@qq.com> Date: Thu, 17 Sep 2026 23:18:11 +0800 Subject: [PATCH 3/3] refactor(docs): configure explicit source roots MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # ♻️ Refactor - Pass independent repository and documentation roots to the source-link plugin instead of reading VitePress's internal configuration global. - Derive the site's absolute repository root from import.meta.url and resolve docsRoot relative to it, keeping builds independent of the working directory. - Support nested and absolute documentation roots without assuming the docs directory is a direct child of the repository. ## Validation - Run pre-commit checks across all files. - Build the VitePress 1.6.4 site from /tmp and verify all 98 source links in the generated English and Chinese pages. - Exercise relative and absolute documentation roots from multiple working directories with the actual VitePress Markdown renderer. --- docs/.vitepress/config.mts | 8 ++++---- docs/.vitepress/plugins/source-links.ts | 12 ++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index e2a54793..4979df40 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -1,5 +1,6 @@ import taskLists from 'markdown-it-task-lists' -import { defineConfig, type SiteConfig } from 'vitepress' +import { fileURLToPath } from 'node:url' +import { defineConfig } from 'vitepress' import sourceLinks from './plugins/source-links' // https://vitepress.dev/reference/site-config @@ -215,13 +216,12 @@ export default defineConfig({ markdown: { math: true, config(md) { - // VitePress exposes its resolved config through this internal global. - const { srcDir } = (globalThis as typeof globalThis & { VITEPRESS_CONFIG: SiteConfig }).VITEPRESS_CONFIG md.use(taskLists) md.use(sourceLinks, { repo: 'https://github.com/redai-studio/Relax', branch: 'main', - srcDir + repoRoot: fileURLToPath(new URL('../..', import.meta.url)), + docsRoot: 'docs' }) } }, diff --git a/docs/.vitepress/plugins/source-links.ts b/docs/.vitepress/plugins/source-links.ts index 80d07ac1..f45d68f5 100644 --- a/docs/.vitepress/plugins/source-links.ts +++ b/docs/.vitepress/plugins/source-links.ts @@ -5,15 +5,15 @@ import type { MarkdownRenderer } from 'vitepress' interface SourceLinksOptions { repo: string branch: string - /** Absolute path to the VitePress Markdown source directory. */ - srcDir: string + repoRoot: string + docsRoot: string } -/** Link repository files to GitHub; the VitePress source directory sits directly inside the repo. */ +/** Link repository files outside the documentation source directory to GitHub. */ export default function sourceLinks(md: MarkdownRenderer, options: SourceLinksOptions): void { - const { repo, branch, srcDir } = options - const docsRoot = resolve(srcDir) - const repoRoot = dirname(docsRoot) + const { repo, branch } = options + const repoRoot = resolve(options.repoRoot) + const docsRoot = resolve(repoRoot, options.docsRoot) function toSourceUrl(href: string, documentPath?: string): string { if (!documentPath || !/^\.\.?\//.test(href)) return href