Repository navigation
Expand file tree
/
Copy pathsync-docs.mjs
More file actions
129 lines (115 loc) · 5.49 KB
/
Copy pathsync-docs.mjs
File metadata and controls
129 lines (115 loc) · 5.49 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
// Sync the user guide + changelog from the repo into the VitePress site (both locales).
//
// The repo is the single source of truth; this copies content into the site at
// dev/build time (the destinations are gitignored — never edit them by hand):
// docs/guide/*.md (English, canonical) → guide/ (EN root locale)
// docs/guide/zh-CN/*.md (Chinese) → zh/guide/ (zh locale)
// CHANGELOG.md (English, canonical) → changelog.md (EN root locale)
// CHANGELOG.zh-CN.md (Chinese) → zh/changelog.md (zh locale)
// Links that escape the guide (e.g. ../arch/, ../../README, or CHANGELOG's LICENSE)
// point at pages the site does not host, so they are rewritten to absolute GitHub
// URLs; intra-guide links stay relative for VitePress to resolve. The per-file
// language switcher (a "**English** · [简体中文]" line) is stripped — the site has
// its own locale menu, and the switcher's cross-locale relative links don't map
// onto the site.
import { readdir, readFile, writeFile, rm, mkdir } from 'node:fs/promises'
import path from 'node:path'
const CWD = process.cwd() // website/
const REPO_BLOB = 'https://github.com/huhamhire/code-meeseeks/blob/master'
// One sync unit: source guide dir, where it lives in the repo (for resolving
// escaping links), and the destination dir under the site.
const LOCALES = [
{ src: path.resolve(CWD, '../docs/guide'), repoDir: 'docs/guide', dest: path.resolve(CWD, 'guide') },
{
src: path.resolve(CWD, '../docs/guide/zh-CN'),
repoDir: 'docs/guide/zh-CN',
dest: path.resolve(CWD, 'zh/guide'),
},
]
// Standalone top-level docs synced from the repo root (single source of truth).
// repoDir '.' resolves their escaping links (e.g. CHANGELOG's LICENSE) against the
// repo root, so they rewrite to absolute GitHub URLs.
const SINGLES = [
{ src: path.resolve(CWD, '../CHANGELOG.md'), repoDir: '.', dest: path.resolve(CWD, 'changelog.md') },
{
src: path.resolve(CWD, '../CHANGELOG.zh-CN.md'),
repoDir: '.',
dest: path.resolve(CWD, 'zh/changelog.md'),
},
]
// Matches the per-file language switcher line, both directions.
const SWITCHER_RE = /^(?:\*\*English\*\*|\[English\]\()[^\n]*(?:简体中文)[^\n]*$/
// Rewrite a single markdown link target, resolving relative links against the
// guide's location in the repo (repoDir).
function rewriteTarget(target, repoDir) {
if (/^(https?:)?\/\//.test(target) || target.startsWith('#') || target.startsWith('/')) {
return target
}
const [rel, hash] = target.split('#')
if (!rel) return target // pure anchor
const resolved = path.posix.normalize(path.posix.join(repoDir, rel))
if (resolved.startsWith('docs/guide/')) {
return target // stays inside the guide → keep relative
}
// Escapes the guide → link to the file on GitHub.
return `${REPO_BLOB}/${resolved}${hash ? '#' + hash : ''}`
}
function rewriteLinks(md, repoDir) {
return md.replace(/\]\(([^)]+)\)/g, (_m, target) => `](${rewriteTarget(target, repoDir)})`)
}
// Give changelog version headings a stable, date-independent anchor so release
// notes can deep-link to a version: `## [0.10.0] - 2026-07-05` gains a trailing
// `{#v0-10-0}` (VitePress custom-anchor syntax). GitHub's CHANGELOG stays clean —
// the anchor lives only in the synced site copy. release.yml builds the same slug
// from the tag (`v` + version, dots → hyphens). `## [Unreleased]` (non-numeric) is
// left untouched.
function addVersionAnchors(md) {
return md.replace(
/^(## \[)(\d[^\]]*)(\][^\n]*)$/gm,
(_line, open, version, rest) => `${open}${version}${rest} {#v${version.replace(/\./g, '-')}}`,
)
}
// Drop the language-switcher line (and a single adjacent blank line so we don't
// leave a stray gap under the H1).
function stripSwitcher(md) {
const lines = md.split('\n')
const i = lines.findIndex((l) => SWITCHER_RE.test(l.trim()))
if (i === -1) return md
lines.splice(i, 1)
if (lines[i] === '' && lines[i - 1] === '') lines.splice(i, 1)
return lines.join('\n')
}
async function syncLocale({ src, repoDir, dest }) {
await rm(dest, { recursive: true, force: true })
await mkdir(dest, { recursive: true })
const entries = await readdir(src, { withFileTypes: true })
let count = 0
for (const entry of entries) {
if (!entry.isFile() || !entry.name.endsWith('.md')) continue // skip zh-CN/ subdir on the EN pass
const raw = await readFile(path.join(src, entry.name), 'utf8')
const md = rewriteLinks(stripSwitcher(raw), repoDir)
// README.md is the guide index → index.md
const outName = entry.name === 'README.md' ? 'index.md' : entry.name
await writeFile(path.join(dest, outName), md, 'utf8')
count++
}
console.log(`[sync-docs] copied ${count} guide file(s) → ${path.relative(CWD, dest)}/`)
}
// Sync one standalone file (the CHANGELOGs): strip the switcher, rewrite escaping
// links, add stable version anchors, write to the destination page (creating the
// parent dir if needed).
async function syncSingle({ src, repoDir, dest }) {
const raw = await readFile(src, 'utf8')
const md = addVersionAnchors(rewriteLinks(stripSwitcher(raw), repoDir))
await mkdir(path.dirname(dest), { recursive: true })
await writeFile(dest, md, 'utf8')
console.log(`[sync-docs] copied ${path.basename(src)} → ${path.relative(CWD, dest)}`)
}
async function main() {
for (const locale of LOCALES) await syncLocale(locale)
for (const single of SINGLES) await syncSingle(single)
}
main().catch((err) => {
console.error('[sync-docs] failed:', err)
process.exit(1)
})