mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-17 22:05:50 +00:00
docs: 文档大重组,对齐 README 入口
以 README 为单一事实来源,重构整个 docs/ 目录。 最终结构(3 大组、15 篇文档): - 开始: installation / quickstart / model-providers - 核心功能: pipes-and-lan、acp、channels、chrome-control、computer-use、 voice-mode、web-browser-tool、auto-dream、remote-control-self-hosting、 langfuse-monitoring - 内部机制: growthbook-adapter、sentry-setup 主要变更: - 删除 56 个 README 未提及的文档(architecture 全部 / guides 全部 / features 中未在 README 出现的 20 篇 / internals 中的 5 篇) - 合并 6 组重复文档(pipes-and-lan、chrome-control、acp、computer-use、 auto-dream、coordinator-mode 简化为入口) - features 子组从 5 → 4,ui/ 合并入 tools/ - 所有保留文档加上人性化 frontmatter(title/description/keywords) - docs.json navigation 简化为 3 大组,redirects 重新过滤为 7 条合并跳转 - 新增 docs.md 工作大纲与验证脚本(verify-docs / check-docs-orphans / dump-docs-outline) 总计 130 文件改动,从约 35000 行精简到约 2000 行。 Co-Authored-By: glm-5.2 <zai-org@claude-code-best.win>
This commit is contained in:
54
scripts/check-docs-orphans.mjs
Normal file
54
scripts/check-docs-orphans.mjs
Normal file
@@ -0,0 +1,54 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* 反向校验:找出 docs/ 下所有 .md/.mdx 文件,但 docs.json 导航没引用的。
|
||||
* 用法: node scripts/check-docs-orphans.mjs
|
||||
*/
|
||||
import { readFileSync, readdirSync, statSync, existsSync } from 'node:fs'
|
||||
import { resolve, dirname, relative, join } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url))
|
||||
const ROOT = resolve(__dirname, '..')
|
||||
const DOCS = resolve(ROOT, 'docs')
|
||||
|
||||
const docsJson = JSON.parse(readFileSync(resolve(ROOT, 'docs.json'), 'utf8'))
|
||||
|
||||
const referenced = new Set()
|
||||
const walk = pages => {
|
||||
if (!Array.isArray(pages)) return
|
||||
for (const p of pages) {
|
||||
if (typeof p === 'string') {
|
||||
referenced.add(p.replace(/^docs\//, ''))
|
||||
} else if (p && Array.isArray(p.pages)) {
|
||||
walk(p.pages)
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const g of docsJson?.navigation?.groups ?? []) walk(g.pages)
|
||||
|
||||
const collectMd = dir => {
|
||||
const out = []
|
||||
for (const name of readdirSync(dir)) {
|
||||
if (name === 'logo' || name === 'images') continue
|
||||
const full = join(dir, name)
|
||||
const rel = relative(DOCS, full).replace(/\\/g, '/')
|
||||
if (statSync(full).isDirectory()) {
|
||||
out.push(...collectMd(full))
|
||||
} else if (name.endsWith('.md') || name.endsWith('.mdx')) {
|
||||
out.push(rel.replace(/\.(md|mdx)$/, ''))
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
const all = collectMd(DOCS)
|
||||
const orphans = all.filter(p => !referenced.has(p))
|
||||
|
||||
if (orphans.length === 0) {
|
||||
console.log(`✅ All ${all.length} docs files are referenced in navigation.`)
|
||||
process.exit(0)
|
||||
} else {
|
||||
console.warn(`⚠️ ${orphans.length} orphan doc(s) not in navigation:`)
|
||||
for (const o of orphans) console.warn(' - docs/' + o)
|
||||
process.exit(0) // 不失败,只是提示
|
||||
}
|
||||
92
scripts/dump-docs-outline.mjs
Normal file
92
scripts/dump-docs-outline.mjs
Normal file
@@ -0,0 +1,92 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* 扫描所有 docs/ 文档的 frontmatter,按 docs.json 的导航结构输出完整大纲。
|
||||
* 输出到 stdout,可重定向到文件。
|
||||
*/
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { resolve, dirname } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url))
|
||||
const ROOT = resolve(__dirname, '..')
|
||||
|
||||
const docsJson = JSON.parse(readFileSync(resolve(ROOT, 'docs.json'), 'utf8'))
|
||||
|
||||
const EXTS = ['.mdx', '.md']
|
||||
|
||||
const readFrontmatter = relPath => {
|
||||
for (const ext of EXTS) {
|
||||
try {
|
||||
const full = resolve(ROOT, relPath + ext)
|
||||
const text = readFileSync(full, 'utf8')
|
||||
// 1) 优先 YAML frontmatter
|
||||
const m = text.match(/^---\n([\s\S]*?)\n---/)
|
||||
let title = ''
|
||||
let description = ''
|
||||
if (m) {
|
||||
const fm = m[1]
|
||||
const titleM = fm.match(/^title:\s*"?(.+?)"?\s*$/m)
|
||||
const descM = fm.match(/^description:\s*"?(.+?)"?\s*$/m)
|
||||
title = titleM ? titleM[1] : ''
|
||||
description = descM ? descM[1] : ''
|
||||
}
|
||||
// 2) fallback: 第一个 H1 标题
|
||||
if (!title) {
|
||||
const h1M = text.match(/^#\s+(.+?)\s*$/m)
|
||||
if (h1M) title = h1M[1].replace(/\s*[—–-].*$/, '').trim()
|
||||
}
|
||||
// 3) fallback: 第一个段落 / 引用作为描述
|
||||
if (!description) {
|
||||
const bodyM = text.match(/(?:^|\n)(?:>\s*(.+?)|([^>\n#][^\n]+))\n/)
|
||||
if (bodyM)
|
||||
description = (bodyM[1] || bodyM[2] || '').replace(/^>\s*/, '').trim()
|
||||
}
|
||||
return { title: title || '(无标题)', description }
|
||||
} catch {}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
let lines = []
|
||||
let groupIdx = 0
|
||||
|
||||
const emitPage = (pageRef, depth) => {
|
||||
const fm = readFrontmatter(pageRef)
|
||||
const indent = ' '.repeat(depth)
|
||||
if (!fm) {
|
||||
lines.push(`${indent}- ❓ _MISSING: ${pageRef}_`)
|
||||
return
|
||||
}
|
||||
const short = pageRef.replace(/^docs\//, '')
|
||||
const desc = fm.description ? ` — ${fm.description}` : ''
|
||||
lines.push(`${indent}- \`${short}\` — **${fm.title || '(无标题)'}**${desc}`)
|
||||
}
|
||||
|
||||
const emitGroup = (pages, depth) => {
|
||||
for (const p of pages) {
|
||||
if (typeof p === 'string') {
|
||||
emitPage(p, depth)
|
||||
} else if (p && p.group) {
|
||||
const indent = ' '.repeat(depth)
|
||||
lines.push(`${indent}- ### ${p.group}`)
|
||||
if (p.pages) emitGroup(p.pages, depth + 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lines.push('# Claude Code Best 文档大纲')
|
||||
lines.push('')
|
||||
lines.push(
|
||||
`> 自动生成自 docs.json 与各文档 frontmatter。共 ${docsJson.navigation.groups.length} 个顶级分组。`,
|
||||
)
|
||||
lines.push('')
|
||||
|
||||
for (const g of docsJson.navigation.groups) {
|
||||
groupIdx++
|
||||
lines.push(`## ${groupIdx}. ${g.group}`)
|
||||
lines.push('')
|
||||
emitGroup(g.pages, 0)
|
||||
lines.push('')
|
||||
}
|
||||
|
||||
console.log(lines.join('\n'))
|
||||
45
scripts/verify-docs.mjs
Normal file
45
scripts/verify-docs.mjs
Normal file
@@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* 校验 docs.json 引用的所有页面文件是否真实存在。
|
||||
* 用法: node scripts/verify-docs.mjs
|
||||
*/
|
||||
import { readFileSync, existsSync } from 'node:fs'
|
||||
import { resolve, dirname } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url))
|
||||
const ROOT = resolve(__dirname, '..')
|
||||
|
||||
const docsJson = JSON.parse(readFileSync(resolve(ROOT, 'docs.json'), 'utf8'))
|
||||
|
||||
const referenced = new Set()
|
||||
const walk = pages => {
|
||||
if (!Array.isArray(pages)) return
|
||||
for (const p of pages) {
|
||||
if (typeof p === 'string') {
|
||||
referenced.add(p)
|
||||
} else if (p && Array.isArray(p.pages)) {
|
||||
walk(p.pages)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const groups = docsJson?.navigation?.groups ?? []
|
||||
for (const g of groups) walk(g.pages)
|
||||
|
||||
const candidates = ['.mdx', '.md']
|
||||
const missing = []
|
||||
for (const ref of referenced) {
|
||||
const base = resolve(ROOT, ref)
|
||||
const found = candidates.some(ext => existsSync(base + ext))
|
||||
if (!found) missing.push(ref)
|
||||
}
|
||||
|
||||
if (missing.length === 0) {
|
||||
console.log(`✅ All ${referenced.size} referenced pages exist.`)
|
||||
process.exit(0)
|
||||
} else {
|
||||
console.error(`❌ ${missing.length} missing page(s):`)
|
||||
for (const m of missing) console.error(' - ' + m)
|
||||
process.exit(1)
|
||||
}
|
||||
Reference in New Issue
Block a user