docs: 部署脚本与项目文档

- scripts/ 部署与构建脚本
- README/.gitignore 等
This commit is contained in:
2026-08-23 22:31:36 +08:00
parent e2244a3cae
commit d33b25fd55
5 changed files with 325 additions and 0 deletions
+103
View File
@@ -0,0 +1,103 @@
#!/usr/bin/env node
/**
* Build search-index.json from docs/*.zh.md and *.en.md for client-side search.
* Run before vite build so dist gets the index.
*/
import { readdir, readFile, writeFile } from "fs/promises";
import { join } from "path";
import { fileURLToPath } from "url";
const __dirname = fileURLToPath(new URL(".", import.meta.url));
const docsDir = join(__dirname, "..", "public", "docs");
const outPath = join(__dirname, "..", "public", "search-index.json");
const EXCERPT_LEN = 800;
function slugifyHeading(text) {
const s = text
.trim()
.replace(/\s+/g, "-")
.replace(/[^a-zA-Z0-9_\-\u4e00-\u9fa5]/g, "");
return s || "section";
}
function stripMarkdownForExcerpt(md) {
return md
.replace(/<!--[\s\S]*?-->/g, " ")
.replace(/<svg[\s\S]*?<\/svg>/gi, " ")
.replace(/<[^>]+>/g, " ")
.replace(/^#+\s+.+$/gm, "")
.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1")
.replace(/[*_`#]/g, "")
.replace(/\s+/g, " ")
.trim();
}
function parseDoc(md) {
const lines = md.split("\n");
let title = "";
const headings = [];
let body = [];
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
const h2 = /^##\s+(.+)$/.exec(line);
const h3 = /^###\s+(.+)$/.exec(line);
if (i === 0 && line.startsWith("# ")) {
title = line
.slice(2)
.replace(/#+\s*$/, "")
.trim();
continue;
}
if (h2) {
const text = h2[1].replace(/#+\s*$/, "").trim();
headings.push({ level: 2, text, id: slugifyHeading(text) });
continue;
}
if (h3) {
const text = h3[1].replace(/#+\s*$/, "").trim();
headings.push({ level: 3, text, id: slugifyHeading(text) });
continue;
}
if (!title && line.trim()) title = line.replace(/#+\s*$/, "").trim();
body.push(line);
}
const fullBody = body.join("\n");
const excerpt = stripMarkdownForExcerpt(fullBody).slice(0, EXCERPT_LEN);
return { title: title || "Untitled", headings, excerpt };
}
async function main() {
const files = await readdir(docsDir);
const entries = [];
for (const f of files) {
const zhMatch = f.match(/^(.+)\.zh\.md$/);
const enMatch = f.match(/^(.+)\.en\.md$/);
const lang = zhMatch ? "zh" : enMatch ? "en" : null;
const slug = zhMatch?.[1] ?? enMatch?.[1];
if (!lang || !slug) continue;
const path = join(docsDir, f);
const md = await readFile(path, "utf-8");
const { title, headings, excerpt } = parseDoc(md);
entries.push({
slug,
lang,
title,
headings: headings.map((h) => ({ text: h.text, id: h.id })),
excerpt,
});
}
await writeFile(outPath, JSON.stringify(entries), "utf-8");
console.log(`Wrote ${entries.length} doc entries to ${outPath}`);
}
main().catch((e) => {
console.error(e);
process.exit(1);
});
+57
View File
@@ -0,0 +1,57 @@
#!/usr/bin/env node
/**
* After vite build, copy index.html into docs/ and docs/<slug>/ so that
* static hosts (e.g. GitHub Pages) serve the SPA for each route without
* needing a Node server. Must match DOC_SLUGS in src/pages/Docs.tsx.
*/
import { mkdir, readFile, writeFile } from "fs/promises";
import { dirname, join } from "path";
import { fileURLToPath } from "url";
const __dirname = fileURLToPath(new URL(".", import.meta.url));
const distDir = join(__dirname, "..", "dist");
const DOC_SLUGS = [
"intro",
"quickstart",
"console",
"channels",
"skills",
"memory",
"compact",
"commands",
"heartbeat",
"config",
"backup",
"cli",
"creator",
"community",
"contributing",
];
async function main() {
const indexHtml = await readFile(join(distDir, "index.html"), "utf-8");
const BLOG_SLUGS = [
"introducing-qwenpaw-driver",
"qwenpaw-developer-day-collection",
"play-with-qwenpaw-pet",
];
const paths = [
"docs",
"docs/search",
...DOC_SLUGS.map((s) => `docs/${s}`),
"blog",
...BLOG_SLUGS.map((s) => `blog/${s}`),
];
for (const p of paths) {
const out = join(distDir, p, "index.html");
await mkdir(dirname(out), { recursive: true });
await writeFile(out, indexHtml);
}
console.log("[spa-fallback-pages] Wrote index.html for /, /docs, /docs/*");
}
main().catch((err) => {
console.error(err);
process.exit(1);
});